diff options
849 files changed, 31032 insertions, 10782 deletions
diff --git a/Android.bp b/Android.bp index 4e7eba26c6f1..cd55dcf999d8 100644 --- a/Android.bp +++ b/Android.bp @@ -100,9 +100,9 @@ filegroup { ":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.hardware.tv.tuner-V2-java-source", ":android.security.apc-java-source", ":android.security.authorization-java-source", ":android.security.legacykeystore-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 e3bd5acd7bc7..dcc6aa6a1d67 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.annotation.SuppressLint; 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 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService } 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 @@ public class DeviceIdleController extends SystemService 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 @@ public class DeviceIdleController extends SystemService } 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 @@ public class DeviceIdleController extends SystemService 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 5e189f2c1340..7b38bd11d847 100644 --- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h +++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h @@ -39,7 +39,7 @@ class BinaryStreamVisitor : public Visitor { 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 4b271a1ff96f..89769246434a 100644 --- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp +++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp @@ -38,7 +38,7 @@ void BinaryStreamVisitor::Write32(uint32_t value) { 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 d517e29f3369..dd5be21cd164 100644 --- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp +++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp @@ -101,10 +101,10 @@ FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue( } 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 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { 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 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { .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 813dff1c141c..7c0b937122c7 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -317,7 +317,7 @@ Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping( } 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 4e3f54d2583e..76c70cab6296 100644 --- a/cmds/idmap2/libidmap2/PolicyUtils.cpp +++ b/cmds/idmap2/libidmap2/PolicyUtils.cpp @@ -53,7 +53,7 @@ std::vector<std::string> BitmaskToPolicies(const PolicyBitmask& bitmask) { 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 bb31c11d629c..b2300cea3a68 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -89,7 +89,7 @@ Result<Unit> CheckOverlayable(const TargetResourceContainer& target, // 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 7d804938dc38..26e20f601c7a 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 android.os.ServiceManager; 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 @@ public class UsbCommand extends Svc.Command { 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 5f875378b04e..f7a9a505f80b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1041,6 +1041,7 @@ package android { 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 @@ package android.app { 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); @@ -12474,6 +12476,7 @@ package android.content.pm { 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; @@ -13024,10 +13027,12 @@ package android.credentials { } 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; } @@ -13065,10 +13070,11 @@ package android.credentials { } 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; } @@ -15480,7 +15486,6 @@ package android.graphics { } 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(); @@ -32697,7 +32702,6 @@ package android.os { 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(); @@ -32711,7 +32715,6 @@ package android.os { 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); @@ -45667,6 +45670,19 @@ package android.text { method public int previousStartBoundary(@IntRange(from=0) int); } + public class Highlights { + method @NonNull public android.graphics.Paint getPaint(int); + method @NonNull public int[] getRanges(int); + method public int getSize(); + } + + public static final class Highlights.Builder { + ctor public Highlights.Builder(); + method @NonNull public android.text.Highlights.Builder addRange(@NonNull android.graphics.Paint, int, int); + method @NonNull public android.text.Highlights.Builder addRanges(@NonNull android.graphics.Paint, @NonNull int...); + method @NonNull public android.text.Highlights build(); + } + public class Html { method public static String escapeHtml(CharSequence); method @Deprecated public static android.text.Spanned fromHtml(String); @@ -45758,6 +45774,9 @@ package android.text { ctor protected Layout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float); method public void draw(android.graphics.Canvas); method public void draw(android.graphics.Canvas, android.graphics.Path, android.graphics.Paint, int); + method public void draw(@NonNull android.graphics.Canvas, @Nullable java.util.List<android.graphics.Path>, @Nullable java.util.List<android.graphics.Paint>, @Nullable android.graphics.Path, @Nullable android.graphics.Paint, int); + method public void drawBackground(@NonNull android.graphics.Canvas); + method public void drawText(@NonNull android.graphics.Canvas); method public void fillCharacterBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int); method public final android.text.Layout.Alignment getAlignment(); method public abstract int getBottomPadding(); @@ -48692,6 +48711,7 @@ package android.view { 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(); @@ -48772,6 +48792,13 @@ package android.view { 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(); @@ -52153,6 +52180,7 @@ package android.view { 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(); @@ -52188,6 +52216,7 @@ package android.view { 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); @@ -58561,6 +58590,7 @@ package android.widget { method public boolean getFreezesText(); method public int getGravity(); method @ColorInt public int getHighlightColor(); + method @Nullable public android.text.Highlights getHighlights(); method public CharSequence getHint(); method public final android.content.res.ColorStateList getHintTextColors(); method public int getHyphenationFrequency(); @@ -58690,6 +58720,7 @@ package android.widget { method public void setGravity(int); method public void setHeight(int); method public void setHighlightColor(@ColorInt int); + method public void setHighlights(@Nullable android.text.Highlights); method public final void setHint(CharSequence); method public final void setHint(@StringRes int); method public final void setHintTextColor(@ColorInt int); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index b6e2d2a3e98b..286a80051809 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -121,6 +121,7 @@ package android.hardware.usb { 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 @@ -392,7 +393,6 @@ package android.os { method public static void traceBegin(long, @NonNull String); method public static void traceCounter(long, @NonNull String, int); method public static void traceEnd(long); - field public static final long TRACE_TAG_AIDL = 16777216L; // 0x1000000L field public static final long TRACE_TAG_NETWORK = 2097152L; // 0x200000L } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 057c1ada8f00..66423c8ede34 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2964,6 +2964,7 @@ package android.companion.virtual { 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 @@ package android.companion.virtual { 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 @@ package android.companion.virtual { 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.audio { } +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 { @@ -7474,6 +7521,7 @@ package android.media.tv.tuner.filter { field public static final int VIDEO_STREAM_TYPE_VC1 = 7; // 0x7 field public static final int VIDEO_STREAM_TYPE_VP8 = 8; // 0x8 field public static final int VIDEO_STREAM_TYPE_VP9 = 9; // 0x9 + field public static final int VIDEO_STREAM_TYPE_VVC = 13; // 0xd } public static class AvSettings.Builder { @@ -7666,6 +7714,7 @@ package android.media.tv.tuner.filter { field public static final int INDEX_TYPE_SC = 1; // 0x1 field public static final int INDEX_TYPE_SC_AVC = 3; // 0x3 field public static final int INDEX_TYPE_SC_HEVC = 2; // 0x2 + field public static final int INDEX_TYPE_SC_VVC = 4; // 0x4 field public static final int MPT_INDEX_AUDIO = 262144; // 0x40000 field public static final int MPT_INDEX_MPT = 65536; // 0x10000 field public static final int MPT_INDEX_TIMESTAMP_TARGET_AUDIO = 1048576; // 0x100000 @@ -7688,6 +7737,13 @@ package android.media.tv.tuner.filter { field public static final int SC_INDEX_SEQUENCE = 8; // 0x8 field public static final int SC_INDEX_SI_SLICE = 128; // 0x80 field public static final int SC_INDEX_SP_SLICE = 256; // 0x100 + field public static final int SC_VVC_INDEX_AUD = 64; // 0x40 + field public static final int SC_VVC_INDEX_SLICE_CRA = 4; // 0x4 + field public static final int SC_VVC_INDEX_SLICE_GDR = 8; // 0x8 + field public static final int SC_VVC_INDEX_SLICE_IDR_N_LP = 2; // 0x2 + field public static final int SC_VVC_INDEX_SLICE_IDR_W_RADL = 1; // 0x1 + field public static final int SC_VVC_INDEX_SPS = 32; // 0x20 + field public static final int SC_VVC_INDEX_VPS = 16; // 0x10 field public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG = 4096; // 0x1000 field public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED = 8; // 0x8 field public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED = 4; // 0x4 @@ -9965,6 +10021,10 @@ package android.os { field public static final int STATUS_WAITING_REBOOT = 5; // 0x5 } + public final class Trace { + field public static final long TRACE_TAG_AIDL = 16777216L; // 0x1000000L + } + public class UpdateEngine { ctor public UpdateEngine(); method @NonNull @WorkerThread public android.os.UpdateEngine.AllocateSpaceResult allocateSpace(@NonNull String, @NonNull String[]); @@ -10049,6 +10109,8 @@ package android.os { 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 +10124,7 @@ package android.os { 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 +10142,7 @@ package android.os { 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; @@ -12226,6 +12290,7 @@ package android.service.voice { method @Nullable public android.media.AudioTimestamp getTimestamp(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordAudioStream> CREATOR; + field public static final String KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES = "android.service.voice.key.AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES"; } public static final class HotwordAudioStream.Builder { @@ -12825,14 +12890,14 @@ package android.telephony { 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 +12977,28 @@ package android.telephony { 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 +13749,8 @@ package android.telephony { } 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 +14851,7 @@ package android.telephony.ims { 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 @@ -15827,6 +15916,7 @@ package android.telephony.ims.stub { public class ImsSmsImplBase { ctor public ImsSmsImplBase(); method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int); + method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int, @NonNull byte[]); method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int); method public String getSmsFormat(); method public void onReady(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index fa7317849b31..7d85fafe9e9c 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1039,6 +1039,7 @@ package android.graphics { 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; } @@ -1908,6 +1909,7 @@ package android.os { } 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); @@ -1922,6 +1924,7 @@ package android.os { } 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(); @@ -2885,6 +2888,10 @@ package android.view { 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); } @@ -3267,6 +3274,7 @@ package android.widget { public class Spinner extends android.widget.AbsSpinner implements android.content.DialogInterface.OnClickListener { method public boolean isPopupShowing(); + method public void onClick(int); } @android.widget.RemoteViews.RemoteView public class TextClock extends android.widget.TextView { diff --git a/core/java/Android.bp b/core/java/Android.bp index af0fa392e337..a4a12d743442 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -417,6 +417,16 @@ filegroup { ], } +// This file group is used by service fuzzer +filegroup { + name: "framework-core-sources-for-fuzzers", + srcs: [ + "android/os/IInterface.java", + "android/os/Binder.java", + "android/os/IBinder.java", + ], +} + aidl_interface { name: "android.os.statsbootstrap_aidl", unstable: true, diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index f25e639a9c80..9d5c01af1a02 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -767,11 +767,11 @@ public class BroadcastOptions extends ComponentOptions { */ @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 @@ public class BroadcastOptions extends ComponentOptions { * 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 9bf8550df6d0..63fdc2e1b686 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -48,6 +48,7 @@ import android.compat.annotation.Disabled; 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 @@ public abstract class ForegroundServiceTypePolicy { 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 f92194d3ffac..2f51b174113c 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -213,7 +213,7 @@ public final class GameManager { @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/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl index aea097d069dc..3d6ab6fd9478 100644 --- a/core/java/android/app/IGameManagerService.aidl +++ b/core/java/android/app/IGameManagerService.aidl @@ -29,7 +29,7 @@ interface IGameManagerService { @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 6d7a161d1687..3a7d483c69c3 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -1128,12 +1128,10 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac /** * 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/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 5d87012ec7e7..762ac23b6efe 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1617,6 +1617,7 @@ public final class SystemServiceRegistry { case Context.INCREMENTAL_SERVICE: case Context.ETHERNET_SERVICE: case Context.CONTEXTHUB_SERVICE: + case Context.VIRTUALIZATION_SERVICE: return null; } Slog.wtf(TAG, "Manager wrapper not available: " + name); diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 295d69d4b27d..0837d85bb91c 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -19,6 +19,9 @@ package android.companion.virtual; 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 @@ interface IVirtualDevice { 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 78e588687604..01b42bfa661c 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -22,12 +22,15 @@ import android.annotation.IntRange; 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.RetentionPolicy; 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; @@ -89,6 +93,26 @@ public final class VirtualDeviceManager { */ 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( @@ -251,7 +275,10 @@ public final class VirtualDeviceManager { }; @Nullable private VirtualAudioDevice mVirtualAudioDevice; + @NonNull + private List<VirtualSensor> mVirtualSensors = new ArrayList<>(); + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) private VirtualDevice( IVirtualDeviceManager service, Context context, @@ -265,6 +292,10 @@ public final class VirtualDeviceManager { associationId, params, mActivityListenerBinder); + final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs(); + for (int i = 0; i < virtualSensorConfigs.size(); ++i) { + mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i))); + } } /** @@ -279,6 +310,23 @@ public final class VirtualDeviceManager { } /** + * 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 @@ -438,6 +486,7 @@ public final class VirtualDeviceManager { @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(); @@ -623,6 +672,28 @@ public final class VirtualDeviceManager { } /** + * 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 f8c2e34a0e2a..1cbe910d131a 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -23,20 +23,22 @@ import android.annotation.NonNull; 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 @@ public final class VirtualDeviceParams implements Parcelable { @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 @@ public final class VirtualDeviceParams implements Parcelable { @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 @@ public final class VirtualDeviceParams implements Parcelable { mDefaultActivityPolicy = parcel.readInt(); mName = parcel.readString8(); mDevicePolicies = parcel.readSparseIntArray(); + mVirtualSensorConfigs = new ArrayList<>(); + parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR); } /** @@ -310,12 +313,21 @@ public final class VirtualDeviceParams implements Parcelable { * 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 @@ public final class VirtualDeviceParams implements Parcelable { dest.writeInt(mDefaultActivityPolicy); dest.writeString8(mName); dest.writeSparseIntArray(mDevicePolicies); + dest.writeTypedList(mVirtualSensorConfigs); } @Override @@ -428,6 +441,7 @@ public final class VirtualDeviceParams implements Parcelable { 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 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setUsersWithMatchingAccounts( @NonNull Set<UserHandle> usersWithMatchingAccounts) { - Preconditions.checkNotNull(usersWithMatchingAccounts); - mUsersWithMatchingAccounts = usersWithMatchingAccounts; + mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts); return this; } @@ -491,7 +504,6 @@ public final class VirtualDeviceParams implements Parcelable { @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 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED; mDefaultNavigationPolicyConfigured = true; - mAllowedCrossTaskNavigations = allowedCrossTaskNavigations; + mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations); return this; } @@ -523,7 +535,6 @@ public final class VirtualDeviceParams implements Parcelable { @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 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED; mDefaultNavigationPolicyConfigured = true; - mBlockedCrossTaskNavigations = blockedCrossTaskNavigations; + mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations); return this; } @@ -551,7 +562,6 @@ public final class VirtualDeviceParams implements Parcelable { */ @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 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED; mDefaultActivityPolicyConfigured = true; - mAllowedActivities = allowedActivities; + mAllowedActivities = Objects.requireNonNull(allowedActivities); return this; } @@ -578,7 +588,6 @@ public final class VirtualDeviceParams implements Parcelable { */ @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 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED; mDefaultActivityPolicyConfigured = true; - mBlockedActivities = blockedActivities; + mBlockedActivities = Objects.requireNonNull(blockedActivities); return this; } @@ -606,25 +615,64 @@ public final class VirtualDeviceParams implements Parcelable { } /** - * 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 + * <p>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. * * @param policyType the type of policy, i.e. which behavior to specify a policy for. * @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 @@ public final class VirtualDeviceParams implements Parcelable { 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 000000000000..b99cc7eb67a5 --- /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 000000000000..a184481bdf99 --- /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 000000000000..48b463ab5d50 --- /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 000000000000..7982fa59daf3 --- /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 000000000000..99439465a48f --- /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 000000000000..8f8860ed5e6e --- /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 cc7977a267a5..99fc5a3ae543 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 @@ public class FabricatedOverlay { } /** + * 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,90 +133,110 @@ public class FabricatedOverlay { } /** - * 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) { - ensureValidResourceName(resourceName); - - final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry(); - entry.resourceName = resourceName; - entry.dataType = dataType; - entry.data = value; - mEntries.add(entry); - return this; + @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 + * 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) + * [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 */ - public Builder setResourceValue(@NonNull String resourceName, int dataType, int value, - String configuration) { + @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 string representing the new value - * + * @return the builder itself * @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; + @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 + * 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) + * [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) { + @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.stringData = 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; @@ -204,23 +246,32 @@ public class FabricatedOverlay { * 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 812f6b0fc34b..ed1f6a2b1457 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.Compatibility; 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 @@ public class OverlayManager { 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 class OverlayManager { public OverlayManager(Context context, IOverlayManager service) { mContext = context; mService = service; + mOverlayManagerImpl = new OverlayManagerImpl(context); } /** @hide */ @@ -301,6 +308,17 @@ public class OverlayManager { * @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 @@ public class OverlayManager { 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 868dab298108..42b3ef3c370d 100644 --- a/core/java/android/content/om/OverlayManagerTransaction.java +++ b/core/java/android/content/om/OverlayManagerTransaction.java @@ -20,19 +20,22 @@ import static com.android.internal.util.Preconditions.checkNotNull; 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 @@ public class OverlayManagerTransaction // 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 @@ public class OverlayManagerTransaction final Bundle extras = source.readBundle(null); mRequests.add(new Request(request, overlay, userId, extras)); } + mOverlayManager = null; } @Override @@ -156,6 +163,20 @@ public class OverlayManagerTransaction */ 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 @@ public class OverlayManagerTransaction * * @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 @@ public class OverlayManagerTransaction * * @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 @@ public class OverlayManagerTransaction * @see OverlayManager#commit * @return a new transaction */ + @NonNull public OverlayManagerTransaction build() { - return new OverlayManagerTransaction(mRequests); + return new OverlayManagerTransaction(mRequests, mOverlayManager); } } @@ -269,4 +297,23 @@ public class OverlayManagerTransaction 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/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 9e6cf62f2397..7ea6733fa2ff 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -366,24 +366,48 @@ public class ServiceInfo extends ComponentInfo 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> - * </ul> + * 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. * - * @see Service#onTimeout + * <p>The system will <em>not</em> automatically stop it. * - * @hide + * <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 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 7a5ac8ede4a4..143c00dd4d81 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -26,6 +26,8 @@ import android.text.TextUtils; 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 @@ public final class ApkAssets { 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/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java index c7fdb16682e3..457225d26610 100644 --- a/core/java/android/content/res/FontScaleConverter.java +++ b/core/java/android/content/res/FontScaleConverter.java @@ -36,11 +36,6 @@ import java.util.Arrays; * @hide */ public class FontScaleConverter { - /** - * How close the given SP should be to a canonical SP in the array before they are considered - * the same for lookup purposes. - */ - private static final float THRESHOLD_FOR_MATCHING_SP = 0.02f; @VisibleForTesting final float[] mFromSpValues; @@ -78,10 +73,11 @@ public class FontScaleConverter { public float convertSpToDp(float sp) { final float spPositive = Math.abs(sp); // TODO(b/247861374): find a match at a higher index? - final int spRounded = Math.round(spPositive); final float sign = Math.signum(sp); - final int index = Arrays.binarySearch(mFromSpValues, spRounded); - if (index >= 0 && Math.abs(spRounded - spPositive) < THRESHOLD_FOR_MATCHING_SP) { + // We search for exact matches only, even if it's just a little off. The interpolation will + // handle any non-exact matches. + final int index = Arrays.binarySearch(mFromSpValues, spPositive); + if (index >= 0) { // exact match, return the matching dp return sign * mToDpValues[index]; } else { diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java index 463dcaced723..a5a1fa689f9b 100644 --- a/core/java/android/content/res/loader/ResourcesProvider.java +++ b/core/java/android/content/res/loader/ResourcesProvider.java @@ -18,7 +18,10 @@ package android.content.res.loader; 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 android.util.Log; 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 @@ public class ResourcesProvider implements AutoCloseable, Closeable { } /** + * 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 22ef23019dcd..45890392bed7 100644 --- a/core/java/android/credentials/CreateCredentialRequest.java +++ b/core/java/android/credentials/CreateCredentialRequest.java @@ -39,10 +39,17 @@ public final class CreateCredentialRequest implements Parcelable { 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 @@ public final class CreateCredentialRequest implements Parcelable { } /** - * 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 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 getData() { - return mData; + 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 @@ public final class CreateCredentialRequest implements Parcelable { @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 final class CreateCredentialRequest implements Parcelable { public String toString() { return "CreateCredentialRequest {" + "type=" + mType - + ", data=" + mData + + ", credentialData=" + mCredentialData + + ", candidateQueryData=" + mCandidateQueryData + ", requireSystemProvider=" + mRequireSystemProvider + "}"; } @@ -100,44 +130,37 @@ public final class CreateCredentialRequest implements Parcelable { * 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 a0d3c0b2e6ed..ed93daef20d3 100644 --- a/core/java/android/credentials/GetCredentialOption.java +++ b/core/java/android/credentials/GetCredentialOption.java @@ -67,8 +67,6 @@ public final class GetCredentialOption implements Parcelable { /** * 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 @@ public final class GetCredentialOption implements Parcelable { * * @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 1c4898a4c8d0..18118f5bfe29 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.IOException; 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 @@ public class SystemSensorManager extends SensorManager { 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 @@ public class SystemSensorManager extends SensorManager { 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 @@ public class SystemSensorManager extends SensorManager { 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 @@ public class SystemSensorManager extends SensorManager { 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 @@ public class SystemSensorManager extends SensorManager { 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 @@ public class SystemSensorManager extends SensorManager { 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 @@ public class SystemSensorManager extends SensorManager { } } + 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 441fd88f15ee..f7675e835eb2 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -588,18 +588,20 @@ public final class DisplayManager { * @see #DISPLAY_CATEGORY_PRESENTATION */ public Display[] getDisplays(String category) { - final int[] displayIds = mGlobal.getDisplayIds(); + boolean includeDisabled = (category != null + && category.equals(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)); + final int[] displayIds = mGlobal.getDisplayIds(includeDisabled); synchronized (mLock) { try { - if (category == null - || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) { - addAllDisplaysLocked(mTempDisplays, displayIds); - } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) { + if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) { addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL); addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL); + } else if (category == null + || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) { + addAllDisplaysLocked(mTempDisplays, displayIds); } return mTempDisplays.toArray(new Display[mTempDisplays.size()]); } finally { diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index cc397d57d838..f038c66d3b51 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -212,6 +212,16 @@ public final class DisplayManagerGlobal { */ @UnsupportedAppUsage public int[] getDisplayIds() { + return getDisplayIds(/* includeDisabled= */ false); + } + + /** + * Gets all currently valid logical display ids. + * + * @param includeDisabled True if the returned list of displays includes disabled displays. + * @return An array containing all display ids. + */ + public int[] getDisplayIds(boolean includeDisabled) { try { synchronized (mLock) { if (USE_CACHE) { @@ -220,7 +230,7 @@ public final class DisplayManagerGlobal { } } - int[] displayIds = mDm.getDisplayIds(); + int[] displayIds = mDm.getDisplayIds(includeDisabled); if (USE_CACHE) { mDisplayIdCache = displayIds; } diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 6b5594b1a3dd..28bb35f7d9cf 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -37,7 +37,7 @@ import android.view.Surface; interface IDisplayManager { @UnsupportedAppUsage DisplayInfo getDisplayInfo(int displayId); - int[] getDisplayIds(); + int[] getDisplayIds(boolean includeDisabled); boolean isUidPresentOnDisplay(int uid, int displayId); diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index fb5ac5a4efcb..6f63dbfc3ba9 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -956,7 +956,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { if (mService == null) { - Slog.w(TAG, "onFingerDown: no fingerprint service"); + Slog.w(TAG, "onPointerDown: no fingerprint service"); return; } @@ -979,7 +979,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPointerUp(long requestId, int sensorId) { if (mService == null) { - Slog.w(TAG, "onFingerDown: no fingerprint service"); + Slog.w(TAG, "onPointerUp: no fingerprint service"); return; } @@ -993,6 +993,58 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** + * TODO(b/218388821): The parameter list should be replaced with PointerContext. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onPointerDown( + long requestId, + int sensorId, + int pointerId, + float x, + float y, + float minor, + float major, + float orientation, + long time, + long gestureStart, + boolean isAod) { + if (mService == null) { + Slog.w(TAG, "onPointerDown: no fingerprint service"); + return; + } + + // TODO(b/218388821): Propagate all the parameters to FingerprintService. + Slog.e(TAG, "onPointerDown: not implemented!"); + } + + /** + * TODO(b/218388821): The parameter list should be replaced with PointerContext. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onPointerUp( + long requestId, + int sensorId, + int pointerId, + float x, + float y, + float minor, + float major, + float orientation, + long time, + long gestureStart, + boolean isAod) { + if (mService == null) { + Slog.w(TAG, "onPointerUp: no fingerprint service"); + return; + } + + // TODO(b/218388821): Propagate all the parameters to FingerprintService. + Slog.e(TAG, "onPointerUp: not implemented!"); + } + + /** * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 51236fe3b2c0..248b5d0398f6 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -122,10 +122,10 @@ interface IUsbManager 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 342c33665b02..7a8117c1b684 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; 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.List; 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * <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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { /** * 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 @@ public class UsbManager { 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 @@ public class UsbManager { GADGET_HAL_V1_0, GADGET_HAL_V1_1, GADGET_HAL_V1_2, + GADGET_HAL_V2_0, }) public @interface UsbGadgetHalVersion {} @@ -705,7 +728,7 @@ public class UsbManager { private final IUsbManager mService; /** - * {@hide} + * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public UsbManager(Context context, IUsbManager service) { @@ -816,7 +839,7 @@ public class UsbManager { * {@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 @@ public class UsbManager { * 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 @@ public class UsbManager { * @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 @@ public class UsbManager { * @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 @@ public class UsbManager { * @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 @@ public class UsbManager { * @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 @@ public class UsbManager { * @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 @@ public class UsbManager { * @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 @@ public class UsbManager { * @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 @@ public class UsbManager { * @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 @@ public class UsbManager { * * @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 @@ public class UsbManager { * * @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 @@ public class UsbManager { * * @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 @@ public class UsbManager { * @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 @@ public class UsbManager { * * @return Whether the mask is settable. * - * {@hide} + * @hide */ public static boolean areSettableFunctions(long functions) { return functions == FUNCTION_NONE @@ -1540,7 +1567,7 @@ public class UsbManager { * * @return String representation of given mask * - * {@hide} + * @hide */ public static String usbFunctionsToString(long functions) { StringJoiner joiner = new StringJoiner(","); @@ -1576,7 +1603,7 @@ public class UsbManager { * * @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 @@ public class UsbManager { * * @return a value of USB bandwidth * - * {@hide} + * @hide */ public static int usbSpeedToBandwidth(int speed) { switch (speed) { @@ -1632,12 +1659,14 @@ public class UsbManager { * * @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 fb66cb9cfcc6..872414a7c147 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -70,11 +70,14 @@ import android.app.compat.CompatChanges; 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.text.method.MovementMethod; 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.InputMethodPrivilegedOperationsRegistry; 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 @@ public class InputMethodService extends AbstractInputMethodService { @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 @@ public class InputMethodService extends AbstractInputMethodService { 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 @@ public class InputMethodService extends AbstractInputMethodService { 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 a887f2a6ef29..d31540a65f2f 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -58,6 +58,8 @@ interface IUserManager { 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 9ea42780981d..394927e17167 100644 --- a/core/java/android/os/ServiceManager.java +++ b/core/java/android/os/ServiceManager.java @@ -252,10 +252,12 @@ public final class ServiceManager { } /** - * Returns the list of declared instances for an interface. + * Returns an array of all declared instances for a particular interface. + * + * 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. * - * @return true if the service is declared somewhere (eg. VINTF manifest) and - * waitForService should always be able to return the service. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index fb197f5be2a8..cdde18aed604 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -100,7 +100,7 @@ public final class Trace { /** @hide */ public static final long TRACE_TAG_VIBRATOR = 1L << 23; /** @hide */ - @SystemApi(client = MODULE_LIBRARIES) + @SystemApi public static final long TRACE_TAG_AIDL = 1L << 24; /** @hide */ public static final long TRACE_TAG_NNAPI = 1L << 25; @@ -241,9 +241,16 @@ public final class Trace { /** * 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 @@ public final class Trace { * 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 @@ public final class Trace { * 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 @@ public final class Trace { * {@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 1f21bfe6cd72..dd02e022c639 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2377,14 +2377,16 @@ public class UserManager { } /** - * 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 @@ public class UserManager { } /** + * 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 @@ public class UserManager { * </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 @@ public class UserManager { * 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 @@ public class UserManager { } /** + * 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 71bc4b36f4e0..3448a9eed80c 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -227,6 +227,31 @@ public abstract class VibrationEffect implements Parcelable { } /** + * 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 @@ public abstract class VibrationEffect implements Parcelable { 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 @@ public abstract class VibrationEffect implements Parcelable { 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/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java index 1c57700d38e1..6ae952c045cb 100644 --- a/core/java/android/service/voice/HotwordAudioStream.java +++ b/core/java/android/service/voice/HotwordAudioStream.java @@ -47,6 +47,21 @@ import java.util.Objects; public final class HotwordAudioStream implements Parcelable { /** + * Key for int value to be read from {@link #getMetadata()}. The value is read by the system and + * is the length (in bytes) of the byte buffers created to copy bytes in the + * {@link #getAudioStreamParcelFileDescriptor()} written by the {@link HotwordDetectionService}. + * The buffer length should be chosen such that no additional latency is introduced. Typically, + * this should be <em>at least</em> the size of byte chunks written by the + * {@link HotwordDetectionService}. + * + * <p>If no value specified in the metadata for the buffer length, or if the value is less than + * 1, or if it is greater than 65,536, or if it is not an int, the default value of 2,560 will + * be used.</p> + */ + public static final String KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES = + "android.service.voice.key.AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES"; + + /** * The {@link AudioFormat} of the audio stream. */ @NonNull @@ -414,10 +429,10 @@ public final class HotwordAudioStream implements Parcelable { } @DataClass.Generated( - time = 1669184301563L, + time = 1669916341034L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/HotwordAudioStream.java", - inputSignatures = "private final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static android.media.AudioTimestamp defaultTimestamp()\nprivate static android.os.PersistableBundle defaultMetadata()\npublic android.service.voice.HotwordAudioStream.Builder buildUpon()\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)") + inputSignatures = "public static final java.lang.String KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES\nprivate final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull android.os.ParcelFileDescriptor mAudioStreamParcelFileDescriptor\nprivate final @android.annotation.Nullable android.media.AudioTimestamp mTimestamp\nprivate final @android.annotation.NonNull android.os.PersistableBundle mMetadata\nprivate static android.media.AudioTimestamp defaultTimestamp()\nprivate static android.os.PersistableBundle defaultMetadata()\npublic android.service.voice.HotwordAudioStream.Builder buildUpon()\nclass HotwordAudioStream extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index aebd91a0be2f..84a233ffd2ad 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -2183,6 +2183,17 @@ public abstract class WallpaperService extends Service { } } + /** + * 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 @@ public abstract class WallpaperService extends Service { 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 e5c9adba46a9..dded76c9a97a 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -26,7 +26,6 @@ import android.os.Build; 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 @@ public class PhoneStateListener { */ @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 @@ public class PhoneStateListener { () -> 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 e8960b8e35cd..257f3b7dbf40 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -27,6 +27,7 @@ import android.os.Binder; 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 @@ import java.util.concurrent.Executor; * 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 @@ public class TelephonyCallback { @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 @@ public class TelephonyCallback { * {@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 @@ public class TelephonyCallback { () -> 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 a3696e398668..0a1538de9f5d 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -32,13 +32,13 @@ import android.telephony.Annotation.CallState; 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 @@ public class TelephonyRegistryManager { * @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/text/Highlights.java b/core/java/android/text/Highlights.java new file mode 100644 index 000000000000..356dfcaeb93b --- /dev/null +++ b/core/java/android/text/Highlights.java @@ -0,0 +1,148 @@ +/* + * 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.text; + +import android.graphics.Paint; +import android.util.Pair; + +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * A class that represents of the highlight of the text. + */ +public class Highlights { + private final List<Pair<Paint, int[]>> mHighlights; + + private Highlights(List<Pair<Paint, int[]>> highlights) { + mHighlights = highlights; + } + + /** + * Returns a number of highlight. + * + * @return a number of highlight. + * + * @see Builder#addRange(Paint, int, int) + * @see Builder#addRanges(Paint, int...) + */ + public @IntRange(from = 0) int getSize() { + return mHighlights.size(); + } + + /** + * Returns a paint used for the i-th highlight. + * + * @param index an index of the highlight. Must be between 0 and {@link #getSize()} + * @return a paint object + * + * @see Builder#addRange(Paint, int, int) + * @see Builder#addRanges(Paint, int...) + */ + public @NonNull Paint getPaint(@IntRange(from = 0) int index) { + return mHighlights.get(index).first; + } + + /** + * Returns ranges of the i-th highlight. + * + * Ranges are represented of flattened inclusive start and exclusive end integers array. The + * inclusive start offset of the {@code i}-th range is stored in {@code 2 * i}-th of the array. + * The exclusive end offset of the {@code i}-th range is stored in {@code 2* i + 1}-th of the + * array. For example, the two ranges: (1, 2) and (3, 4) are flattened into single int array + * [1, 2, 3, 4]. + * + * @param index an index of the highlight. Must be between 0 and {@link #getSize()} + * @return a paint object + * + * @see Builder#addRange(Paint, int, int) + * @see Builder#addRanges(Paint, int...) + */ + public @NonNull int[] getRanges(int index) { + return mHighlights.get(index).second; + } + + /** + * A builder for the Highlights. + */ + public static final class Builder { + private final List<Pair<Paint, int[]>> mHighlights = new ArrayList<>(); + + /** + * Add single range highlight. + * + * @param paint a paint object used for drawing highlight path. + * @param start an inclusive offset of the text. + * @param end an exclusive offset of the text. + * @return this builder instance. + */ + public @NonNull Builder addRange(@NonNull Paint paint, @IntRange(from = 0) int start, + @IntRange(from = 0) int end) { + if (start > end) { + throw new IllegalArgumentException("start must not be larger than end: " + + start + ", " + end); + } + Objects.requireNonNull(paint); + + int[] range = new int[] {start, end}; + mHighlights.add(new Pair<>(paint, range)); + return this; + } + + /** + * Add multiple ranges highlight. + * + * @param paint a paint object used for drawing highlight path. + * @param ranges a flatten ranges. The {@code 2 * i}-th element is an inclusive start offset + * of the {@code i}-th character. The {@code 2 * i + 1}-th element is an + * exclusive end offset of the {@code i}-th character. + * @return this builder instance. + */ + public @NonNull Builder addRanges(@NonNull Paint paint, @NonNull int... ranges) { + if (ranges.length % 2 == 1) { + throw new IllegalArgumentException( + "Flatten ranges must have even numbered elements"); + } + for (int j = 0; j < ranges.length / 2; ++j) { + int start = ranges[j * 2]; + int end = ranges[j * 2 + 1]; + if (start > end) { + throw new IllegalArgumentException( + "Reverse range found in the flatten range: " + Arrays.toString( + ranges)); + } + } + Objects.requireNonNull(paint); + mHighlights.add(new Pair<>(paint, ranges)); + return this; + } + + /** + * Build a new Highlights instance. + * + * @return a new Highlights instance. + */ + public @NonNull Highlights build() { + return new Highlights(mHighlights); + } + } +} diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 54ec07edd3ee..64dc16de3e4d 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -44,6 +44,7 @@ import com.android.internal.util.GrowingArrayUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.List; /** * A base class that manages text layout in visual elements on @@ -347,9 +348,13 @@ public abstract class Layout { /** * Draw this Layout on the specified Canvas. + * + * This API draws background first, then draws text on top of it. + * + * @see #draw(Canvas, List, List, Path, Paint, int) */ public void draw(Canvas c) { - draw(c, null, null, 0); + draw(c, (Path) null, (Paint) null, 0); } /** @@ -357,23 +362,142 @@ public abstract class Layout { * between the background and the text. * * @param canvas the canvas - * @param highlight the path of the highlight or cursor; can be null - * @param highlightPaint the paint for the highlight + * @param selectionHighlight the path of the selection highlight or cursor; can be null + * @param selectionHighlightPaint the paint for the selection highlight * @param cursorOffsetVertical the amount to temporarily translate the * canvas while rendering the highlight + * + * @see #draw(Canvas, List, List, Path, Paint, int) */ - public void draw(Canvas canvas, Path highlight, Paint highlightPaint, + public void draw( + Canvas canvas, Path selectionHighlight, + Paint selectionHighlightPaint, int cursorOffsetVertical) { + draw(canvas, null, null, selectionHighlight, selectionHighlightPaint, cursorOffsetVertical); + } + + /** + * Draw this layout on the specified canvas. + * + * This API draws background first, then draws highlight paths on top of it, then draws + * selection or cursor, then finally draws text on top of it. + * + * @see #drawBackground(Canvas) + * @see #drawText(Canvas) + * + * @param canvas the canvas + * @param highlightPaths the path of the highlights. The highlightPaths and highlightPaints must + * have the same length and aligned in the same order. For example, the + * paint of the n-th of the highlightPaths should be stored at the n-th of + * highlightPaints. + * @param highlightPaints the paints for the highlights. The highlightPaths and highlightPaints + * must have the same length and aligned in the same order. For example, + * the paint of the n-th of the highlightPaths should be stored at the + * n-th of highlightPaints. + * @param selectionPath the selection or cursor path + * @param selectionPaint the paint for the selection or cursor. + * @param cursorOffsetVertical the amount to temporarily translate the canvas while rendering + * the highlight + */ + public void draw(@NonNull Canvas canvas, + @Nullable List<Path> highlightPaths, + @Nullable List<Paint> highlightPaints, + @Nullable Path selectionPath, + @Nullable Paint selectionPaint, int cursorOffsetVertical) { final long lineRange = getLineRangeForDraw(canvas); int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); if (lastLine < 0) return; - drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical, - firstLine, lastLine); + drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint, + cursorOffsetVertical, firstLine, lastLine); + drawText(canvas, firstLine, lastLine); + } + + /** + * Draw text part of this layout. + * + * Different from {@link #draw(Canvas, List, List, Path, Paint, int)} API, this API only draws + * text part, not drawing highlights, selections, or backgrounds. + * + * @see #draw(Canvas, List, List, Path, Paint, int) + * @see #drawBackground(Canvas) + * + * @param canvas the canvas + */ + public void drawText(@NonNull Canvas canvas) { + final long lineRange = getLineRangeForDraw(canvas); + int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); + int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); + if (lastLine < 0) return; drawText(canvas, firstLine, lastLine); } + /** + * Draw background of this layout. + * + * Different from {@link #draw(Canvas, List, List, Path, Paint, int)} API, this API only draws + * background, not drawing text, highlights or selections. The background here is drawn by + * {@link LineBackgroundSpan} attached to the text. + * + * @see #draw(Canvas, List, List, Path, Paint, int) + * @see #drawText(Canvas) + * + * @param canvas the canvas + */ + public void drawBackground(@NonNull Canvas canvas) { + final long lineRange = getLineRangeForDraw(canvas); + int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); + int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); + if (lastLine < 0) return; + drawBackground(canvas, firstLine, lastLine); + } + + /** + * @hide public for Editor.java + */ + public void drawWithoutText( + @NonNull Canvas canvas, + @Nullable List<Path> highlightPaths, + @Nullable List<Paint> highlightPaints, + @Nullable Path selectionPath, + @Nullable Paint selectionPaint, + int cursorOffsetVertical, + int firstLine, + int lastLine) { + drawBackground(canvas, firstLine, lastLine); + if (highlightPaths == null && highlightPaints == null) { + return; + } + if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical); + try { + if (highlightPaths != null) { + if (highlightPaints == null) { + throw new IllegalArgumentException( + "if highlight is specified, highlightPaint must be specified."); + } + if (highlightPaints.size() != highlightPaths.size()) { + throw new IllegalArgumentException( + "The highlight path size is different from the size of highlight" + + " paints"); + } + for (int i = 0; i < highlightPaths.size(); ++i) { + final Path highlight = highlightPaths.get(i); + final Paint highlightPaint = highlightPaints.get(i); + if (highlight != null) { + canvas.drawPath(highlight, highlightPaint); + } + } + } + + if (selectionPath != null) { + canvas.drawPath(selectionPath, selectionPaint); + } + } finally { + if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical); + } + } + private boolean isJustificationRequired(int lineNum) { if (mJustificationMode == JUSTIFICATION_MODE_NONE) return false; final int lineEnd = getLineEnd(lineNum); @@ -635,8 +759,9 @@ public abstract class Layout { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint, - int cursorOffsetVertical, int firstLine, int lastLine) { + public void drawBackground( + @NonNull Canvas canvas, + int firstLine, int lastLine) { // First, draw LineBackgroundSpans. // LineBackgroundSpans know nothing about the alignment, margins, or // direction of the layout or line. XXX: Should they? @@ -700,14 +825,6 @@ public abstract class Layout { } mLineBackgroundSpans.recycle(); } - - // There can be a highlight even without spans if we are drawing - // a non-spanned transformation of a spanned editing buffer. - if (highlight != null) { - if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical); - canvas.drawPath(highlight, highlightPaint); - if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical); - } } /** diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 608cbda28370..4277d01c091a 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -140,6 +140,13 @@ public class FeatureFlagUtils { 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 @@ public class FeatureFlagUtils { 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 7b28b8a607de..bc0e35da37c3 100644 --- a/core/java/android/util/IntArray.java +++ b/core/java/android/util/IntArray.java @@ -234,4 +234,23 @@ public class IntArray implements Cloneable { 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/util/RotationUtils.java b/core/java/android/util/RotationUtils.java index c54d9b604ba2..3e7c67e72031 100644 --- a/core/java/android/util/RotationUtils.java +++ b/core/java/android/util/RotationUtils.java @@ -25,6 +25,7 @@ import android.annotation.Dimension; import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.view.Surface.Rotation; import android.view.SurfaceControl; @@ -193,6 +194,29 @@ public class RotationUtils { } /** + * Same as {@link #rotatePoint}, but for float coordinates. + */ + public static void rotatePointF(PointF inOutPoint, @Rotation int rotation, + float parentW, float parentH) { + float origX = inOutPoint.x; + switch (rotation) { + case ROTATION_0: + return; + case ROTATION_90: + inOutPoint.x = inOutPoint.y; + inOutPoint.y = parentW - origX; + return; + case ROTATION_180: + inOutPoint.x = parentW - inOutPoint.x; + inOutPoint.y = parentH - inOutPoint.y; + return; + case ROTATION_270: + inOutPoint.x = parentH - inOutPoint.y; + inOutPoint.y = origX; + } + } + + /** * Sets a matrix such that given a rotation, it transforms physical display * coordinates to that rotation's logical coordinates. * diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index fbca37359779..274585892b61 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1009,6 +1009,28 @@ public final class Display { } /** + * 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 0ba3072c0813..138017c5f0ec 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -323,6 +323,9 @@ public final class DisplayInfo implements Parcelable { @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 @@ public final class DisplayInfo implements Parcelable { && 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 @@ public final class DisplayInfo implements Parcelable { brightnessDefault = other.brightnessDefault; roundedCorners = other.roundedCorners; installOrientation = other.installOrientation; + displayShape = other.displayShape; } public void readFromParcel(Parcel source) { @@ -506,6 +511,7 @@ public final class DisplayInfo implements Parcelable { userDisabledHdrTypes[i] = source.readInt(); } installOrientation = source.readInt(); + displayShape = source.readTypedObject(DisplayShape.CREATOR); } @Override @@ -562,6 +568,7 @@ public final class DisplayInfo implements Parcelable { 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 000000000000..af8b4176a12d --- /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 000000000000..43bd773159f1 --- /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 e2bc5668d058..0743ccb37f51 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -738,9 +738,8 @@ interface IWindowManager * 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 a8cc9b62d30a..c56d61808665 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -197,6 +197,9 @@ public class InsetsState implements Parcelable { private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds(); + /** The display shape */ + private DisplayShape mDisplayShape = DisplayShape.NONE; + public InsetsState() { } @@ -271,6 +274,7 @@ public class InsetsState implements Parcelable { alwaysConsumeSystemBars, calculateRelativeCutout(frame), calculateRelativeRoundedCorners(frame), calculateRelativePrivacyIndicatorBounds(frame), + calculateRelativeDisplayShape(frame), compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); } @@ -335,6 +339,16 @@ public class InsetsState implements Parcelable { 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 @@ public class InsetsState implements Parcelable { 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 @@ public class InsetsState implements Parcelable { 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 @@ public class InsetsState implements Parcelable { 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 @@ public class InsetsState implements Parcelable { 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 @@ public class InsetsState implements Parcelable { 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 @@ public class InsetsState implements Parcelable { || !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 @@ public class InsetsState implements Parcelable { @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 @@ public class InsetsState implements Parcelable { 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 @@ public class InsetsState implements Parcelable { mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR); mRoundedCornerFrame.readFromParcel(in); mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR); + mDisplayShape = in.readTypedObject(DisplayShape.CREATOR); } @Override @@ -998,6 +1027,7 @@ public class InsetsState implements Parcelable { + ", 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 57b2d39a3ba8..33ea92de68b4 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -947,108 +947,112 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall + " 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; - } + try { + 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 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 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 realSizeChanged = performSurfaceTransaction(viewRoot, translator, - creating, sizeChanged, hintChanged, relativeZChanged, - surfaceUpdateTransaction); + 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); + } - try { - SurfaceHolder.Callback[] callbacks = null; + final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator, + creating, sizeChanged, hintChanged, relativeZChanged, + surfaceUpdateTransaction); - final boolean surfaceChanged = creating; - if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) { - mSurfaceCreated = false; - notifySurfaceDestroyed(); - } + try { + SurfaceHolder.Callback[] 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); - } + 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 (redrawNeeded) { - if (DEBUG) { - Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded"); - } - if (callbacks == null) { - callbacks = getSurfaceCallbacks(); + 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 (shouldSyncBuffer) { - handleSyncBufferCallback(callbacks, syncBufferTransactionCallback); - } else { - handleSyncNoBuffer(callbacks); + if (redrawNeeded) { + if (DEBUG) { + Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded"); + } + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } + + 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 03b25c25ab99..8de15c1d0ce6 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -42,7 +42,6 @@ import android.compat.annotation.UnsupportedAppUsage; 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 @@ public final class WindowInsets { @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 final class WindowInsets { 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 @@ public final class WindowInsets { boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, RoundedCorners roundedCorners, PrivacyIndicatorBounds privacyIndicatorBounds, + DisplayShape displayShape, @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) { mSystemWindowInsetsConsumed = typeInsetsMap == null; mTypeInsetsMap = mSystemWindowInsetsConsumed @@ -177,6 +163,7 @@ public final class WindowInsets { mRoundedCorners = roundedCorners; mPrivacyIndicatorBounds = privacyIndicatorBounds; + mDisplayShape = displayShape; } /** @@ -191,6 +178,7 @@ public final class WindowInsets { src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src), src.mRoundedCorners, src.mPrivacyIndicatorBounds, + src.mDisplayShape, src.mCompatInsetsTypes, src.mCompatIgnoreVisibility); } @@ -244,15 +232,18 @@ public final class WindowInsets { @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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { } /** + * 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 @@ public final class WindowInsets { mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, - null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, + null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -602,7 +608,7 @@ public final class WindowInsets { // it. (mCompatInsetsTypes & displayCutout()) != 0 ? null : displayCutoutCopyConstructorArgument(this), - mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes, + mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -911,6 +917,8 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { mPrivacyIndicatorBounds == null ? null : mPrivacyIndicatorBounds.inset(left, top, right, bottom), + mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -1037,7 +1046,8 @@ public final class WindowInsets { && 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { mIsRound = insets.mIsRound; mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars; mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds; + mDisplayShape = insets.mDisplayShape; } /** @@ -1381,6 +1393,19 @@ public final class WindowInsets { 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 @@ public final class WindowInsets { 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 6dc90117bf55..5c4305cc1647 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -366,7 +366,7 @@ public final class WindowManagerImpl implements WindowManager { 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/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index 52658404548c..e6379cfb27cf 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -968,7 +968,8 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { final int position = getSelectedItemPosition(); if (position >= 0) { // we fire selection events here not in View - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + // posting the event should delay it long enough for UI changes to take effect. + post(() -> sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED)); } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 5740f86b3486..558d96089bb7 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -2054,7 +2054,10 @@ public class Editor { } } - void onDraw(Canvas canvas, Layout layout, Path highlight, Paint highlightPaint, + void onDraw(Canvas canvas, Layout layout, + List<Path> highlightPaths, + List<Paint> highlightPaints, + Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) { final int selectionStart = mTextView.getSelectionStart(); final int selectionEnd = mTextView.getSelectionEnd(); @@ -2078,37 +2081,40 @@ public class Editor { mCorrectionHighlighter.draw(canvas, cursorOffsetVertical); } - if (highlight != null && selectionStart == selectionEnd && mDrawableForCursor != null) { + if (selectionHighlight != null && selectionStart == selectionEnd + && mDrawableForCursor != null) { drawCursor(canvas, cursorOffsetVertical); // Rely on the drawable entirely, do not draw the cursor line. // Has to be done after the IMM related code above which relies on the highlight. - highlight = null; + selectionHighlight = null; } if (mSelectionActionModeHelper != null) { mSelectionActionModeHelper.onDraw(canvas); if (mSelectionActionModeHelper.isDrawingHighlight()) { - highlight = null; + selectionHighlight = null; } } if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) { - drawHardwareAccelerated(canvas, layout, highlight, highlightPaint, - cursorOffsetVertical); + drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints, + selectionHighlight, selectionHighlightPaint, cursorOffsetVertical); } else { - layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical); + layout.draw(canvas, highlightPaths, highlightPaints, selectionHighlight, + selectionHighlightPaint, cursorOffsetVertical); } } - private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight, - Paint highlightPaint, int cursorOffsetVertical) { + private void drawHardwareAccelerated(Canvas canvas, Layout layout, + List<Path> highlightPaths, List<Paint> highlightPaints, + Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) { final long lineRange = layout.getLineRangeForDraw(canvas); int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); if (lastLine < 0) return; - layout.drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical, - firstLine, lastLine); + layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight, + selectionHighlightPaint, cursorOffsetVertical, firstLine, lastLine); if (layout instanceof DynamicLayout) { if (mTextRenderNodes == null) { @@ -2154,8 +2160,9 @@ public class Editor { continue; } startIndexToFindAvailableRenderNode = drawHardwareAcceleratedInner(canvas, layout, - highlight, highlightPaint, cursorOffsetVertical, blockEndLines, - blockIndices, i, numberOfBlocks, startIndexToFindAvailableRenderNode); + selectionHighlight, selectionHighlightPaint, cursorOffsetVertical, + blockEndLines, blockIndices, i, numberOfBlocks, + startIndexToFindAvailableRenderNode); if (blockEndLines[i] >= lastLine) { lastIndex = Math.max(indexFirstChangedBlock, i + 1); break; @@ -2169,9 +2176,9 @@ public class Editor { || mTextRenderNodes[blockIndex] == null || mTextRenderNodes[blockIndex].needsToBeShifted) { startIndexToFindAvailableRenderNode = drawHardwareAcceleratedInner(canvas, - layout, highlight, highlightPaint, cursorOffsetVertical, - blockEndLines, blockIndices, block, numberOfBlocks, - startIndexToFindAvailableRenderNode); + layout, selectionHighlight, selectionHighlightPaint, + cursorOffsetVertical, blockEndLines, blockIndices, block, + numberOfBlocks, startIndexToFindAvailableRenderNode); } } } diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index ba6fa197f164..ad431efc0bd2 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -802,6 +802,21 @@ public class Spinner extends AbsSpinner implements OnClickListener { dialog.dismiss(); } + /** + * Sets selection and dismisses the spinner's popup if it can be dismissed. + * For ease of use in tests, where publicly obtaining the spinner's popup is difficult. + * + * @param which index of the item to be selected. + * @hide + */ + @TestApi + public void onClick(int which) { + setSelection(which); + if (mPopup != null && mPopup.isShowing()) { + mPopup.dismiss(); + } + } + @Override public CharSequence getAccessibilityClassName() { return Spinner.class.getName(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index bf1a2bd51d91..475a8491de4d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -102,6 +102,7 @@ import android.text.Editable; import android.text.GetChars; import android.text.GraphemeClusterSegmentFinder; import android.text.GraphicsOperations; +import android.text.Highlights; import android.text.InputFilter; import android.text.InputType; import android.text.Layout; @@ -238,6 +239,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -926,6 +928,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @UnsupportedAppUsage private boolean mHighlightPathBogus = true; + private List<Path> mHighlightPaths; + private List<Paint> mHighlightPaints; + private Highlights mHighlights; + private final List<Path> mPathRecyclePool = new ArrayList<>(); + private boolean mHighlightPathsBogus = true; + // Although these fields are specific to editable text, they are not added to Editor because // they are defined by the TextView's style and are theme-dependent. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) @@ -6131,6 +6139,34 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Set Highlights + * + * @param highlights A highlight object. Call with null for reset. + * + * @see #getHighlights() + * @see Highlights + */ + public void setHighlights(@Nullable Highlights highlights) { + mHighlights = highlights; + mHighlightPathsBogus = true; + invalidate(); + } + + /** + * Returns highlights + * + * @return a highlight to be drawn. null if no highlight was set. + * + * @see #setHighlights(Highlights) + * @see Highlights + * + */ + @Nullable + public Highlights getHighlights() { + return mHighlights; + } + + /** * Convenience method to append the specified text to the TextView's * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} * if it was not already editable. @@ -8219,6 +8255,54 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return drawableState; } + private void maybeUpdateHighlightPaths() { + if (!mHighlightPathsBogus) { + return; + } + + if (mHighlightPaths != null) { + mPathRecyclePool.addAll(mHighlightPaths); + mHighlightPaths.clear(); + mHighlightPaints.clear(); + } else { + mHighlightPaths = new ArrayList<>(); + mHighlightPaints = new ArrayList<>(); + } + + if (mHighlights != null) { + for (int i = 0; i < mHighlights.getSize(); ++i) { + final int[] ranges = mHighlights.getRanges(i); + final Paint paint = mHighlights.getPaint(i); + + final Path path; + if (mPathRecyclePool.isEmpty()) { + path = new Path(); + } else { + path = mPathRecyclePool.get(mPathRecyclePool.size() - 1); + mPathRecyclePool.remove(mPathRecyclePool.size() - 1); + path.reset(); + } + + boolean atLeastOnePathAdded = false; + for (int j = 0; j < ranges.length / 2; ++j) { + final int start = ranges[2 * j]; + final int end = ranges[2 * j + 1]; + if (start < end) { + mLayout.getSelection(start, end, (left, top, right, bottom, layout) -> + path.addRect(left, top, right, bottom, Path.Direction.CW) + ); + atLeastOnePathAdded = true; + } + } + if (atLeastOnePathAdded) { + mHighlightPaths.add(path); + mHighlightPaints.add(paint); + } + } + } + mHighlightPathsBogus = false; + } + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private Path getUpdatedHighlightPath() { Path highlight = null; @@ -8418,17 +8502,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int cursorOffsetVertical = voffsetCursor - voffsetText; + maybeUpdateHighlightPaths(); Path highlight = getUpdatedHighlightPath(); if (mEditor != null) { - mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); + mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight, + mHighlightPaint, cursorOffsetVertical); } else { - layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); + layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, + cursorOffsetVertical); } if (mMarquee != null && mMarquee.shouldDrawGhost()) { final float dx = mMarquee.getGhostOffset(); canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); - layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); + layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint, + cursorOffsetVertical); } canvas.restore(); @@ -9750,6 +9838,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mOldMaxMode = mMaxMode; mHighlightPathBogus = true; + mHighlightPathsBogus = true; if (wantWidth < 0) { wantWidth = 0; diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index c2da638aca8d..a35e13e3ac3b 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -421,6 +421,53 @@ public final class TransitionInfo implements Parcelable { 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 @@ public final class TransitionInfo implements Parcelable { 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/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java index 828e5cf1fd4c..2d04bdbda846 100644 --- a/core/java/com/android/internal/content/om/OverlayConfig.java +++ b/core/java/com/android/internal/content/om/OverlayConfig.java @@ -140,7 +140,6 @@ public class OverlayConfig { ArrayMap<Integer, List<String>> activeApexesPerPartition = getActiveApexes(partitions); - boolean foundConfigFile = false; final Map<String, ParsedOverlayInfo> packageManagerOverlayInfos = packageProvider == null ? null : getOverlayPackageInfos(packageProvider); @@ -154,7 +153,6 @@ public class OverlayConfig { activeApexesPerPartition.getOrDefault(partition.type, Collections.emptyList())); if (partitionOverlays != null) { - foundConfigFile = true; overlays.addAll(partitionOverlays); continue; } @@ -191,12 +189,6 @@ public class OverlayConfig { overlays.addAll(partitionConfigs); } - if (!foundConfigFile) { - // If no overlay configuration files exist, disregard partition precedence and allow - // android:priority to reorder overlays across partition boundaries. - overlays.sort(sStaticOverlayComparator); - } - for (int i = 0, n = overlays.size(); i < n; i++) { // Add the configurations to a map so definitions of an overlay in an earlier // partition can be replaced by an overlay with the same package name in a later diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java index 6ceccd1b544b..260d1a2c88e5 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 @@ public class OverlayManagerImpl { * @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 1e3714eb342d..8cb568d6e0e6 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl @@ -40,7 +40,6 @@ oneway interface IInputMethod { 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 4b1753a82762..9cb2e68229f0 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 @@ oneway interface IPhoneStateListener { 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 c7fa757ac0b7..7ba26866ee03 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -66,8 +66,8 @@ interface ITelephonyRegistry { 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 f140e7980f52..1bc903a191ad 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -40,6 +40,8 @@ cc_library_shared { cppflags: ["-Wno-conversion-null"], + cpp_std: "gnu++20", + srcs: [ "android_animation_PropertyValuesHolder.cpp", "android_os_SystemClock.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 9e563dec3e4e..6ceffde68e87 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -645,7 +645,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p char jitmaxsizeOptsBuf[sizeof("-Xjitmaxsize:")-1 + PROPERTY_VALUE_MAX]; char jitinitialsizeOptsBuf[sizeof("-Xjitinitialsize:")-1 + PROPERTY_VALUE_MAX]; char jitthresholdOptsBuf[sizeof("-Xjitthreshold:")-1 + PROPERTY_VALUE_MAX]; - char useJitProfilesOptsBuf[sizeof("-Xjitsaveprofilinginfo:")-1 + PROPERTY_VALUE_MAX]; char jitprithreadweightOptBuf[sizeof("-Xjitprithreadweight:")-1 + PROPERTY_VALUE_MAX]; char jittransitionweightOptBuf[sizeof("-Xjittransitionweight:")-1 + PROPERTY_VALUE_MAX]; char hotstartupsamplesOptsBuf[sizeof("-Xps-hot-startup-method-samples:")-1 + PROPERTY_VALUE_MAX]; @@ -860,10 +859,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p parseRuntimeOption("dalvik.vm.jitpthreadpriority", jitpthreadpriorityOptsBuf, "-Xjitpthreadpriority:"); - property_get("dalvik.vm.usejitprofiles", useJitProfilesOptsBuf, ""); - if (strcmp(useJitProfilesOptsBuf, "true") == 0) { - addOption("-Xjitsaveprofilinginfo"); - } + addOption("-Xjitsaveprofilinginfo"); parseRuntimeOption("dalvik.vm.jitprithreadweight", jitprithreadweightOptBuf, diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index a8d72316036e..e9ada235b388 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -96,7 +96,7 @@ class LoaderAssetsProvider : public AssetsProvider { } bool ForEachFile(const std::string& /* root_path */, - const std::function<void(const StringPiece&, FileType)>& /* f */) const { + android::base::function_ref<void(StringPiece, FileType)> /* f */) const { return true; } @@ -402,7 +402,7 @@ static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) 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 @@ static jboolean NativeDefinesOverlayable(JNIEnv* env, jclass /*clazz*/, jlong pt // 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 cb97698fefea..939a0e411913 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -243,6 +243,23 @@ nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject } } +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 jint nativeInjectSensorData(JNIEnv *env, jclass clazz, jlong eventQ, jint //---------------------------------------------------------------------------- static const JNINativeMethod gSystemSensorManagerMethods[] = { - {"nativeClassInit", - "()V", - (void*)nativeClassInit }, - {"nativeCreate", - "(Ljava/lang/String;)J", - (void*)nativeCreate }, - - {"nativeGetSensorAtIndex", - "(JLandroid/hardware/Sensor;I)Z", - (void*)nativeGetSensorAtIndex }, - - {"nativeGetDynamicSensors", - "(JLjava/util/List;)V", - (void*)nativeGetDynamicSensors }, - - {"nativeIsDataInjectionEnabled", - "(J)Z", - (void*)nativeIsDataInjectionEnabled }, - - {"nativeCreateDirectChannel", - "(JJIILandroid/hardware/HardwareBuffer;)I", - (void*)nativeCreateDirectChannel }, - - {"nativeDestroyDirectChannel", - "(JI)V", - (void*)nativeDestroyDirectChannel }, - - {"nativeConfigDirectChannel", - "(JIII)I", - (void*)nativeConfigDirectChannel }, - - {"nativeSetOperationParameter", - "(JII[F[I)I", - (void*)nativeSetOperationParameter }, + {"nativeClassInit", "()V", (void *)nativeClassInit}, + {"nativeCreate", "(Ljava/lang/String;)J", (void *)nativeCreate}, + + {"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z", + (void *)nativeGetSensorAtIndex}, + + {"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors}, + + {"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors}, + + {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled}, + + {"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I", + (void *)nativeCreateDirectChannel}, + + {"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel}, + + {"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 465000069aab..7393c6f9324a 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -977,12 +977,11 @@ message UserControllerProto { 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 5816488e1098..ad8f7fb2d425 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 --> diff --git a/core/res/OWNERS b/core/res/OWNERS index 6d05e0785ec1..a2ef4005f5cb 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -8,7 +8,6 @@ hackbod@android.com hackbod@google.com ilyamaty@google.com jaggies@google.com -jdemeulenaere@google.com jsharkey@android.com jsharkey@google.com juliacr@google.com diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 2d832bc71684..79464937ff30 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 6460007b52de..eb7034437423 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 ccce9ba6a709..9a585a194ed1 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> @@ -2070,6 +2078,13 @@ <!-- Flag indicating whether the current device supports "Ask every time" for sms--> <bool name="config_sms_ask_every_time_support">true</bool> + <!-- Flag indicating whether the current device allows acknowledgement of SIM operation like + SM-PP or saving SMS to SIM can be done via the IMS interfaces. + If true,this means that the device supports sending of sim operation response via the + IMS interface APIs. This can be overridden to false for devices which can't send + sim operation acknowledgements via IMS interface APIs. --> + <bool name="config_smppsim_response_via_ims">false</bool> + <!-- Flag indicating whether the current device allows data. If true, this means that the device supports data connectivity through the telephony network. @@ -5977,4 +5992,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 61229cb79154..bc5878a0b2ed 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 ae033cab0807..ace7e4c31b4a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -315,6 +315,7 @@ <java-symbol type="bool" name="config_sms_ask_every_time_support" /> <java-symbol type="bool" name="config_sms_capable" /> <java-symbol type="bool" name="config_sms_utf8_support" /> + <java-symbol type="bool" name="config_smppsim_response_via_ims" /> <java-symbol type="bool" name="config_mobile_data_capable" /> <java-symbol type="bool" name="config_suspendWhenScreenOffDueToProximity" /> <java-symbol type="bool" name="config_swipeDisambiguation" /> @@ -1963,6 +1964,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 +2127,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 +4916,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/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java index 36aa915a67ec..1cc0a98512bf 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java @@ -210,9 +210,9 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes any(IServiceCallback.class))); doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule( - eq(FM_RADIO_MODULE_ID), anyString(), any(IBinder.class), any(Object.class))); + eq(FM_RADIO_MODULE_ID), anyString(), any(IBinder.class))); doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule( - eq(DAB_RADIO_MODULE_ID), anyString(), any(IBinder.class), any(Object.class))); + eq(DAB_RADIO_MODULE_ID), anyString(), any(IBinder.class))); when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock); when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java index 7a8475fe4d8f..a0346538ddc5 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java @@ -58,14 +58,13 @@ public final class RadioModuleTest { @Mock private android.hardware.broadcastradio.ICloseHandle mHalCloseHandleMock; - private final Object mLock = new Object(); // RadioModule under test private RadioModule mRadioModule; private android.hardware.broadcastradio.IAnnouncementListener mHalListener; @Before public void setup() throws RemoteException { - mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES, mLock); + mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES); // TODO(b/241118988): test non-null image for getImage method when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 87d0ea473665..993ca7728374 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -83,7 +83,6 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Mock private IBroadcastRadio mBroadcastRadioMock; private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks; - private final Object mLock = new Object(); // RadioModule under test private RadioModule mRadioModule; @@ -104,7 +103,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mRadioModule = new RadioModule(mBroadcastRadioMock, - AidlTestUtils.makeDefaultModuleProperties(), mLock); + AidlTestUtils.makeDefaultModuleProperties()); doAnswer(invocation -> { mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0]; diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java index 0b7bbeaab28e..c9224bfbe1c7 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/BroadcastRadioServiceHidlTest.java @@ -59,8 +59,6 @@ public final class BroadcastRadioServiceHidlTest extends ExtendedRadioMockitoTes new ArrayList<>(Arrays.asList("FmService", "DabService")); private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC}; - private final Object mLock = new Object(); - private BroadcastRadioService mBroadcastRadioService; private DeathRecipient mFmDeathRecipient; @@ -200,7 +198,7 @@ public final class BroadcastRadioServiceHidlTest extends ExtendedRadioMockitoTes mockServiceManager(); mBroadcastRadioService = new BroadcastRadioService(/* nextModuleId= */ FM_RADIO_MODULE_ID, - mLock, mServiceManagerMock); + mServiceManagerMock); } private void mockServiceManager() throws RemoteException { @@ -221,9 +219,9 @@ public final class BroadcastRadioServiceHidlTest extends ExtendedRadioMockitoTes }).thenReturn(true); doReturn(mFmRadioModuleMock).when(() -> RadioModule.tryLoadingModule( - eq(FM_RADIO_MODULE_ID), anyString(), any(Object.class))); + eq(FM_RADIO_MODULE_ID), anyString())); doReturn(mDabRadioModuleMock).when(() -> RadioModule.tryLoadingModule( - eq(DAB_RADIO_MODULE_ID), anyString(), any(Object.class))); + eq(DAB_RADIO_MODULE_ID), anyString())); when(mFmRadioModuleMock.getProperties()).thenReturn(mFmModuleMock); when(mDabRadioModuleMock.getProperties()).thenReturn(mDabModuleMock); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java index 48f5a461d631..1f5e77038728 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/RadioModuleHidlTest.java @@ -62,13 +62,12 @@ public final class RadioModuleHidlTest { @Mock private android.hardware.broadcastradio.V2_0.ICloseHandle mHalCloseHandleMock; - private final Object mLock = new Object(); private RadioModule mRadioModule; private android.hardware.broadcastradio.V2_0.IAnnouncementListener mHalListener; @Before public void setup() throws RemoteException { - mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES, mLock); + mRadioModule = new RadioModule(mBroadcastRadioMock, TEST_MODULE_PROPERTIES); when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(new ArrayList<Byte>(0)); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java index e3c9faa601e7..7d604d497984 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/StartProgramListUpdatesFanoutTest.java @@ -64,7 +64,6 @@ public class StartProgramListUpdatesFanoutTest extends ExtendedRadioMockitoTestC @Mock ITunerSession mHalTunerSessionMock; private android.hardware.radio.ITunerCallback[] mAidlTunerCallbackMocks; - private final Object mLock = new Object(); // RadioModule under test private RadioModule mRadioModule; @@ -100,7 +99,7 @@ public class StartProgramListUpdatesFanoutTest extends ExtendedRadioMockitoTestC doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mRadioModule = new RadioModule(mBroadcastRadioMock, - TestUtils.makeDefaultModuleProperties(), mLock); + TestUtils.makeDefaultModuleProperties()); doAnswer((Answer) invocation -> { mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0]; diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java index b7da5d038f77..ff988a21473a 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java @@ -84,7 +84,6 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR); private static final int UNSUPPORTED_CONFIG_FLAG = 0; - private final Object mLock = new Object(); private final ArrayMap<Integer, Boolean> mHalConfigMap = new ArrayMap<>(); private RadioModule mRadioModule; private ITunerCallback mHalTunerCallback; @@ -105,7 +104,7 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mRadioModule = new RadioModule(mBroadcastRadioMock, - TestUtils.makeDefaultModuleProperties(), mLock); + TestUtils.makeDefaultModuleProperties()); doAnswer(invocation -> { mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0]; 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 000000000000..11afd045f364 --- /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 000000000000..a9583fdc2e2d --- /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/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt index cfca0375bb96..625c318d9efd 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt @@ -18,8 +18,12 @@ package android.content.res import androidx.core.util.forEach import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest import androidx.test.filters.SmallTest import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import kotlin.math.ceil +import kotlin.math.floor import org.junit.Test import org.junit.runner.RunWith @@ -72,7 +76,70 @@ class FontScaleConverterFactoryTest { } } + @LargeTest + @Test + fun allFeasibleScalesAndConversionsDoNotCrash() { + generateSequenceOfFractions(-10000f..10000f, step = 0.01f) + .mapNotNull{ FontScaleConverterFactory.forScale(it) } + .flatMap{ table -> + generateSequenceOfFractions(-10000f..10000f, step = 0.01f) + .map{ Pair(table, it) } + } + .forEach { (table, sp) -> + try { + assertWithMessage( + "convertSpToDp(%s) on table: %s", + sp.toString(), + table.toString() + ) + .that(table.convertSpToDp(sp)) + .isFinite() + } catch (e: Exception) { + throw AssertionError("Exception during convertSpToDp($sp) on table: $table", e) + } + } + } + + @Test + fun testGenerateSequenceOfFractions() { + val fractions = generateSequenceOfFractions(-1000f..1000f, step = 0.1f) + .toList() + fractions.forEach { + assertThat(it).isAtLeast(-1000f) + assertThat(it).isAtMost(1000f) + } + + assertThat(fractions).isInStrictOrder() + assertThat(fractions).hasSize(1000 * 2 * 10 + 1) // Don't forget the 0 in the middle! + + assertThat(fractions).contains(100f) + assertThat(fractions).contains(500.1f) + assertThat(fractions).contains(500.2f) + assertThat(fractions).contains(0.2f) + assertThat(fractions).contains(0f) + assertThat(fractions).contains(-10f) + assertThat(fractions).contains(-10f) + assertThat(fractions).contains(-10.3f) + + assertThat(fractions).doesNotContain(-10.31f) + assertThat(fractions).doesNotContain(0.35f) + assertThat(fractions).doesNotContain(0.31f) + assertThat(fractions).doesNotContain(-.35f) + } + companion object { private const val CONVERSION_TOLERANCE = 0.05f } } + +fun generateSequenceOfFractions( + range: ClosedFloatingPointRange<Float>, + step: Float +): Sequence<Float> { + val multiplier = 1f / step + val start = floor(range.start * multiplier).toInt() + val endInclusive = ceil(range.endInclusive * multiplier).toInt() + return generateSequence(start) { it + 1 } + .takeWhile { it <= endInclusive } + .map{ it.toFloat() / multiplier } +} diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java index a528c1975177..6bf8f5678b33 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java @@ -203,16 +203,22 @@ public class TypefaceTest { 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 f7ca822c36e2..0c7ff4a762b5 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.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import java.time.Duration; +import java.util.Arrays; @Presubmit @RunWith(MockitoJUnitRunner.class) @@ -62,16 +64,363 @@ public class VibrationEffectTest { 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 @@ public class VibrationEffectTest { @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 @@ public class VibrationEffectTest { 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/util/RotationUtilsTest.java b/core/tests/coretests/src/android/util/RotationUtilsTest.java index 826eb3070c7e..1b1ee4fb1a1c 100644 --- a/core/tests/coretests/src/android/util/RotationUtilsTest.java +++ b/core/tests/coretests/src/android/util/RotationUtilsTest.java @@ -18,6 +18,7 @@ package android.util; import static android.util.RotationUtils.rotateBounds; import static android.util.RotationUtils.rotatePoint; +import static android.util.RotationUtils.rotatePointF; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; @@ -25,6 +26,7 @@ import static android.view.Surface.ROTATION_90; import static org.junit.Assert.assertEquals; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -79,4 +81,26 @@ public class RotationUtilsTest { rotatePoint(testResult, ROTATION_270, parentW, parentH); assertEquals(new Point(560, 60), testResult); } + + @Test + public void testRotatePointF() { + float parentW = 1000f; + float parentH = 600f; + PointF testPt = new PointF(60f, 40f); + + PointF testResult = new PointF(testPt); + rotatePointF(testResult, ROTATION_90, parentW, parentH); + assertEquals(40f, testResult.x, .1f); + assertEquals(940f, testResult.y, .1f); + + testResult.set(testPt.x, testPt.y); + rotatePointF(testResult, ROTATION_180, parentW, parentH); + assertEquals(940f, testResult.x, .1f); + assertEquals(560f, testResult.y, .1f); + + testResult.set(testPt.x, testPt.y); + rotatePointF(testResult, ROTATION_270, parentW, parentH); + assertEquals(560f, testResult.x, .1f); + assertEquals(60f, testResult.y, .1f); + } } 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 000000000000..77dd8bd7f976 --- /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 be9da11057a2..6a96f280d603 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -517,6 +517,19 @@ public class InsetsStateTest { 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 dd9634bd68fb..4fed396456da 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -39,13 +39,16 @@ public class WindowInsetsTest { @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 @@ public class WindowInsetsTest { 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/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java index caec3651210a..0f30cfead4f7 100644 --- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java @@ -286,6 +286,39 @@ public class OverlayConfigTest { } @Test + public void testPartialConfigPartitionPrecedence() throws IOException { + createFile("/odm/overlay/config/config.xml", + "<config>" + + " <overlay package=\"two\" enabled=\"true\" />" + + "</config>"); + + mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true, + 1); + mScannerRule.addOverlay(createFile("/odm/overlay/two.apk"), "two"); + mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 0, + true, 0); + + final OverlayConfig overlayConfig = createConfigImpl(); + assertConfig(overlayConfig, "one", false, true, 0); + assertConfig(overlayConfig, "two", true, true, 1); + assertConfig(overlayConfig, "three", false, true, 2); + } + + @Test + public void testNoConfigPartitionPrecedence() throws IOException { + mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true, + 1); + mScannerRule.addOverlay(createFile("/odm/overlay/two.apk"), "two", "android", 0, true, 2); + mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 0, + true, 0); + + final OverlayConfig overlayConfig = createConfigImpl(); + assertConfig(overlayConfig, "one", false, true, 0); + assertConfig(overlayConfig, "two", false, true, 1); + assertConfig(overlayConfig, "three", false, true, 2); + } + + @Test public void testImmutable() throws IOException { createFile("/product/overlay/config/config.xml", "<config>" @@ -507,37 +540,6 @@ public class OverlayConfigTest { } @Test - public void testNoConfigsAllowPartitionReordering() throws IOException { - mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true, - 1); - mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true, - 0); - - final OverlayConfig overlayConfig = createConfigImpl(); - assertConfig(overlayConfig, "one", false, true, 1); - assertConfig(overlayConfig, "two", false, true, 0); - } - - @Test - public void testConfigDisablesPartitionReordering() throws IOException { - createFile("/odm/overlay/config/config.xml", - "<config>" - + " <overlay package=\"two\" enabled=\"true\" />" - + "</config>"); - - mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true, - 1); - mScannerRule.addOverlay(createFile("/odm/overlay/two.apk"), "two"); - mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 0, - true, 0); - - final OverlayConfig overlayConfig = createConfigImpl(); - assertConfig(overlayConfig, "one", false, true, 0); - assertConfig(overlayConfig, "two", true, true, 1); - assertConfig(overlayConfig, "three", false, true, 2); - } - - @Test public void testStaticOverlayOutsideOverlayDir() throws IOException { mScannerRule.addOverlay(createFile("/product/app/one.apk"), "one", "android", 0, true, 0); @@ -550,7 +552,7 @@ public class OverlayConfigTest { @Test public void testSortStaticOverlaysDifferentTargets() throws IOException { mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "other", 0, true, 0); - mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true, + mScannerRule.addOverlay(createFile("/vendor/overlay/two.apk"), "two", "android", 0, true, 0); final OverlayConfig overlayConfig = createConfigImpl(); @@ -559,15 +561,33 @@ public class OverlayConfigTest { } @Test + public void testSortStaticOverlaysDifferentPartitions() throws IOException { + mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true, + 2); + mScannerRule.addOverlay(createFile("/vendor/overlay/two.apk"), "two", "android", 0, true, + 3); + mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 0, + true, 0); + mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four", "android", 0, + true, 1); + + final OverlayConfig overlayConfig = createConfigImpl(); + assertConfig(overlayConfig, "one", false, true, 0); + assertConfig(overlayConfig, "two", false, true, 1); + assertConfig(overlayConfig, "three", false, true, 2); + assertConfig(overlayConfig, "four", false, true, 3); + } + + @Test public void testSortStaticOverlaysSamePriority() throws IOException { mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true, 0); - mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true, + mScannerRule.addOverlay(createFile("/vendor/overlay/two.apk"), "two", "android", 0, true, 0); final OverlayConfig overlayConfig = createConfigImpl(); - assertConfig(overlayConfig, "one", false, true, 1); - assertConfig(overlayConfig, "two", false, true, 0); + assertConfig(overlayConfig, "one", false, true, 0); + assertConfig(overlayConfig, "two", false, true, 1); } @Test 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 d10f173328be..4d4ec3523d39 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 @@ public class ActionBarOverlayLayoutTest { } 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/fuzzers/FuzzService/Android.bp b/core/tests/fuzzers/FuzzService/Android.bp new file mode 100644 index 000000000000..5093185688df --- /dev/null +++ b/core/tests/fuzzers/FuzzService/Android.bp @@ -0,0 +1,28 @@ +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +java_library { + name: "random_parcel_lib", + srcs: ["FuzzBinder.java"], +} + +cc_library_shared { + name: "librandom_parcel_jni", + defaults: ["service_fuzzer_defaults"], + srcs: [ + "random_parcel_jni.cpp", + ], + shared_libs: [ + "libandroid_runtime", + "libbase", + "liblog", + ], + static_libs: [ + "libnativehelper_lazy", + "libbinder_random_parcel", + ], + cflags: [ + "-Wno-unused-parameter", + ], +} diff --git a/core/tests/fuzzers/FuzzService/FuzzBinder.java b/core/tests/fuzzers/FuzzService/FuzzBinder.java new file mode 100644 index 000000000000..7096f52ab392 --- /dev/null +++ b/core/tests/fuzzers/FuzzService/FuzzBinder.java @@ -0,0 +1,38 @@ +/* + * 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 randomparcel; +import android.os.IBinder; + +public class FuzzBinder { + static { + System.loadLibrary("random_parcel_jni"); + } + + // DO NOT REUSE: This API should be called from fuzzer to setup JNI dependencies from + // libandroid_runtime. THIS IS WORKAROUND. Please file a bug if you need to use this. + public static void init() { + System.loadLibrary("android_runtime"); + registerNatives(); + } + + // This API automatically fuzzes provided service + public static void fuzzService(IBinder binder, byte[] data) { + fuzzServiceInternal(binder, data); + } + + private static native void fuzzServiceInternal(IBinder binder, byte[] data); + private static native int registerNatives(); +} diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp new file mode 100644 index 000000000000..c0528d5c7b9a --- /dev/null +++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#include "random_parcel_jni.h" +#include <android_util_Binder.h> +#include <fuzzbinder/libbinder_driver.h> +#include <fuzzer/FuzzedDataProvider.h> +using namespace android; + +// JNI interface for fuzzService +JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fuzzServiceInternal(JNIEnv *env, jobject thiz, jobject javaBinder, jbyteArray fuzzData) { + size_t len = static_cast<size_t>(env->GetArrayLength(fuzzData)); + uint8_t data[len]; + env->GetByteArrayRegion(fuzzData, 0, len, reinterpret_cast<jbyte*>(data)); + + FuzzedDataProvider provider(data, len); + sp<IBinder> binder = android::ibinderForJavaObject(env, javaBinder); + fuzzService(binder, std::move(provider)); +} + +// API used by AIDL fuzzers to access JNI functions from libandroid_runtime. +JNIEXPORT jint JNICALL Java_randomparcel_FuzzBinder_registerNatives(JNIEnv* env) { + return registerFrameworkNatives(env); +} diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.h b/core/tests/fuzzers/FuzzService/random_parcel_jni.h new file mode 100644 index 000000000000..20a4c9d46aa6 --- /dev/null +++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.h @@ -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. + */ +#include <jni.h> + +extern "C" { + JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fuzzServiceInternal(JNIEnv *env, jobject thiz, jobject javaBinder, jbyteArray fuzzData); + + // Function to register libandroid_runtime JNI functions with java env. + JNIEXPORT jint JNICALL Java_randomparcel_FuzzBinder_registerNatives(JNIEnv* env); + + // Function from AndroidRuntime + jint registerFrameworkNatives(JNIEnv* env); +} diff --git a/core/tests/fuzzers/OWNERS b/core/tests/fuzzers/OWNERS new file mode 100644 index 000000000000..b972ac0f74e6 --- /dev/null +++ b/core/tests/fuzzers/OWNERS @@ -0,0 +1,2 @@ +smoreland@google.com +waghpawan@google.com diff --git a/core/tests/fuzzers/java_service_fuzzer/Android.bp b/core/tests/fuzzers/java_service_fuzzer/Android.bp new file mode 100644 index 000000000000..625de143a685 --- /dev/null +++ b/core/tests/fuzzers/java_service_fuzzer/Android.bp @@ -0,0 +1,40 @@ +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +aidl_interface { + name: "fuzzTestInterface", + srcs: ["fuzztest/ITestService.aidl"], + unstable: true, + backend: { + java: { + enabled: true, + }, + }, +} + +java_fuzz { + name: "java_binder_service_fuzzer", + srcs: [ + "ServiceFuzzer.java", + "TestService.java", + ":framework-core-sources-for-fuzzers", + ], + static_libs: [ + "jazzer", + "fuzzTestInterface-java", + "random_parcel_lib", + ], + jni_libs: [ + "librandom_parcel_jni", + "libc++", + "libandroid_runtime", + ], + libs: [ + "framework", + "unsupportedappusage", + "ext", + "framework-res", + ], + native_bridge_supported: true, +} diff --git a/core/tests/fuzzers/java_service_fuzzer/ServiceFuzzer.java b/core/tests/fuzzers/java_service_fuzzer/ServiceFuzzer.java new file mode 100644 index 000000000000..a6e09865fcad --- /dev/null +++ b/core/tests/fuzzers/java_service_fuzzer/ServiceFuzzer.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; + +import randomparcel.FuzzBinder; + +public class ServiceFuzzer { + + static { + // Initialize fuzzService and JNI dependencies + FuzzBinder.init(); + } + + public static void fuzzerTestOneInput(FuzzedDataProvider data) { + TestService service = new TestService(); + FuzzBinder.fuzzService(service, data.consumeRemainingAsBytes()); + } +} diff --git a/core/tests/fuzzers/java_service_fuzzer/TestService.java b/core/tests/fuzzers/java_service_fuzzer/TestService.java new file mode 100644 index 000000000000..4404386bd06c --- /dev/null +++ b/core/tests/fuzzers/java_service_fuzzer/TestService.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +import fuzztest.ITestService; + +public class TestService extends ITestService.Stub { + + @Override + public boolean repeatData(boolean token) { + return token; + } +} diff --git a/core/tests/fuzzers/java_service_fuzzer/fuzztest/ITestService.aidl b/core/tests/fuzzers/java_service_fuzzer/fuzztest/ITestService.aidl new file mode 100644 index 000000000000..b766c9f85a53 --- /dev/null +++ b/core/tests/fuzzers/java_service_fuzzer/fuzztest/ITestService.aidl @@ -0,0 +1,20 @@ +/* + * 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 fuzztest; + +interface ITestService { + boolean repeatData(boolean token); +}
\ No newline at end of file diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java index a76c640db74a..caa7312a4475 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 androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; + @RunWith(AndroidJUnit4.class) @SmallTest public class IntArrayTest { @@ -35,51 +37,65 @@ public class IntArrayTest { 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); + } + + @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(int[] expected, IntArray intArray) { - assertEquals(expected.length, intArray.size()); - assertArrayEquals(expected, intArray.toArray()); + 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 215b60ec9d0e..a43e2251d486 100644 --- a/data/keyboards/Generic.kl +++ b/data/keyboards/Generic.kl @@ -371,6 +371,10 @@ key 405 LAST_CHANNEL # 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 33b9a475ad21..bfda690e0b05 100644 --- a/graphics/java/android/graphics/PathIterator.java +++ b/graphics/java/android/graphics/PathIterator.java @@ -281,7 +281,7 @@ public class PathIterator implements Iterator<PathIterator.Segment> { 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 3b7d0e14893c..9fb627fcc501 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1207,6 +1207,7 @@ public class Typeface { * 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 @@ public class Typeface { /** * 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 82ced438f0a5..0e198d5c56ec 100644 --- a/graphics/java/android/view/PixelCopy.java +++ b/graphics/java/android/view/PixelCopy.java @@ -382,9 +382,9 @@ public final class PixelCopy { } /** - * 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 @@ public final class PixelCopy { } /** - * 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 @@ public final class PixelCopy { * * @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 @@ public final class PixelCopy { } /** - * 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 @@ public final class PixelCopy { } /** - * 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/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 1fd91debe3f6..9674b69baa00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -475,8 +475,13 @@ public class BubbleController implements ConfigurationChangeListener { @VisibleForTesting public void onStatusBarStateChanged(boolean isShade) { + boolean didChange = mIsStatusBarShade != isShade; + if (DEBUG_BUBBLE_CONTROLLER) { + Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange); + } mIsStatusBarShade = isShade; - if (!mIsStatusBarShade) { + if (!mIsStatusBarShade && didChange) { + // Only collapse stack on change collapseStack(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index e8b0f0265394..214b304df07c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -219,7 +219,11 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); // Only insets the divider bar with task bar when it's expanded so that the rounded corners // will be drawn against task bar. - if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { + // But there is no need to do it when IME showing because there are no rounded corners at + // the bottom. This also avoids the problem of task bar height not changing when IME + // floating. + if (!insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME) + && taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { mTempRect.inset(taskBarInsetsSource.calculateVisibleInsets(mTempRect)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index ae496165028f..45b234a6398a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -122,6 +122,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private int mDensity; private final boolean mDimNonImeSide; + private ValueAnimator mDividerFlingAnimator; public SplitLayout(String windowName, Context context, Configuration configuration, SplitLayoutHandler splitLayoutHandler, @@ -395,6 +396,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mSplitWindowManager.release(t); mDisplayImeController.removePositionProcessor(mImePositionProcessor); mImePositionProcessor.reset(); + if (mDividerFlingAnimator != null) { + mDividerFlingAnimator.cancel(); + } + resetDividerPosition(); } public void release() { @@ -577,13 +582,18 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange CUJ_SPLIT_SCREEN_RESIZE); return; } - ValueAnimator animator = ValueAnimator + + if (mDividerFlingAnimator != null) { + mDividerFlingAnimator.cancel(); + } + + mDividerFlingAnimator = ValueAnimator .ofInt(from, to) .setDuration(duration); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - animator.addUpdateListener( + mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mDividerFlingAnimator.addUpdateListener( animation -> updateDivideBounds((int) animation.getAnimatedValue())); - animator.addListener(new AnimatorListenerAdapter() { + mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (flingFinishedCallback != null) { @@ -591,14 +601,15 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } InteractionJankMonitorUtils.endTracing( CUJ_SPLIT_SCREEN_RESIZE); + mDividerFlingAnimator = null; } @Override public void onAnimationCancel(Animator animation) { - setDividePosition(to, true /* applyLayoutChange */); + mDividerFlingAnimator = null; } }); - animator.start(); + mDividerFlingAnimator.start(); } /** Switch both surface position with animation. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 90b35a5a55e1..44bcdb2d5de5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -82,7 +82,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { mTasks.put(taskInfo.taskId, state); if (!Transitions.ENABLE_SHELL_TRANSITIONS) { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t); + mWindowDecorationViewModel.onTaskOpening(taskInfo, leash, t, t); t.apply(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java index 168f6d79a390..6e710f7caeda 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java @@ -120,7 +120,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - mWindowDecorViewModel.createWindowDecoration( + mWindowDecorViewModel.onTaskOpening( change.getTaskInfo(), change.getLeash(), startT, finishT); } @@ -128,31 +128,23 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - mWindowDecorViewModel.setupWindowDecorationForTransition( - change.getTaskInfo(), startT, finishT); + mWindowDecorViewModel.onTaskClosing(change.getTaskInfo(), startT, finishT); } private void onChangeTransitionReady( TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - mWindowDecorViewModel.setupWindowDecorationForTransition( - change.getTaskInfo(), startT, finishT); + mWindowDecorViewModel.onTaskChanging( + change.getTaskInfo(), change.getLeash(), startT, finishT); } private void onToFrontTransitionReady( TransitionInfo.Change change, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - boolean exists = mWindowDecorViewModel.setupWindowDecorationForTransition( - change.getTaskInfo(), - startT, - finishT); - if (!exists) { - // Window caption does not exist, create it - mWindowDecorViewModel.createWindowDecoration( - change.getTaskInfo(), change.getLeash(), startT, finishT); - } + mWindowDecorViewModel.onTaskChanging( + change.getTaskInfo(), change.getLeash(), startT, finishT); } @Override @@ -188,4 +180,4 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i)); } } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index 75a4091c7d78..6623f5ca84ee 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -103,7 +103,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { if (mWindowDecorViewModelOptional.isPresent()) { SurfaceControl.Transaction t = new SurfaceControl.Transaction(); createdWindowDecor = mWindowDecorViewModelOptional.get() - .createWindowDecoration(taskInfo, leash, t, t); + .onTaskOpening(taskInfo, leash, t, t); t.apply(); } if (!createdWindowDecor) { 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 f170e774739f..8ba2583757cd 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.graphics.Rect; 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } }; + /** + * 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index d28a9f3cf8ff..efe938f0a274 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -612,12 +612,21 @@ public class PipController implements PipTransitionController.PipTransitionCallb new DisplayInsetsController.OnInsetsChangedListener() { @Override public void insetsChanged(InsetsState insetsState) { + DisplayLayout pendingLayout = + mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()); + if (mIsInFixedRotation + || pendingLayout.rotation() + != mPipBoundsState.getDisplayLayout().rotation()) { + // bail out if there is a pending rotation or fixed rotation change + return; + } int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom; onDisplayChanged( mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()), false /* saveRestoreSnapFraction */); int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom; if (!mEnablePipKeepClearAlgorithm) { + // offset PiP to adjust for bottom inset change int pipTop = mPipBoundsState.getBounds().top; int diff = newMaxMovementBound - oldMaxMovementBound; if (diff < 0 && pipTop > newMaxMovementBound) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index acb71a80ee8a..4cb76230606f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -669,6 +669,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.init(); mSplitLayout.setDivideRatio(splitRatio); + // Apply surface bounds before animation start. + SurfaceControl.Transaction startT = mTransactionPool.acquire(); + updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */); + startT.apply(); + mTransactionPool.release(startT); + // Set false to avoid record new bounds with old task still on top; mShouldUpdateRecents = false; mIsDividerRemoteAnimating = true; @@ -742,7 +748,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { setDividerVisibility(true, t); - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); }); setEnterInstanceId(instanceId); @@ -1035,7 +1040,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mIsDividerRemoteAnimating = false; mSplitLayout.getInvisibleBounds(mTempRect1); - if (childrenToTop == null) { + if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) { mSideStage.removeAllTasks(wct, false /* toTop */); mMainStage.deactivate(wct, false /* toTop */); wct.reorder(mRootTaskInfo.token, false /* onTop */); @@ -1294,13 +1299,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - private void onStageChildTaskEnterPip() { - // When the exit split-screen is caused by one of the task enters auto pip, - // we want both tasks to be put to bottom instead of top, otherwise it will end up - // a fullscreen plus a pinned task instead of pinned only at the end of the transition. - exitSplitScreen(null, EXIT_REASON_CHILD_TASK_ENTER_PIP); - } - private void updateRecentTasksSplitPair() { if (!mShouldUpdateRecents) { return; @@ -2063,7 +2061,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Update divider state after animation so that it is still around and positioned // properly for the animation itself. mSplitLayout.release(); - mSplitLayout.resetDividerPosition(); mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; } } @@ -2340,11 +2337,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onChildTaskEnterPip() { - StageCoordinator.this.onStageChildTaskEnterPip(); - } - - @Override public void onRootTaskVanished() { reset(); StageCoordinator.this.onRootTaskVanished(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index bcf900b99c69..358f712f76b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -18,7 +18,6 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.RemoteAnimationTarget.MODE_OPENING; @@ -74,8 +73,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); - void onChildTaskEnterPip(); - void onRootTaskVanished(); void onNoLongerSupportMultiWindow(); @@ -257,9 +254,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { // Status is managed/synchronized by the transition lifecycle. return; } - if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { - mCallbacks.onChildTaskEnterPip(); - } sendStatusChanged(); } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 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 4e1fa290270d..485b400f458d 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 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { 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 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { 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 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { } }; 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 9469529de8f1..b4e05848882c 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 @@ package com.android.wm.shell.transition; 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 class RemoteTransitionHandler implements Transitions.TransitionHandler { 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 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { }; 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 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { 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 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } }; 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 56d51bda762f..c6935c054422 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 @@ public class Transitions implements RemoteCallable<Transitions> { // 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 @@ public class Transitions implements RemoteCallable<Transitions> { 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 @@ public class Transitions implements RemoteCallable<Transitions> { } 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 @@ public class Transitions implements RemoteCallable<Transitions> { 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 @@ public class Transitions implements RemoteCallable<Transitions> { } 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/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 8369569b4163..e40db4e4dcf2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -55,6 +55,8 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.transition.Transitions; +import java.util.function.Supplier; + /** * View model for the window decoration with a caption and shadows. Works with * {@link CaptionWindowDecoration}. @@ -62,6 +64,8 @@ import com.android.wm.shell.transition.Transitions; public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private static final String TAG = "CaptionViewModel"; + private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory; + private final Supplier<InputManager> mInputManagerSupplier; private final ActivityTaskManager mActivityTaskManager; private final ShellTaskOrganizer mTaskOrganizer; private final Context mContext; @@ -77,6 +81,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl(); + private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory(); public CaptionWindowDecorViewModel( Context context, @@ -86,6 +91,29 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { DisplayController displayController, SyncTransactionQueue syncQueue, DesktopModeController desktopModeController) { + this( + context, + mainHandler, + mainChoreographer, + taskOrganizer, + displayController, + syncQueue, + desktopModeController, + new CaptionWindowDecoration.Factory(), + InputManager::getInstance); + } + + public CaptionWindowDecorViewModel( + Context context, + Handler mainHandler, + Choreographer mainChoreographer, + ShellTaskOrganizer taskOrganizer, + DisplayController displayController, + SyncTransactionQueue syncQueue, + DesktopModeController desktopModeController, + CaptionWindowDecoration.Factory captionWindowDecorFactory, + Supplier<InputManager> inputManagerSupplier) { + mContext = context; mMainHandler = mainHandler; mMainChoreographer = mainChoreographer; @@ -94,7 +122,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mDisplayController = displayController; mSyncQueue = syncQueue; mDesktopModeController = desktopModeController; - mTransitionDragActive = false; + + mCaptionWindowDecorFactory = captionWindowDecorFactory; + mInputManagerSupplier = inputManagerSupplier; + } + + void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) { + mEventReceiverFactory = eventReceiverFactory; } @Override @@ -103,42 +137,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } @Override - public boolean createWindowDecoration( + public boolean onTaskOpening( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { if (!shouldShowWindowDecor(taskInfo)) return false; - CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId); - if (oldDecoration != null) { - // close the old decoration if it exists to avoid two window decorations being added - oldDecoration.close(); - } - final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration( - mContext, - mDisplayController, - mTaskOrganizer, - taskInfo, - taskSurface, - mMainHandler, - mMainChoreographer, - mSyncQueue); - mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); - - TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration, - mDragStartListener); - CaptionTouchEventListener touchEventListener = - new CaptionTouchEventListener(taskInfo, taskPositioner, - windowDecoration.getDragDetector()); - windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); - windowDecoration.setDragResizeCallback(taskPositioner); - setupWindowDecorationForTransition(taskInfo, startT, finishT); - if (mInputMonitor == null) { - mInputMonitor = InputManager.getInstance().monitorGestureInput( - "caption-touch", mContext.getDisplayId()); - mEventReceiver = new EventReceiver( - mInputMonitor.getInputChannel(), Looper.myLooper()); - } + createWindowDecoration(taskInfo, taskSurface, startT, finishT); return true; } @@ -151,25 +156,45 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } @Override - public boolean setupWindowDecorationForTransition( + public void onTaskChanging( + RunningTaskInfo taskInfo, + SurfaceControl taskSurface, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); + + if (!shouldShowWindowDecor(taskInfo)) { + if (decoration != null) { + destroyWindowDecoration(taskInfo); + } + return; + } + + if (decoration == null) { + createWindowDecoration(taskInfo, taskSurface, startT, finishT); + } else { + decoration.relayout(taskInfo, startT, finishT); + } + } + + @Override + public void onTaskClosing( RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); - if (decoration == null) return false; + if (decoration == null) return; decoration.relayout(taskInfo, startT, finishT); - return true; } @Override - public boolean destroyWindowDecoration(RunningTaskInfo taskInfo) { + public void destroyWindowDecoration(RunningTaskInfo taskInfo) { final CaptionWindowDecoration decoration = mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId); - if (decoration == null) return false; + if (decoration == null) return; decoration.close(); - return true; } private class CaptionTouchEventListener implements @@ -217,6 +242,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { decoration.setButtonVisibility(); } } + private void injectBackKey() { sendBackEvent(KeyEvent.ACTION_DOWN); sendBackEvent(KeyEvent.ACTION_UP); @@ -266,7 +292,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { */ private void handleEventForMove(MotionEvent e) { RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); - int windowingMode = mDesktopModeController + int windowingMode = mDesktopModeController .getDisplayAreaWindowingMode(taskInfo.displayId); if (windowingMode == WINDOWING_MODE_FULLSCREEN) { return; @@ -302,7 +328,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } // InputEventReceiver to listen for touch input outside of caption bounds - private class EventReceiver extends InputEventReceiver { + class EventReceiver extends InputEventReceiver { EventReceiver(InputChannel channel, Looper looper) { super(channel, looper); } @@ -318,8 +344,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } } + class EventReceiverFactory { + EventReceiver create(InputChannel channel, Looper looper) { + return new EventReceiver(channel, looper); + } + } + /** * Handle MotionEvents relevant to focused task's caption that don't directly touch it + * * @param ev the {@link MotionEvent} received by {@link EventReceiver} */ private void handleReceivedMotionEvent(MotionEvent ev) { @@ -401,7 +434,6 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { return focusedDecor; } - private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; return DesktopModeStatus.IS_SUPPORTED @@ -410,7 +442,47 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { .getResources().getConfiguration().smallestScreenWidthDp >= 600; } - private class DragStartListenerImpl implements TaskPositioner.DragStartListener{ + private void createWindowDecoration( + ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl taskSurface, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT) { + CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId); + if (oldDecoration != null) { + // close the old decoration if it exists to avoid two window decorations being added + oldDecoration.close(); + } + final CaptionWindowDecoration windowDecoration = + mCaptionWindowDecorFactory.create( + mContext, + mDisplayController, + mTaskOrganizer, + taskInfo, + taskSurface, + mMainHandler, + mMainChoreographer, + mSyncQueue); + mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); + + TaskPositioner taskPositioner = + new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener); + CaptionTouchEventListener touchEventListener = + new CaptionTouchEventListener( + taskInfo, taskPositioner, windowDecoration.getDragDetector()); + windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); + windowDecoration.setDragResizeCallback(taskPositioner); + windowDecoration.relayout(taskInfo, startT, finishT); + if (mInputMonitor == null) { + InputManager inputManager = mInputManagerSupplier.get(); + mInputMonitor = + inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId()); + mEventReceiver = + mEventReceiverFactory.create( + mInputMonitor.getInputChannel(), Looper.myLooper()); + } + } + + private class DragStartListenerImpl implements TaskPositioner.DragStartListener { @Override public void onDragStart(int taskId) { mWindowDecorByTaskId.get(taskId).closeHandleMenu(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 59576cd3ec15..037ca2031254 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -42,7 +42,8 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus; /** * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with - * {@link CaptionWindowDecorViewModel}. The caption bar contains a handle, back button, and close button. + * {@link CaptionWindowDecorViewModel}. The caption bar contains a handle, back button, and close + * button. * * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't. */ @@ -181,12 +182,12 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) { closeDragResizeListener(); mDragResizeListener = new DragResizeInputListener( - mContext, - mHandler, - mChoreographer, - mDisplay.getDisplayId(), - mDecorationContainerSurface, - mDragResizeCallback); + mContext, + mHandler, + mChoreographer, + mDisplay.getDisplayId(), + mDecorationContainerSurface, + mDragResizeCallback); } int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop(); @@ -242,7 +243,6 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL /** * Sets the visibility of buttons and color of caption based on desktop mode status - * */ void setButtonVisibility() { mDesktopActive = DesktopModeStatus.isActive(mContext); @@ -313,6 +313,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL /** * Close an open handle menu if input is outside of menu coordinates + * * @param ev the tapped point to compare against */ void closeHandleMenuIfNeeded(MotionEvent ev) { @@ -329,6 +330,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL /** * Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption + * * @param ev the {@link MotionEvent} to offset * @return the point of the input in local space */ @@ -343,7 +345,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL /** * Determine if a passed MotionEvent is in a view in caption - * @param ev the {@link MotionEvent} to check + * + * @param ev the {@link MotionEvent} to check * @param layoutId the id of the view * @return {@code true} if event is inside the specified view, {@code false} if not */ @@ -363,6 +366,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL * Check a passed MotionEvent if a click has occurred on any button on this caption * Note this should only be called when a regular onClick is not possible * (i.e. the button was clicked through status bar layer) + * * @param ev the MotionEvent to compare */ void checkClickEvent(MotionEvent ev) { @@ -399,4 +403,27 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL closeHandleMenu(); super.close(); } + + static class Factory { + + CaptionWindowDecoration create( + Context context, + DisplayController displayController, + ShellTaskOrganizer taskOrganizer, + ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl taskSurface, + Handler handler, + Choreographer choreographer, + SyncTransactionQueue syncQueue) { + return new CaptionWindowDecoration( + context, + displayController, + taskOrganizer, + taskInfo, + taskSurface, + handler, + choreographer, + syncQueue); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java index 2ce4d04377a1..907977c661f8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java @@ -28,7 +28,6 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; * servers. */ public interface WindowDecorViewModel { - /** * Sets the transition starter that starts freeform task transitions. * @@ -37,16 +36,16 @@ public interface WindowDecorViewModel { void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter); /** - * Creates a window decoration for the given task. - * Can be {@code null} for Fullscreen tasks but not Freeform ones. + * Creates a window decoration for the given task. Can be {@code null} for Fullscreen tasks but + * not Freeform ones. * - * @param taskInfo the initial task info of the task + * @param taskInfo the initial task info of the task * @param taskSurface the surface of the task - * @param startT the start transaction to be applied before the transition - * @param finishT the finish transaction to restore states after the transition + * @param startT the start transaction to be applied before the transition + * @param finishT the finish transaction to restore states after the transition * @return {@code true} if window decoration was created, {@code false} otherwise */ - boolean createWindowDecoration( + boolean onTaskOpening( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, @@ -54,7 +53,7 @@ public interface WindowDecorViewModel { /** * Notifies a task info update on the given task, with the window decoration created previously - * for this task by {@link #createWindowDecoration}. + * for this task by {@link #onTaskOpening}. * * @param taskInfo the new task info of the task */ @@ -62,13 +61,29 @@ public interface WindowDecorViewModel { /** * Notifies a transition is about to start about the given task to give the window decoration a - * chance to prepare for this transition. + * chance to prepare for this transition. Unlike {@link #onTaskInfoChanged}, this method creates + * a window decoration if one does not exist but is required. + * + * @param taskInfo the initial task info of the task + * @param taskSurface the surface of the task + * @param startT the start transaction to be applied before the transition + * @param finishT the finish transaction to restore states after the transition + */ + void onTaskChanging( + ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl taskSurface, + SurfaceControl.Transaction startT, + SurfaceControl.Transaction finishT); + + /** + * Notifies that the given task is about to close to give the window decoration a chance to + * prepare for this transition. * - * @param startT the start transaction to be applied before the transition - * @param finishT the finish transaction to restore states after the transition - * @return {@code true} if window decoration exists, {@code false} otherwise + * @param taskInfo the initial task info of the task + * @param startT the start transaction to be applied before the transition + * @param finishT the finish transaction to restore states after the transition */ - boolean setupWindowDecorationForTransition( + void onTaskClosing( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT); @@ -77,7 +92,6 @@ public interface WindowDecorViewModel { * Destroys the window decoration of the give task. * * @param taskInfo the info of the task - * @return {@code true} if window decoration was destroyed, {@code false} otherwise */ - boolean destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo); -} + void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java index 7068a84c3056..48415d47304c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java @@ -103,7 +103,7 @@ public class FreeformTaskTransitionObserverTest { mTransitionObserver.onTransitionReady(transition, info, startT, finishT); mTransitionObserver.onTransitionStarting(transition); - verify(mWindowDecorViewModel).createWindowDecoration( + verify(mWindowDecorViewModel).onTaskOpening( change.getTaskInfo(), change.getLeash(), startT, finishT); } @@ -120,7 +120,7 @@ public class FreeformTaskTransitionObserverTest { mTransitionObserver.onTransitionReady(transition, info, startT, finishT); mTransitionObserver.onTransitionStarting(transition); - verify(mWindowDecorViewModel).setupWindowDecorationForTransition( + verify(mWindowDecorViewModel).onTaskClosing( change.getTaskInfo(), startT, finishT); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java new file mode 100644 index 000000000000..8b134ed1dfe4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java @@ -0,0 +1,217 @@ +/* + * 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.windowdecor; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.hardware.input.InputManager; +import android.os.Handler; +import android.os.Looper; +import android.view.Choreographer; +import android.view.Display; +import android.view.InputChannel; +import android.view.InputMonitor; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; +import androidx.test.rule.GrantPermissionRule; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestRunningTaskInfoBuilder; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.desktopmode.DesktopModeController; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** Tests of {@link CaptionWindowDecorViewModel} */ +@SmallTest +public class CaptionWindowDecorViewModelTests extends ShellTestCase { + @Mock private CaptionWindowDecoration mCaptionWindowDecoration; + + @Mock private CaptionWindowDecoration.Factory mCaptionWindowDecorFactory; + + @Mock private Handler mMainHandler; + + @Mock private Choreographer mMainChoreographer; + + @Mock private ShellTaskOrganizer mTaskOrganizer; + + @Mock private DisplayController mDisplayController; + + @Mock private SyncTransactionQueue mSyncQueue; + + @Mock private DesktopModeController mDesktopModeController; + + @Mock private InputMonitor mInputMonitor; + + @Mock private InputChannel mInputChannel; + + @Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory; + + @Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver; + + @Mock private InputManager mInputManager; + + private final List<InputManager> mMockInputManagers = new ArrayList<>(); + + private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel; + + @Before + public void setUp() { + mMockInputManagers.add(mInputManager); + + mCaptionWindowDecorViewModel = + new CaptionWindowDecorViewModel( + mContext, + mMainHandler, + mMainChoreographer, + mTaskOrganizer, + mDisplayController, + mSyncQueue, + mDesktopModeController, + mCaptionWindowDecorFactory, + new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class))); + mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory); + + doReturn(mCaptionWindowDecoration) + .when(mCaptionWindowDecorFactory) + .create(any(), any(), any(), any(), any(), any(), any(), any()); + + when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor); + when(mEventReceiverFactory.create(any(), any())).thenReturn(mEventReceiver); + when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel); + } + + @Test + public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception { + Looper.prepare(); + final int taskId = 1; + final ActivityManager.RunningTaskInfo taskInfo = + createTaskInfo(taskId, WINDOWING_MODE_FREEFORM); + SurfaceControl surfaceControl = mock(SurfaceControl.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT); + + mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT); + verify(mCaptionWindowDecorFactory) + .create( + mContext, + mDisplayController, + mTaskOrganizer, + taskInfo, + surfaceControl, + mMainHandler, + mMainChoreographer, + mSyncQueue); + + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED); + taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); + mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT); + verify(mCaptionWindowDecoration).close(); + } + + @Test + public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception { + final int taskId = 1; + final ActivityManager.RunningTaskInfo taskInfo = + createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED); + SurfaceControl surfaceControl = mock(SurfaceControl.class); + final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); + + mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT); + + verify(mCaptionWindowDecorFactory, never()) + .create( + mContext, + mDisplayController, + mTaskOrganizer, + taskInfo, + surfaceControl, + mMainHandler, + mMainChoreographer, + mSyncQueue); + + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); + + mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT); + + verify(mCaptionWindowDecorFactory) + .create( + mContext, + mDisplayController, + mTaskOrganizer, + taskInfo, + surfaceControl, + mMainHandler, + mMainChoreographer, + mSyncQueue); + } + + private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { + ActivityManager.RunningTaskInfo taskInfo = + new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setVisible(true) + .build(); + taskInfo.taskId = taskId; + taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); + return taskInfo; + } + + private static class MockObjectSupplier<T> implements Supplier<T> { + private final List<T> mObjects; + private final Supplier<T> mDefaultSupplier; + private int mNumOfCalls = 0; + + private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) { + mObjects = objects; + mDefaultSupplier = defaultSupplier; + } + + @Override + public T get() { + final T mock = + mNumOfCalls < mObjects.size() ? mObjects.get(mNumOfCalls) + : mDefaultSupplier.get(); + ++mNumOfCalls; + return mock; + } + } +} diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 9aa37872b8de..15aaae25f754 100755..100644 --- 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 @@ std::unique_ptr<ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path, 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 3fa369d7ff4a..68f5e4a88c7e 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 @@ void AssetManager2::BuildDynamicRefTable() { // 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 @@ void AssetManager2::BuildDynamicRefTable() { 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 @@ void AssetManager2::BuildDynamicRefTable() { 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 @@ void AssetManager2::BuildDynamicRefTable() { 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,30 +202,39 @@ void AssetManager2::BuildDynamicRefTable() { 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)); + } } void AssetManager2::DumpToLog() const { @@ -317,7 +327,7 @@ const std::unordered_map<std::string, std::string>* 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 @@ bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_ 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 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) con 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 @@ base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag( 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 @@ base::expected<uint32_t, NullOrIOError> AssetManager2::GetResourceId( 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 @@ uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const 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 @@ base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, 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 @@ void Theme::Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t* 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 @@ void Theme::Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t* } 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 @@ base::expected<std::monostate, NullOrIOError> Theme::ResolveAttributeReference( } void Theme::Clear() { + keys_.clear(); entries_.clear(); } @@ -1538,18 +1554,19 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { 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(); + const auto& src_assets = source.asset_manager_->GetApkAssets(); for (size_t i = 0; i < src_assets.size(); i++) { const ApkAssets* src_asset = src_assets[i]; - const auto dest_assets = asset_manager_->GetApkAssets(); + const auto& dest_assets = asset_manager_->GetApkAssets(); for (size_t j = 0; j < dest_assets.size(); j++) { const ApkAssets* dest_asset = dest_assets[j]; if (src_asset != dest_asset) { @@ -1570,15 +1587,17 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { } 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 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { } } + 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 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { 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 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { 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 bce34d37c90b..2d3c06506a1f 100644 --- a/libs/androidfw/AssetsProvider.cpp +++ b/libs/androidfw/AssetsProvider.cpp @@ -73,9 +73,6 @@ std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFd(base::unique_fd fd, (path != nullptr) ? base::unique_fd(-1) : std::move(fd)); } -ZipAssetsProvider::PathOrDebugName::PathOrDebugName(std::string&& value, bool is_path) - : value_(std::forward<std::string>(value)), is_path_(is_path) {} - const std::string* ZipAssetsProvider::PathOrDebugName::GetPath() const { return is_path_ ? &value_ : nullptr; } @@ -84,34 +81,42 @@ const std::string& ZipAssetsProvider::PathOrDebugName::GetDebugName() const { return value_; } +void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const { + ::CloseArchive(a); +} + ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path, package_property_t flags, time_t last_mod_time) - : zip_handle_(handle, ::CloseArchive), - name_(std::forward<PathOrDebugName>(path)), + : zip_handle_(handle), + name_(std::move(path)), flags_(flags), 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. + PLOG(WARNING) << "Failed to stat file '" << path << "'"; + } } return std::unique_ptr<ZipAssetsProvider>( - new ZipAssetsProvider(handle, PathOrDebugName{std::move(path), - true /* is_path */}, flags, sb.st_mtime)); + new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime)); } std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, @@ -133,17 +138,19 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, } 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>( - new ZipAssetsProvider(handle, PathOrDebugName{std::move(friendly_name), - false /* is_path */}, flags, sb.st_mtime)); + return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider( + handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime)); } std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path, @@ -210,9 +217,9 @@ std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path, return asset; } -bool ZipAssetsProvider::ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) - const { +bool ZipAssetsProvider::ForEachFile( + const std::string& root_path, + base::function_ref<void(StringPiece, FileType)> f) const { std::string root_path_full = root_path; if (root_path_full.back() != '/') { root_path_full += '/'; @@ -238,8 +245,7 @@ bool ZipAssetsProvider::ForEachFile(const std::string& root_path, 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 +283,9 @@ const std::string& ZipAssetsProvider::GetDebugName() const { } 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 @@ -287,10 +296,10 @@ bool ZipAssetsProvider::IsUpToDate() const { } DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time) - : dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {} + : dir_(std::move(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 << "'."; @@ -302,12 +311,13 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st return nullptr; } - if (path[path.size() - 1] != OS_PATH_SEPARATOR) { + if (path.back() != OS_PATH_SEPARATOR) { 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 +334,7 @@ std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& bool DirectoryAssetsProvider::ForEachFile( const std::string& /* root_path */, - const std::function<void(const StringPiece&, FileType)>& /* f */) - const { + base::function_ref<void(StringPiece, FileType)> /* f */) const { return true; } @@ -338,7 +347,10 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const { } 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. @@ -349,8 +361,7 @@ bool DirectoryAssetsProvider::IsUpToDate() const { MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) - : primary_(std::forward<std::unique_ptr<AssetsProvider>>(primary)), - secondary_(std::forward<std::unique_ptr<AssetsProvider>>(secondary)) { + : primary_(std::move(primary)), secondary_(std::move(secondary)) { debug_name_ = primary_->GetDebugName() + " and " + secondary_->GetDebugName(); path_ = (primary_->GetDebugName() != kEmptyDebugString) ? primary_->GetPath() : secondary_->GetPath(); @@ -372,9 +383,9 @@ std::unique_ptr<Asset> MultiAssetsProvider::OpenInternal(const std::string& path return (asset) ? std::move(asset) : secondary_->Open(path, mode, file_exists); } -bool MultiAssetsProvider::ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) - const { +bool MultiAssetsProvider::ForEachFile( + const std::string& root_path, + base::function_ref<void(StringPiece, FileType)> f) const { return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f); } @@ -397,8 +408,8 @@ std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create() { 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 +423,7 @@ std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* p bool EmptyAssetsProvider::ForEachFile( const std::string& /* root_path */, - const std::function<void(const StringPiece&, FileType)>& /* f */) const { + base::function_ref<void(StringPiece, FileType)> /* f */) const { return true; } @@ -435,4 +446,4 @@ bool EmptyAssetsProvider::IsUpToDate() const { 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 19ead9583eb2..93a7d173cb97 100644 --- a/libs/androidfw/ConfigDescription.cpp +++ b/libs/androidfw/ConfigDescription.cpp @@ -637,7 +637,7 @@ static bool parseVersion(const char* name, ResTable_config* out) { 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 e122d4844ee9..89835742c8ff 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -201,7 +201,7 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const { const auto& config = configurations_[value.config_index]; values_map[config] = value.value; } - return Result(values_map); + return Result(std::move(values_map)); } return {}; } @@ -250,8 +250,7 @@ std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size } } // namespace -LoadedIdmap::LoadedIdmap(std::string&& idmap_path, - const Idmap_header* header, +LoadedIdmap::LoadedIdmap(std::string&& idmap_path, const Idmap_header* header, const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_target_entry_inline* target_inline_entries, @@ -259,23 +258,21 @@ LoadedIdmap::LoadedIdmap(std::string&& idmap_path, const ConfigDescription* configs, const Idmap_overlay_entry* overlay_entries, std::unique_ptr<ResStringPool>&& string_pool, - std::string_view overlay_apk_path, - std::string_view target_apk_path) - : header_(header), - data_header_(data_header), - target_entries_(target_entries), - target_inline_entries_(target_inline_entries), - inline_entry_values_(inline_entry_values), - configurations_(configs), - overlay_entries_(overlay_entries), - string_pool_(std::move(string_pool)), - idmap_path_(std::move(idmap_path)), - overlay_apk_path_(overlay_apk_path), - 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::string_view overlay_apk_path, std::string_view target_apk_path) + : header_(header), + data_header_(data_header), + target_entries_(target_entries), + target_inline_entries_(target_inline_entries), + inline_entry_values_(inline_entry_values), + configurations_(configs), + overlay_entries_(overlay_entries), + string_pool_(std::move(string_pool)), + idmap_path_(std::move(idmap_path)), + overlay_apk_path_(overlay_apk_path), + target_apk_path_(target_apk_path), + idmap_last_mod_time_(getFileModDate(idmap_path_.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 +362,7 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, // 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 386f718208b3..c0fdfe25da21 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -645,16 +645,16 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, // 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } 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 d87a3ce72177..272a988ec55a 100644 --- a/libs/androidfw/Locale.cpp +++ b/libs/androidfw/Locale.cpp @@ -66,7 +66,7 @@ static inline bool is_number(const std::string& str) { 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 @@ bool LocaleValue::InitFromFilterString(const StringPiece& str) { 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 267190a54195..31516dc58035 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 @@ void Res_png_9patch::serialize(const Res_png_9patch& patch, const int32_t* xDivs } bool IsFabricatedOverlay(const std::string& path) { - std::ifstream fin(path); + 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 IsFabricatedOverlay(fd); +} + +bool IsFabricatedOverlay(base::borrowed_fd fd) { uint32_t magic; - if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) { - return magic == kFabricatedOverlayMagic; + if (!base::ReadFullyAtOffset(fd, &magic, sizeof(magic), 0)) { + return false; } - return false; + return magic == kFabricatedOverlayMagic; } static bool assertIdmapHeader(const void* idmap, size_t size) { @@ -6988,11 +7001,10 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, 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 @@ void DynamicRefTable::addMapping(uint8_t buildPackageId, uint8_t runtimePackageI 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 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { 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 87fb2c038c9f..ccb61561578f 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 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin 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 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin 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 b59e906f6281..1cb8df311c89 100644 --- a/libs/androidfw/StringPool.cpp +++ b/libs/androidfw/StringPool.cpp @@ -161,16 +161,15 @@ const StringPool::Context& StringPool::StyleRef::GetContext() const { 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 @@ StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& c } 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 52ad0dce8187..be55fe8b4bb6 100644 --- a/libs/androidfw/Util.cpp +++ b/libs/androidfw/Util.cpp @@ -42,7 +42,7 @@ void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out } } -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 @@ std::u16string Utf8ToUtf16(const StringPiece& utf8) { 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 @@ std::string Utf16ToUtf8(const StringPiece16& utf16) { 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 @@ std::string Utf8ToModifiedUtf8(const std::string& utf8) { // 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 @@ std::string Utf8ToModifiedUtf8(const std::string& utf8) { 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 @@ std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8) { 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 @@ StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx) { 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 1bde792da2ba..f10cb9bf480a 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> @@ -104,7 +105,7 @@ class AssetManager2 { // new resource IDs. bool SetApkAssets(std::vector<const ApkAssets*> apk_assets, bool invalidate_caches = true); - inline const std::vector<const ApkAssets*> GetApkAssets() const { + inline const std::vector<const ApkAssets*>& GetApkAssets() const { return apk_assets_; } @@ -124,8 +125,7 @@ class AssetManager2 { 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 @@ class AssetManager2 { // 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 @@ class Theme { 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 966ec74c1786..d33c325ff369 100644 --- a/libs/androidfw/include/androidfw/AssetsProvider.h +++ b/libs/androidfw/include/androidfw/AssetsProvider.h @@ -20,6 +20,7 @@ #include <memory> #include <string> +#include "android-base/function_ref.h" #include "android-base/macros.h" #include "android-base/unique_fd.h" @@ -46,7 +47,7 @@ struct AssetsProvider { // 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; + base::function_ref<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 +81,8 @@ struct AssetsProvider { // 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 +91,7 @@ struct ZipAssetsProvider : public AssetsProvider { off64_t len = kUnknownLength); bool ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) const override; + base::function_ref<void(StringPiece, FileType)> f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; @@ -108,7 +109,12 @@ struct ZipAssetsProvider : public AssetsProvider { time_t last_mod_time); struct PathOrDebugName { - PathOrDebugName(std::string&& value, bool is_path); + static PathOrDebugName Path(std::string value) { + return {std::move(value), true}; + } + static PathOrDebugName DebugName(std::string value) { + return {std::move(value), false}; + } // Retrieves the path or null if this class represents a debug name. WARN_UNUSED const std::string* GetPath() const; @@ -117,11 +123,16 @@ struct ZipAssetsProvider : public AssetsProvider { WARN_UNUSED const std::string& GetDebugName() const; private: + PathOrDebugName(std::string value, bool is_path) : value_(std::move(value)), is_path_(is_path) { + } std::string value_; bool is_path_; }; - std::unique_ptr<ZipArchive, void (*)(ZipArchive*)> zip_handle_; + struct ZipCloser { + void operator()(ZipArchive* a) const; + }; + std::unique_ptr<ZipArchive, ZipCloser> zip_handle_; PathOrDebugName name_; package_property_t flags_; time_t last_mod_time_; @@ -132,7 +143,7 @@ struct DirectoryAssetsProvider : public AssetsProvider { 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; + base::function_ref<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 +168,7 @@ struct MultiAssetsProvider : public AssetsProvider { std::unique_ptr<AssetsProvider>&& secondary); bool ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) const override; + base::function_ref<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 +192,10 @@ struct MultiAssetsProvider : public AssetsProvider { // 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; + base::function_ref<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 949c9445b3e8..ca0a9eda9caa 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 @@ namespace android { 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 @@ class ByteBucketArray { 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 @@ class ByteBucketArray { << ") 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 @@ class ByteBucketArray { 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 61d10cd4e55b..71087cdfb6fa 100644 --- a/libs/androidfw/include/androidfw/ConfigDescription.h +++ b/libs/androidfw/include/androidfw/ConfigDescription.h @@ -72,7 +72,7 @@ struct ConfigDescription : public ResTable_config { * 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 273b05ac7689..4d5844eaa069 100644 --- a/libs/androidfw/include/androidfw/IDiagnostics.h +++ b/libs/androidfw/include/androidfw/IDiagnostics.h @@ -35,7 +35,7 @@ struct DiagMessage { 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 @@ struct DiagMessage { 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 a1cbbbf2271b..60689128dffb 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -93,8 +93,8 @@ class IdmapResMap { public: Result() = default; explicit Result(uint32_t value) : data_(value) {}; - explicit Result(const std::map<ConfigDescription, Res_value> &value) - : data_(value) { }; + explicit Result(std::map<ConfigDescription, Res_value> value) : data_(std::move(value)) { + } // Returns `true` if the resource is overlaid. explicit operator bool() const { @@ -157,8 +157,7 @@ class IdmapResMap { 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 79d962829046..4d12885ad291 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -99,8 +99,8 @@ enum : package_property_t { }; struct OverlayableInfo { - std::string name; - std::string actor; + std::string_view name; + std::string_view actor; uint32_t policy_flags; }; @@ -275,7 +275,7 @@ class LoadedPackage { 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 @@ class LoadedPackage { 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 484ed79a8efd..8934bed098fe 100644 --- a/libs/androidfw/include/androidfw/Locale.h +++ b/libs/androidfw/include/androidfw/Locale.h @@ -39,10 +39,10 @@ struct LocaleValue { /** * 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 @@ struct LocaleValue { 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 b2b95b72e0e0..52321dad8a5a 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 @@ constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3; // 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 @@ public: 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 @@ public: 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 bd1c44033b88..2d90a526dfbe 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -25,14 +25,14 @@ namespace android { // 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 0421a91d3eba..ddc9ba421101 100644 --- a/libs/androidfw/include/androidfw/Source.h +++ b/libs/androidfw/include/androidfw/Source.h @@ -34,15 +34,14 @@ struct Source { 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 fac2fa4fa575..f6cc64edfb5a 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 25174d8fe3c8..0190ab57bf23 100644 --- a/libs/androidfw/include/androidfw/StringPool.h +++ b/libs/androidfw/include/androidfw/StringPool.h @@ -167,11 +167,11 @@ class StringPool { // 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 @@ class StringPool { 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 1bbc7f5716bc..a188abb7ecb5 100644 --- a/libs/androidfw/include/androidfw/Util.h +++ b/libs/androidfw/include/androidfw/Util.h @@ -22,7 +22,6 @@ #include <cstdlib> #include <memory> -#include <sstream> #include <vector> #include "androidfw/BigBuffer.h" @@ -33,7 +32,14 @@ #ifdef __ANDROID__ #define ANDROID_LOG(x) LOG(x) #else -#define ANDROID_LOG(x) std::stringstream() +namespace android { +// No default logging for aapt2, as it's too noisy for a command line dev tool. +struct NullLogger { + template <class T> + friend const NullLogger& operator<<(const NullLogger& l, const T&) { return l; } +}; +} +#define ANDROID_LOG(x) (android::NullLogger{}) #endif namespace android { @@ -49,90 +55,28 @@ std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T{std::forward<Args>(args)...}); } -// Based on std::unique_ptr, but uses free() to release malloc'ed memory -// without incurring the size increase of holding on to a custom deleter. -template <typename T> -class unique_cptr { - public: - using pointer = typename std::add_pointer<T>::type; - - constexpr unique_cptr() : ptr_(nullptr) {} - constexpr explicit unique_cptr(std::nullptr_t) : ptr_(nullptr) {} - explicit unique_cptr(pointer ptr) : ptr_(ptr) {} - unique_cptr(unique_cptr&& o) noexcept : ptr_(o.ptr_) { o.ptr_ = nullptr; } - - ~unique_cptr() { std::free(reinterpret_cast<void*>(ptr_)); } - - inline unique_cptr& operator=(unique_cptr&& o) noexcept { - if (&o == this) { - return *this; - } - - std::free(reinterpret_cast<void*>(ptr_)); - ptr_ = o.ptr_; - o.ptr_ = nullptr; - return *this; - } - - inline unique_cptr& operator=(std::nullptr_t) { - std::free(reinterpret_cast<void*>(ptr_)); - ptr_ = nullptr; - return *this; - } - - pointer release() { - pointer result = ptr_; - ptr_ = nullptr; - return result; +// Based on std::unique_ptr, but uses free() to release malloc'ed memory. +struct FreeDeleter { + void operator()(void* ptr) const { + ::free(ptr); } - - inline pointer get() const { return ptr_; } - - void reset(pointer ptr = pointer()) { - if (ptr == ptr_) { - return; - } - - pointer old_ptr = ptr_; - ptr_ = ptr; - std::free(reinterpret_cast<void*>(old_ptr)); - } - - inline void swap(unique_cptr& o) { std::swap(ptr_, o.ptr_); } - - inline explicit operator bool() const { return ptr_ != nullptr; } - - inline typename std::add_lvalue_reference<T>::type operator*() const { return *ptr_; } - - inline pointer operator->() const { return ptr_; } - - inline bool operator==(const unique_cptr& o) const { return ptr_ == o.ptr_; } - - inline bool operator!=(const unique_cptr& o) const { return ptr_ != o.ptr_; } - - inline bool operator==(std::nullptr_t) const { return ptr_ == nullptr; } - - inline bool operator!=(std::nullptr_t) const { return ptr_ != nullptr; } - - private: - DISALLOW_COPY_AND_ASSIGN(unique_cptr); - - pointer ptr_; }; +template <typename T> +using unique_cptr = std::unique_ptr<T, FreeDeleter>; 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,15 +94,15 @@ inline uint32_t DeviceToHost32(uint32_t value) { 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) { - return ((size_t)data.unsafe_ptr() & 0x3U) == 0; +inline bool IsFourByteAligned(const void* data) { + return ((uintptr_t)data & 0x3U) == 0; } -inline bool IsFourByteAligned(const void* data) { - return ((size_t)data & 0x3U) == 0; +template <typename T> +inline bool IsFourByteAligned(const incfs::map_ptr<T>& data) { + return IsFourByteAligned(data.unsafe_ptr()); } // Helper method to extract a UTF-16 string from a StringPool. If the string is stored as UTF-8, diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h index 5a5a0e29125d..d40d24ede769 100644 --- a/libs/androidfw/include/androidfw/misc.h +++ b/libs/androidfw/include/androidfw/misc.h @@ -44,6 +44,10 @@ FileType getFileType(const char* fileName); /* 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 52854205207c..d3949e9cf69f 100644 --- a/libs/androidfw/misc.cpp +++ b/libs/androidfw/misc.cpp @@ -21,12 +21,17 @@ // #include <androidfw/misc.h> -#include <sys/stat.h> +#include "android-base/logging.h" + +#ifdef __linux__ +#include <sys/statvfs.h> +#include <sys/vfs.h> +#endif // __linux__ + #include <cstring> -#include <errno.h> #include <cstdio> - -using namespace android; +#include <errno.h> +#include <sys/stat.h> namespace android { @@ -41,8 +46,7 @@ FileType getFileType(const char* fileName) 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 @@ time_t getFileModDate(const char* fileName) return sb.st_mtime; } +#ifndef __linux__ +// No need to implement these on the host, the functions only matter on a device. +bool isReadonlyFilesystem(const char*) { + return false; +} +bool isReadonlyFilesystem(int) { + return false; +} +#else // __linux__ +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 // __linux__ + }; // namespace android diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp index ddd8ab820cb1..1c89c61c8f78 100644 --- a/libs/androidfw/tests/AttributeResolution_bench.cpp +++ b/libs/androidfw/tests/AttributeResolution_bench.cpp @@ -120,8 +120,8 @@ static void BM_ApplyStyleFramework(benchmark::State& state) { 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 5d464c7dc0f7..9c36cfb212c5 100644 --- a/libs/androidfw/tests/ByteBucketArray_test.cpp +++ b/libs/androidfw/tests/ByteBucketArray_test.cpp @@ -52,4 +52,57 @@ TEST(ByteBucketArrayTest, TestSparseInsertion) { } } +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 ce7f8054e2ca..8fed0a4d22fc 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 @@ TEST(ConfigDescriptionTest, ParseVrAttribute) { 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 316a5c1bf40e..822e527253df 100644 --- a/libs/androidfw/tests/StringPiece_test.cpp +++ b/libs/androidfw/tests/StringPiece_test.cpp @@ -60,36 +60,4 @@ TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) { 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 047d45785409..0e0acae165d9 100644 --- a/libs/androidfw/tests/StringPool_test.cpp +++ b/libs/androidfw/tests/StringPool_test.cpp @@ -321,15 +321,15 @@ TEST(StringPoolTest, ModifiedUTF8) { 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 c0a4fdf5eb74..88cfed9357d8 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -589,6 +589,7 @@ cc_defaults { "ProfileData.cpp", "ProfileDataContainer.cpp", "Readback.cpp", + "Tonemapper.cpp", "TreeInfo.cpp", "WebViewFunctorManager.cpp", "protos/graphicsstats.proto", diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp index 673041a661ce..cd4fae86aa52 100644 --- a/libs/hwui/CanvasTransform.cpp +++ b/libs/hwui/CanvasTransform.cpp @@ -17,6 +17,7 @@ #include "CanvasTransform.h" #include <SkAndroidFrameworkUtils.h> +#include <SkBlendMode.h> #include <SkColorFilter.h> #include <SkGradientShader.h> #include <SkHighContrastFilter.h> diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h index 9a4c5505fa35..a7f8f6189a8e 100644 --- a/libs/hwui/DeferredLayerUpdater.h +++ b/libs/hwui/DeferredLayerUpdater.h @@ -16,6 +16,7 @@ #pragma once +#include <SkBlendMode.h> #include <SkColorFilter.h> #include <SkImage.h> #include <SkMatrix.h> diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 9053c1240957..fc3118ae32dd 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -20,6 +20,8 @@ #include "utils/Color.h" #include "utils/MathUtils.h" +#include <SkBlendMode.h> + #include <log/log.h> namespace android { diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index 02c2e67a319b..8dcd6dbe6421 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 @@ void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<Copy } } - 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 @@ void Readback::copySurfaceInto(ANativeWindow* window, const std::shared_ptr<Copy 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/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index f5ebfd5d9e23..f070e97dff2a 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -22,6 +22,7 @@ #include <experimental/type_traits> #include "SkAndroidFrameworkUtils.h" +#include "SkBlendMode.h" #include "SkCanvas.h" #include "SkCanvasPriv.h" #include "SkColor.h" diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 35bec9335d7c..f37729ebb59c 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -34,6 +34,7 @@ #include <SkRuntimeEffect.h> #include <vector> +enum class SkBlendMode; class SkRRect; namespace android { diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 51007c52260d..eece77e061b6 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -33,6 +33,7 @@ #include <cassert> #include <optional> +enum class SkBlendMode; class SkRRect; namespace android { diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp index 0695dd1ab218..153c3b6f7e04 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 000000000000..a7e76b631140 --- /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 000000000000..c0d5325fa9f8 --- /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/apex/android_paint.cpp b/libs/hwui/apex/android_paint.cpp index 70bd085343ce..cc79cba5e19c 100644 --- a/libs/hwui/apex/android_paint.cpp +++ b/libs/hwui/apex/android_paint.cpp @@ -19,6 +19,7 @@ #include "TypeCast.h" #include <hwui/Paint.h> +#include <SkBlendMode.h> using namespace android; diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 82d23b51b12a..4608088a7cd9 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -30,6 +30,7 @@ #include <SkMatrix.h> class SkAnimatedImage; +enum class SkBlendMode; class SkCanvasState; class SkRRect; class SkRuntimeShaderBuilder; diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp index cef21f91f3c1..4bd7ef47b871 100644 --- a/libs/hwui/jni/ColorFilter.cpp +++ b/libs/hwui/jni/ColorFilter.cpp @@ -17,6 +17,7 @@ #include "GraphicsJNI.h" +#include "SkBlendMode.h" #include "SkColorFilter.h" #include "SkColorMatrixFilter.h" diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp index 213f35a81b88..f3db1705e694 100644 --- a/libs/hwui/jni/RenderEffect.cpp +++ b/libs/hwui/jni/RenderEffect.cpp @@ -15,6 +15,7 @@ */ #include "Bitmap.h" #include "GraphicsJNI.h" +#include "SkBlendMode.h" #include "SkImageFilter.h" #include "SkImageFilters.h" #include "graphics_jni_helpers.h" diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp index 3ba540921f64..99f54c19d2e5 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 @@ static bool shouldFilterRect(const SkMatrix& matrix, const SkRect& srcRect, cons 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 @@ bool LayerDrawable::DrawLayer(GrRecordingContext* context, 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/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h index 6c390c3fce24..c7582e734009 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h @@ -18,6 +18,7 @@ #include "SkiaUtils.h" +#include <SkBlendMode.h> #include <SkCanvas.h> #include <SkDrawable.h> #include <SkMatrix.h> diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp index 2dbeb3adfab3..b169c9200e88 100644 --- a/libs/hwui/pipeline/skia/StretchMask.cpp +++ b/libs/hwui/pipeline/skia/StretchMask.cpp @@ -14,8 +14,10 @@ * limitations under the License. */ #include "StretchMask.h" -#include "SkSurface.h" + +#include "SkBlendMode.h" #include "SkCanvas.h" +#include "SkSurface.h" #include "TransformCanvas.h" #include "SkiaDisplayList.h" diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index 3c7617d35c7c..e168a7b9459a 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -33,6 +33,8 @@ #include "thread/ThreadBase.h" #include "utils/TimeUtils.h" +#include <SkBlendMode.h> + namespace android { namespace uirenderer { namespace skiapipeline { diff --git a/libs/hwui/tests/common/CallCountingCanvas.h b/libs/hwui/tests/common/CallCountingCanvas.h index d3c41191eef1..dc36a2e01815 100644 --- a/libs/hwui/tests/common/CallCountingCanvas.h +++ b/libs/hwui/tests/common/CallCountingCanvas.h @@ -19,6 +19,8 @@ #include <SkCanvasVirtualEnforcer.h> #include <SkNoDrawCanvas.h> +enum class SkBlendMode; + namespace android { namespace uirenderer { namespace test { diff --git a/libs/hwui/tests/common/TestListViewSceneBase.cpp b/libs/hwui/tests/common/TestListViewSceneBase.cpp index 43df4a0b1576..e70d44c9c60a 100644 --- a/libs/hwui/tests/common/TestListViewSceneBase.cpp +++ b/libs/hwui/tests/common/TestListViewSceneBase.cpp @@ -19,6 +19,8 @@ #include "TestContext.h" #include "TestUtils.h" +#include <SkBlendMode.h> + #include <utils/Color.h> namespace android { diff --git a/libs/hwui/tests/common/scenes/BitmapFillrate.cpp b/libs/hwui/tests/common/scenes/BitmapFillrate.cpp index 5af7d43d7f66..19e87f851827 100644 --- a/libs/hwui/tests/common/scenes/BitmapFillrate.cpp +++ b/libs/hwui/tests/common/scenes/BitmapFillrate.cpp @@ -19,6 +19,7 @@ #include "utils/Color.h" #include <SkBitmap.h> +#include <SkBlendMode.h> using namespace android; using namespace android::uirenderer; diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp index 2a016ac1b5bc..3a1ea8c29963 100644 --- a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class ClippingAnimation; static TestScene::Registrar _RectGrid(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp index 4271d2f04b88..484289a8ef1d 100644 --- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp +++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp @@ -20,6 +20,8 @@ #include <hwui/Paint.h> #include <minikin/Layout.h> +#include <SkBlendMode.h> + #include <cstdio> class GlyphStressAnimation; diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp index 0d5ca6df9ff3..dfdd0d8727b9 100644 --- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp +++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp @@ -17,6 +17,7 @@ #include "TestSceneBase.h" #include "utils/Color.h" +#include <SkBlendMode.h> #include <SkColorSpace.h> #include <SkGradientShader.h> #include <SkImagePriv.h> diff --git a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp index cac2fb3d8d5c..2955fb25ec2c 100644 --- a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp +++ b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class HwLayerAnimation; static TestScene::Registrar _HwLayer(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp index 77a59dfe6ba5..8c9a6147f47d 100644 --- a/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp +++ b/libs/hwui/tests/common/scenes/HwLayerSizeAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class HwLayerSizeAnimation; static TestScene::Registrar _HwLayerSize(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/JankyScene.cpp b/libs/hwui/tests/common/scenes/JankyScene.cpp index f5e6b317529a..250b986e7e73 100644 --- a/libs/hwui/tests/common/scenes/JankyScene.cpp +++ b/libs/hwui/tests/common/scenes/JankyScene.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + #include <unistd.h> class JankyScene; diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp index 5eaf1853233a..f669dbc9323e 100644 --- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp @@ -17,6 +17,7 @@ #include "TestSceneBase.h" #include "tests/common/TestListViewSceneBase.h" #include "hwui/Paint.h" +#include <SkBlendMode.h> #include <SkGradientShader.h> class ListOfFadedTextAnimation; diff --git a/libs/hwui/tests/common/scenes/OvalAnimation.cpp b/libs/hwui/tests/common/scenes/OvalAnimation.cpp index 402c1ece2146..1a2af8382ad7 100644 --- a/libs/hwui/tests/common/scenes/OvalAnimation.cpp +++ b/libs/hwui/tests/common/scenes/OvalAnimation.cpp @@ -17,6 +17,8 @@ #include "TestSceneBase.h" #include "utils/Color.h" +#include <SkBlendMode.h> + class OvalAnimation; static TestScene::Registrar _Oval(TestScene::Info{"oval", "Draws 1 oval.", diff --git a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp index fb1b000a995e..25cf4d61bf9d 100644 --- a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp +++ b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class PartialDamageAnimation; static TestScene::Registrar _PartialDamage(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp index 1e343c1dd283..969514c50d14 100644 --- a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp +++ b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp @@ -16,6 +16,8 @@ #include <vector> +#include <SkBlendMode.h> + #include "TestSceneBase.h" class PathClippingAnimation : public TestScene { diff --git a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp index f37bcbc3ee1b..99e785887b16 100644 --- a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp +++ b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class RectGridAnimation; static TestScene::Registrar _RectGrid(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp index e9f353d887f2..2c27969487d3 100644 --- a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp +++ b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + #include <vector> class RoundRectClippingAnimation : public TestScene { diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp index 252f539ffca9..ee30c131efbd 100644 --- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp @@ -16,6 +16,7 @@ #include <hwui/Paint.h> #include <minikin/Layout.h> +#include <SkBlendMode.h> #include <string> #include "TestSceneBase.h" diff --git a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp index 31a8ae1d38cd..d5060c758f93 100644 --- a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp +++ b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class SaveLayerAnimation; static TestScene::Registrar _SaveLayer(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp index c13e80e8c204..827ddab118d9 100644 --- a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp +++ b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class ShadowGrid2Animation; static TestScene::Registrar _ShadowGrid2(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp index 772b98e32220..a4fb10c5081e 100644 --- a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class ShadowGridAnimation; static TestScene::Registrar _ShadowGrid(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp index 0019da5fd80b..58c03727bc29 100644 --- a/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp @@ -16,6 +16,8 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> + class ShadowShaderAnimation; static TestScene::Registrar _ShadowShader(TestScene::Info{ diff --git a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp index 70a1557dcf6a..c0c3dfd9a8c4 100644 --- a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp @@ -17,6 +17,8 @@ #include "TestSceneBase.h" #include "utils/Color.h" +#include <SkBlendMode.h> + #include <cstdio> class ShapeAnimation; diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp index 2aeb42cc0e20..40f2ed081626 100644 --- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp +++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp @@ -16,6 +16,7 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> #include <SkColorFilter.h> #include <SkColorMatrix.h> #include <SkGradientShader.h> diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp index 57a260c8d234..a9e7a34b5b3f 100644 --- a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp +++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp @@ -16,6 +16,7 @@ #include "TestSceneBase.h" +#include <SkBlendMode.h> #include <SkGradientShader.h> class SimpleGradientAnimation; diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp index 7d3ca9642458..bb95490c1d39 100644 --- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp @@ -15,6 +15,7 @@ */ #include <SkBitmap.h> +#include <SkBlendMode.h> #include <SkCanvas.h> #include <SkColor.h> #include <SkFont.h> diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp index d30903679bce..78146b8cabf2 100644 --- a/libs/hwui/tests/common/scenes/TextAnimation.cpp +++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp @@ -17,6 +17,8 @@ #include "TestSceneBase.h" #include "hwui/Paint.h" +#include <SkBlendMode.h> + class TextAnimation; static TestScene::Registrar _Text(TestScene::Info{"text", "Draws a bunch of text.", diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp index 9cd10759a834..a55b72534924 100644 --- a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp +++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp @@ -22,6 +22,8 @@ #include "pipeline/skia/SkiaDisplayList.h" #include "tests/common/TestUtils.h" +#include <SkBlendMode.h> + using namespace android; using namespace android::uirenderer; using namespace android::uirenderer::skiapipeline; diff --git a/libs/hwui/tests/microbench/RenderNodeBench.cpp b/libs/hwui/tests/microbench/RenderNodeBench.cpp index 6aed251481bf..72946c4abdf0 100644 --- a/libs/hwui/tests/microbench/RenderNodeBench.cpp +++ b/libs/hwui/tests/microbench/RenderNodeBench.cpp @@ -19,6 +19,8 @@ #include "hwui/Canvas.h" #include "RenderNode.h" +#include <SkBlendMode.h> + using namespace android; using namespace android::uirenderer; diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp index d2b1ef91a898..1f6edf36af25 100644 --- a/libs/hwui/tests/unit/CanvasOpTests.cpp +++ b/libs/hwui/tests/unit/CanvasOpTests.cpp @@ -23,6 +23,7 @@ #include <tests/common/CallCountingCanvas.h> +#include "SkBlendMode.h" #include "SkBitmap.h" #include "SkCanvas.h" #include "SkColor.h" diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index ec949b80ea55..3caba2d410bd 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -17,6 +17,7 @@ #include <VectorDrawable.h> #include <gtest/gtest.h> +#include <SkBlendMode.h> #include <SkClipStack.h> #include <SkSurface_Base.h> #include <string.h> diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp index 50d9f5683a8b..87c52161d68e 100644 --- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp +++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp @@ -17,10 +17,19 @@ #include "tests/common/TestUtils.h" #include <hwui/Paint.h> +#include <SkAlphaType.h> +#include <SkBitmap.h> +#include <SkBlendMode.h> +#include <SkCanvas.h> #include <SkCanvasStateUtils.h> +#include <SkColor.h> #include <SkColorSpace.h> +#include <SkColorType.h> +#include <SkImageInfo.h> #include <SkPicture.h> #include <SkPictureRecorder.h> +#include <SkRefCnt.h> +#include <SkSurface.h> #include <gtest/gtest.h> using namespace android; diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 7419f8fd89f1..4d0595e03da6 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -17,6 +17,7 @@ #include <VectorDrawable.h> #include <gtest/gtest.h> +#include <SkBlendMode.h> #include <SkClipStack.h> #include <SkSurface_Base.h> #include <string.h> diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h index 94bcb1110e05..f44f9d0fe2d4 100644 --- a/libs/hwui/utils/PaintUtils.h +++ b/libs/hwui/utils/PaintUtils.h @@ -19,6 +19,7 @@ #include <GLES2/gl2.h> #include <utils/Blur.h> +#include <SkBlendMode.h> #include <SkColorFilter.h> #include <SkPaint.h> #include <SkShader.h> diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java index 529edddf4cf0..5acec79a2d3b 100644 --- a/location/java/android/location/provider/LocationProviderBase.java +++ b/location/java/android/location/provider/LocationProviderBase.java @@ -101,6 +101,15 @@ public abstract class LocationProviderBase { 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/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java index 15811d2c4f11..9144087058a7 100644 --- a/media/java/android/media/tv/tuner/filter/AvSettings.java +++ b/media/java/android/media/tv/tuner/filter/AvSettings.java @@ -38,7 +38,8 @@ public class AvSettings extends Settings { VIDEO_STREAM_TYPE_MPEG1, VIDEO_STREAM_TYPE_MPEG2, VIDEO_STREAM_TYPE_MPEG4P2, VIDEO_STREAM_TYPE_AVC, VIDEO_STREAM_TYPE_HEVC, VIDEO_STREAM_TYPE_VC1, VIDEO_STREAM_TYPE_VP8, VIDEO_STREAM_TYPE_VP9, - VIDEO_STREAM_TYPE_AV1, VIDEO_STREAM_TYPE_AVS, VIDEO_STREAM_TYPE_AVS2}) + VIDEO_STREAM_TYPE_AV1, VIDEO_STREAM_TYPE_AVS, VIDEO_STREAM_TYPE_AVS2, + VIDEO_STREAM_TYPE_VVC}) @Retention(RetentionPolicy.SOURCE) public @interface VideoStreamType {} @@ -76,6 +77,10 @@ public class AvSettings extends Settings { */ public static final int VIDEO_STREAM_TYPE_HEVC = android.hardware.tv.tuner.VideoStreamType.HEVC; /* + * ITU-T Rec. H.266 and ISO/IEC 23090-3 + */ + public static final int VIDEO_STREAM_TYPE_VVC = android.hardware.tv.tuner.VideoStreamType.VVC; + /* * Microsoft VC.1 */ public static final int VIDEO_STREAM_TYPE_VC1 = android.hardware.tv.tuner.VideoStreamType.VC1; diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java index d0973f4eb11f..8568c4349dc4 100644 --- a/media/java/android/media/tv/tuner/filter/Filter.java +++ b/media/java/android/media/tv/tuner/filter/Filter.java @@ -349,6 +349,15 @@ public class Filter implements AutoCloseable { + mMainType + ", filter subtype=" + mSubtype + ". config main type=" + config.getType() + ", config subtype=" + subType); } + // Tuner only support VVC after tuner 3.0 + if (s instanceof RecordSettings + && ((RecordSettings) s).getScIndexType() == RecordSettings.INDEX_TYPE_SC_VVC + && !TunerVersionChecker.isHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_3_0)) { + Log.e(TAG, "Tuner version " + TunerVersionChecker.getTunerVersion() + + " does not support VVC"); + return Tuner.RESULT_UNAVAILABLE; + } return nativeConfigureFilter(config.getType(), subType, config); } } diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java index b16d9fb247b7..698bbba2798e 100644 --- a/media/java/android/media/tv/tuner/filter/RecordSettings.java +++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java @@ -23,8 +23,10 @@ import android.hardware.tv.tuner.DemuxRecordScIndexType; import android.hardware.tv.tuner.DemuxScAvcIndex; import android.hardware.tv.tuner.DemuxScHevcIndex; import android.hardware.tv.tuner.DemuxScIndex; +import android.hardware.tv.tuner.DemuxScVvcIndex; import android.hardware.tv.tuner.DemuxTsIndex; import android.media.tv.tuner.TunerUtils; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -138,7 +140,8 @@ public class RecordSettings extends Settings { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "INDEX_TYPE_", value = - {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC, INDEX_TYPE_SC_AVC}) + {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC, INDEX_TYPE_SC_AVC, + INDEX_TYPE_SC_VVC}) public @interface ScIndexType {} /** @@ -157,6 +160,10 @@ public class RecordSettings extends Settings { * Start Code index for AVC. */ public static final int INDEX_TYPE_SC_AVC = DemuxRecordScIndexType.SC_AVC; + /** + * Start Code index for VVC. + */ + public static final int INDEX_TYPE_SC_VVC = DemuxRecordScIndexType.SC_VVC; /** * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream) @@ -253,6 +260,46 @@ public class RecordSettings extends Settings { public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA = DemuxScHevcIndex.SLICE_TRAIL_CRA; /** + * Indexes can be tagged by NAL unit group in VVC according to ISO/IEC 23090-3. + * + * @hide + */ + @IntDef(value = {SC_VVC_INDEX_SLICE_IDR_W_RADL, SC_VVC_INDEX_SLICE_IDR_N_LP, + SC_VVC_INDEX_SLICE_CRA, SC_VVC_INDEX_SLICE_GDR, SC_VVC_INDEX_VPS, SC_VVC_INDEX_SPS, + SC_VVC_INDEX_AUD}) + @Retention(RetentionPolicy.SOURCE) + public @interface ScVvcIndex{} + + /** + * SC VVC index SLICE_IDR_W_RADL (nal_unit_type=IDR_W_RADL) for random access key frame. + */ + public static final int SC_VVC_INDEX_SLICE_IDR_W_RADL = DemuxScVvcIndex.SLICE_IDR_W_RADL; + /** + * SC VVC index SLICE_IDR_N_LP (nal_unit_type=IDR_N_LP) for random access key frame. + */ + public static final int SC_VVC_INDEX_SLICE_IDR_N_LP = DemuxScVvcIndex.SLICE_IDR_N_LP; + /** + * SC VVC index SLICE_CRA (nal_unit_type=CRA_NUT) for random access key frame. + */ + public static final int SC_VVC_INDEX_SLICE_CRA = DemuxScVvcIndex.SLICE_CRA; + /** + * SC VVC index SLICE_GDR (nal_unit_type=GDR_NUT) for random access point. + */ + public static final int SC_VVC_INDEX_SLICE_GDR = DemuxScVvcIndex.SLICE_GDR; + /** + * Optional SC VVC index VPS (nal_unit_type=VPS_NUT) for sequence level info. + */ + public static final int SC_VVC_INDEX_VPS = DemuxScVvcIndex.VPS; + /** + * SC VVC index SPS (nal_unit_type=SPS_NUT) for sequence level info. + */ + public static final int SC_VVC_INDEX_SPS = DemuxScVvcIndex.SPS; + /** + * SC VVC index AUD (nal_unit_type=AUD_NUT) for AU (frame) boundary. + */ + public static final int SC_VVC_INDEX_AUD = DemuxScVvcIndex.AUD; + + /** * @hide */ @IntDef(prefix = "SC_", @@ -261,6 +308,11 @@ public class RecordSettings extends Settings { SC_INDEX_P_FRAME, SC_INDEX_B_FRAME, SC_INDEX_SEQUENCE, + SC_INDEX_I_SLICE, + SC_INDEX_P_SLICE, + SC_INDEX_B_SLICE, + SC_INDEX_SI_SLICE, + SC_INDEX_SP_SLICE, SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP, @@ -269,6 +321,13 @@ public class RecordSettings extends Settings { SC_HEVC_INDEX_SLICE_IDR_W_RADL, SC_HEVC_INDEX_SLICE_IDR_N_LP, SC_HEVC_INDEX_SLICE_TRAIL_CRA, + SC_VVC_INDEX_SLICE_IDR_W_RADL, + SC_VVC_INDEX_SLICE_IDR_N_LP, + SC_VVC_INDEX_SLICE_CRA, + SC_VVC_INDEX_SLICE_GDR, + SC_VVC_INDEX_VPS, + SC_VVC_INDEX_SPS, + SC_VVC_INDEX_AUD }) @Retention(RetentionPolicy.SOURCE) public @interface ScIndexMask {} diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 2afa4d1fdefe..58078cf39227 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -661,6 +661,8 @@ void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size, sc = mediaEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scAvc>(); // Java uses the values defined by HIDL HAL. Left shift 4 bits. sc = sc << 4; + } else if (mediaEvent.scIndexMask.getTag() == DemuxFilterScIndexMask::Tag::scVvc) { + sc = mediaEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scVvc>(); } jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, isDtsPresent, @@ -726,6 +728,8 @@ void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int siz sc = tsRecordEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scAvc>(); // Java uses the values defined by HIDL HAL. Left shift 4 bits. sc = sc << 4; + } else if (tsRecordEvent.scIndexMask.getTag() == DemuxFilterScIndexMask::Tag::scVvc) { + sc = tsRecordEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scVvc>(); } jint ts = tsRecordEvent.tsIndexMask; @@ -3819,6 +3823,8 @@ static DemuxFilterRecordSettings getFilterRecordSettings(JNIEnv *env, const jobj } else if (scIndexType == DemuxRecordScIndexType::SC_AVC) { // Java uses the values defined by HIDL HAL. Right shift 4 bits. filterRecordSettings.scIndexMask.set<DemuxFilterScIndexMask::Tag::scAvc>(scIndexMask >> 4); + } else if (scIndexType == DemuxRecordScIndexType::SC_VVC) { + filterRecordSettings.scIndexMask.set<DemuxFilterScIndexMask::Tag::scVvc>(scIndexMask); } return filterRecordSettings; } diff --git a/native/android/Android.bp b/native/android/Android.bp index 8594ba5ca2da..f1b1d79265de 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -34,6 +34,7 @@ ndk_library { 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 7863a7dba1a7..40eb507a5213 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -71,8 +71,10 @@ private: 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 @@ APerformanceHintSession::APerformanceHintSession(sp<IHintSession> session, : 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 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano */ mActualDurationsNanos.clear(); mTimestampsNanos.clear(); - mLastUpdateTimestamp = elapsedRealtimeNano(); + mFirstTargetMetTimestamp = 0; + mLastTargetMetTimestamp = 0; return 0; } @@ -184,25 +188,38 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano 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 30d0c35bcbb0..fe3132e3d2a3 100644 --- a/native/android/system_fonts.cpp +++ b/native/android/system_fonts.cpp @@ -66,9 +66,6 @@ struct AFont { 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-af/strings.xml b/packages/CredentialManager/res/values-af/strings.xml new file mode 100644 index 000000000000..91771b3ab287 --- /dev/null +++ b/packages/CredentialManager/res/values-af/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Kanselleer"</string> + <string name="string_continue" msgid="1346732695941131882">"Gaan voort"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Skep op ’n ander plek"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Stoor in ’n ander plek"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Gebruik ’n ander toestel"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Stoor op ’n ander toestel"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"’n Maklike manier om veilig aan te meld"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gebruik jou vingerafdruk, gesig of skermslot om aan te meld met ’n unieke wagwoordsleutel wat nie vergeet of gesteel kan word nie. Kom meer te wete"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Kies waar om <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"stoor jou wagwoord"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"stoor jou aanmeldinligting"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Skep ’n wagwoordsleutel in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Stoor jou wagwoord in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Stoor jou aanmeldinligting in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Jy kan jou <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> op enige toestel gebruik. Dit is in <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> gestoor vir <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"wagwoordsleutel"</string> + <string name="password" msgid="6738570945182936667">"wagwoord"</string> + <string name="sign_ins" msgid="4710739369149469208">"aanmeldings"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gebruik <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> vir al jou aanmeldings?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Stel as verstek"</string> + <string name="use_once" msgid="9027366575315399714">"Gebruik een keer"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> wagwoordsleutels"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> wagwoordsleutels"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"’n Ander toestel"</string> + <string name="other_password_manager" msgid="565790221427004141">"Ander wagwoordbestuurders"</string> + <string name="close_sheet" msgid="1393792015338908262">"Maak sigblad toe"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gaan terug na die vorige bladsy"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gebruik jou gestoorde wagwoordsleutel vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gebruik jou gestoorde aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Kies ’n gestoorde aanmelding vir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Meld op ’n ander manier aan"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nee, dankie"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Gaan voort"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Aanmeldopsies"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Vir <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Geslote wagwoordbestuurders"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tik om te ontsluit"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Bestuur aanmeldings"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Van ’n ander toestel af"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gebruik ’n ander toestel"</string> +</resources> diff --git a/packages/CredentialManager/res/values-am/strings.xml b/packages/CredentialManager/res/values-am/strings.xml new file mode 100644 index 000000000000..e77b1a788ea4 --- /dev/null +++ b/packages/CredentialManager/res/values-am/strings.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <!-- no translation found for string_cancel (6369133483981306063) --> + <skip /> + <!-- no translation found for string_continue (1346732695941131882) --> + <skip /> + <!-- no translation found for string_create_in_another_place (1033635365843437603) --> + <skip /> + <!-- no translation found for string_save_to_another_place (7590325934591079193) --> + <skip /> + <!-- no translation found for string_use_another_device (8754514926121520445) --> + <skip /> + <string name="string_save_to_another_device" msgid="1959562542075194458">"ወደ ሌላ መሣሪያ ያስቀምጡ"</string> + <!-- no translation found for passkey_creation_intro_title (402553911484409884) --> + <skip /> + <!-- no translation found for passkey_creation_intro_body (7493320456005579290) --> + <skip /> + <!-- no translation found for choose_provider_title (7245243990139698508) --> + <skip /> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <!-- no translation found for save_your_password (6597736507991704307) --> + <skip /> + <!-- no translation found for save_your_sign_in_info (7213978049817076882) --> + <skip /> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <!-- no translation found for choose_create_option_passkey_title (4146408187146573131) --> + <skip /> + <!-- no translation found for choose_create_option_password_title (8812546498357380545) --> + <skip /> + <!-- no translation found for choose_create_option_sign_in_title (6318246378475961834) --> + <skip /> + <!-- no translation found for choose_create_option_description (4419171903963100257) --> + <skip /> + <!-- no translation found for passkey (632353688396759522) --> + <skip /> + <!-- no translation found for password (6738570945182936667) --> + <skip /> + <!-- no translation found for sign_ins (4710739369149469208) --> + <skip /> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <!-- no translation found for use_provider_for_all_title (4201020195058980757) --> + <skip /> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <!-- no translation found for set_as_default (4415328591568654603) --> + <skip /> + <!-- no translation found for use_once (9027366575315399714) --> + <skip /> + <!-- no translation found for more_options_usage_passwords_passkeys (4794903978126339473) --> + <skip /> + <!-- no translation found for more_options_usage_passwords (1632047277723187813) --> + <skip /> + <!-- no translation found for more_options_usage_passkeys (5390320437243042237) --> + <skip /> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <!-- no translation found for another_device (5147276802037801217) --> + <skip /> + <!-- no translation found for other_password_manager (565790221427004141) --> + <skip /> + <!-- no translation found for close_sheet (1393792015338908262) --> + <skip /> + <!-- no translation found for accessibility_back_arrow_button (3233198183497842492) --> + <skip /> + <!-- no translation found for get_dialog_title_use_passkey_for (6236608872708021767) --> + <skip /> + <!-- no translation found for get_dialog_title_use_sign_in_for (5283099528915572980) --> + <skip /> + <!-- no translation found for get_dialog_title_choose_sign_in_for (1361715440877613701) --> + <skip /> + <!-- no translation found for get_dialog_use_saved_passkey_for (4618100798664888512) --> + <skip /> + <!-- no translation found for get_dialog_button_label_no_thanks (8114363019023838533) --> + <skip /> + <!-- no translation found for get_dialog_button_label_continue (6446201694794283870) --> + <skip /> + <!-- no translation found for get_dialog_title_sign_in_options (2092876443114893618) --> + <skip /> + <!-- no translation found for get_dialog_heading_for_username (3456868514554204776) --> + <skip /> + <!-- no translation found for get_dialog_heading_locked_password_managers (8911514851762862180) --> + <skip /> + <!-- no translation found for locked_credential_entry_label_subtext (9213450912991988691) --> + <skip /> + <!-- no translation found for get_dialog_heading_manage_sign_ins (3522556476480676782) --> + <skip /> + <!-- no translation found for get_dialog_heading_from_another_device (1166697017046724072) --> + <skip /> + <!-- no translation found for get_dialog_option_headline_use_a_different_device (8201578814988047549) --> + <skip /> +</resources> diff --git a/packages/CredentialManager/res/values-ar/strings.xml b/packages/CredentialManager/res/values-ar/strings.xml new file mode 100644 index 000000000000..4af875ce3519 --- /dev/null +++ b/packages/CredentialManager/res/values-ar/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"إلغاء"</string> + <string name="string_continue" msgid="1346732695941131882">"متابعة"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"الإنشاء في مكان آخر"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"الحفظ في مكان آخر"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"استخدام جهاز آخر"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"طريقة بسيطة لتسجيل الدخول بأمان"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"استخدِم بصمة إصبعك أو وجهك أو قفل الشاشة لتسجيل الدخول باستخدام مفتاح مرور فريد لا يمكن نسيانه أو سرقته. مزيد من المعلومات"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"اختيار مكان <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"حفظ كلمة المرور"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"حفظ معلومات تسجيل الدخول"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"هل تريد إنشاء مفتاح مرور في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"هل تريد حفظ كلمة مرورك في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"هل تريد حفظ معلومات تسجيل الدخول في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"يمكنك استخدام <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> على أي جهاز. يتم حفظه في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\" لـ \"<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>\"."</string> + <string name="passkey" msgid="632353688396759522">"مفتاح مرور"</string> + <string name="password" msgid="6738570945182936667">"كلمة المرور"</string> + <string name="sign_ins" msgid="4710739369149469208">"عمليات تسجيل الدخول"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"هل تريد استخدام \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" لكل عمليات تسجيل الدخول؟"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ضبط الخيار كتلقائي"</string> + <string name="use_once" msgid="9027366575315399714">"الاستخدام مرة واحدة"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"عدد كلمات المرور هو <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>، و عدد مفاتيح المرور هو <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"عدد كلمات المرور: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"عدد مفاتيح المرور: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"جهاز آخر"</string> + <string name="other_password_manager" msgid="565790221427004141">"خدمات مدراء كلمات المرور الأخرى"</string> + <string name="close_sheet" msgid="1393792015338908262">"إغلاق ورقة البيانات"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"العودة إلى الصفحة السابقة"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"هل تريد استخدام مفتاح المرور المحفوظ لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"هل تريد استخدام بيانات اعتماد تسجيل الدخول المحفوظة لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"؟"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"اختيار بيانات اعتماد تسجيل دخول محفوظة لـ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"تسجيل الدخول بطريقة أخرى"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"لا، شكرًا"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"متابعة"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"خيارات تسجيل الدخول"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"معلومات تسجيل دخول \"<xliff:g id="USERNAME">%1$s</xliff:g>\""</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"خدمات إدارة كلمات المرور المقفولة"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"انقر لإلغاء القفل."</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"إداراة عمليات تسجيل الدخول"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"من جهاز آخر"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"استخدام جهاز مختلف"</string> +</resources> diff --git a/packages/CredentialManager/res/values-as/strings.xml b/packages/CredentialManager/res/values-as/strings.xml new file mode 100644 index 000000000000..c1040987b3d3 --- /dev/null +++ b/packages/CredentialManager/res/values-as/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"বাতিল কৰক"</string> + <string name="string_continue" msgid="1346732695941131882">"অব্যাহত ৰাখক"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"অন্য ঠাইত সৃষ্টি কৰক"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"অন্য ঠাইত ছেভ কৰক"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"অন্য ডিভাইচ ব্যৱহাৰ কৰক"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"সুৰক্ষিতভাৱে ছাইন ইন কৰাৰ এক সৰল উপায়"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"পাহৰি নোযোৱা অথবা চুৰি কৰিব নোৱৰা এটা অদ্বিতীয় পাছকী ব্যৱহাৰ কৰি ছাইন ইন কৰিবলৈ আপোনাৰ ফিংগাৰপ্ৰিণ্ট, মুখাৱয়ব অথবা স্ক্ৰীন লক ব্যৱহাৰ কৰক। অধিক জানক"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"ক’ত <xliff:g id="CREATETYPES">%1$s</xliff:g> সেয়া বাছনি কৰক"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"আপোনাৰ পাছৱৰ্ড ছেভ কৰক"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"আপোনাৰ ছাইন ইন কৰাৰ তথ্য ছেভ কৰক"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ত পাছকী সৃষ্টি কৰিবনে?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ত আপোনাৰ পাছৱৰ্ড ছেভ কৰিবনে?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ত আপোনাৰ ছাইন ইন কৰাৰ তথ্য ছেভ কৰিবনে?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"আপুনি আপোনাৰ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> যিকোনো ডিভাইচত ব্যৱহাৰ কৰিব পাৰে। এইটো <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>ৰ বাবে <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>ত ছেভ কৰা হৈছে"</string> + <string name="passkey" msgid="632353688396759522">"পাছকী"</string> + <string name="password" msgid="6738570945182936667">"পাছৱৰ্ড"</string> + <string name="sign_ins" msgid="4710739369149469208">"ছাইন-ইন"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"আপোনাৰ আটাইবোৰ ছাইন ইনৰ বাবে <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যৱহাৰ কৰিবনে?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ডিফ’ল্ট হিচাপে ছেট কৰক"</string> + <string name="use_once" msgid="9027366575315399714">"এবাৰ ব্যৱহাৰ কৰক"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> টা পাছকী"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> টা পাছকী"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"অন্য এটা ডিভাইচ"</string> + <string name="other_password_manager" msgid="565790221427004141">"অন্য পাছৱৰ্ড পৰিচালক"</string> + <string name="close_sheet" msgid="1393792015338908262">"শ্বীট বন্ধ কৰক"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"পূৰ্বৱৰ্তী পৃষ্ঠালৈ ঘূৰি যাওক"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে আপোনাৰ ছেভ হৈ থকা পাছকী ব্যৱহাৰ কৰিবনে?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে আপোনাৰ ছেভ হৈ থকা ছাইন ইন তথ্য ব্যৱহাৰ কৰিবনে?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাবে ছেভ হৈ থকা এটা ছাইন ইন বাছনি কৰক"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"অন্য উপায়েৰে ছাইন ইন কৰক"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"নালাগে, ধন্যবাদ"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"অব্যাহত ৰাখক"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ছাইন ইনৰ বিকল্প"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>ৰ বাবে"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"লক হৈ থকা পাছৱৰ্ড পৰিচালক"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"আনলক কৰিবলৈ টিপক"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ছাইন ইন পৰিচালনা কৰক"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"অন্য এটা ডিভাইচৰ পৰা"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"অন্য এটা ডিভাইচ ব্যৱহাৰ কৰক"</string> +</resources> diff --git a/packages/CredentialManager/res/values-az/strings.xml b/packages/CredentialManager/res/values-az/strings.xml new file mode 100644 index 000000000000..4a8fef514785 --- /dev/null +++ b/packages/CredentialManager/res/values-az/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Ləğv edin"</string> + <string name="string_continue" msgid="1346732695941131882">"Davam edin"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Başqa yerdə yaradın"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Başqa yerdə yadda saxlayın"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Digər cihaz istifadə edin"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Başqa cihazda yadda saxlayın"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Təhlükəsiz daxil olmağın sadə yolu"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Unutmaq və ya oğurlamaq mümkün olmayan unikal giriş açarı ilə daxil olmaq üçün barmaq izi, üz və ya ekran kilidindən istifadə edin. Ətraflı məlumat"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> üçün yer seçin"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"parolunuzu yadda saxlayın"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"giriş məlumatınızı yadda saxlayın"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xidmətində giriş açarı yaradılsın?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Parol <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xidmətində saxlanılsın?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Giriş məlumatınız <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xidmətində saxlanılsın?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> domenini istənilən cihazda istifadə edə bilərsiniz. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> üçün <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> xidmətində saxlanılıb"</string> + <string name="passkey" msgid="632353688396759522">"giriş açarı"</string> + <string name="password" msgid="6738570945182936667">"parol"</string> + <string name="sign_ins" msgid="4710739369149469208">"girişlər"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Bütün girişlər üçün <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> istifadə edilsin?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Defolt olaraq seçin"</string> + <string name="use_once" msgid="9027366575315399714">"Bir dəfə istifadə edin"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> giriş açarı"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> giriş açarı"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Digər cihaz"</string> + <string name="other_password_manager" msgid="565790221427004141">"Digər parol menecerləri"</string> + <string name="close_sheet" msgid="1393792015338908262">"Səhifəni bağlayın"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Əvvəlki səhifəyə qayıdın"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış giriş açarı istifadə edilsin?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış girişdən istifadə edilsin?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> üçün yadda saxlanmış girişi seçin"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Başqa üsulla daxil olun"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Xeyr, təşəkkürlər"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Davam edin"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Giriş seçimləri"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> üçün"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Kilidli parol menecerləri"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Kilidi açmaq üçün tıklayın"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Girişləri idarə edin"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Başqa cihazdan"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Başqa cihaz istifadə edin"</string> +</resources> diff --git a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml new file mode 100644 index 000000000000..b46518eafef4 --- /dev/null +++ b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Otkaži"</string> + <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Napravi na drugom mestu"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Sačuvaj na drugom mestu"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Koristi drugi uređaj"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Sačuvaj na drugi uređaj"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednostavan način da se bezbedno prijavljujete"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Koristite otisak prsta, zaključavanje licem ili zaključavanje ekrana da biste se prijavili pomoću jedinstvenog pristupnog koda koji ne može da se zaboravi ili ukrade. Saznajte više"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite lokaciju za: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"sačuvajte lozinku"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"sačuvajte podatke o prijavljivanju"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Želite da napravite pristupni kôd kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Želite da sačuvate lozinku kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Želite da sačuvate podatke o prijavljivanju kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Možete da koristite tip domena <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> na bilo kom uređaju. Čuva se kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> za: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"pristupni kôd"</string> + <string name="password" msgid="6738570945182936667">"lozinka"</string> + <string name="sign_ins" msgid="4710739369149469208">"prijavljivanja"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite da za sva prijavljivanja koristite: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Podesi kao podrazumevano"</string> + <string name="use_once" msgid="9027366575315399714">"Koristi jednom"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, pristupnih kodova:<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Pristupnih kodova: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string> + <string name="other_password_manager" msgid="565790221427004141">"Drugi menadžeri lozinki"</string> + <string name="close_sheet" msgid="1393792015338908262">"Zatvorite tabelu"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vratite se na prethodnu stranicu"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Želite da koristite sačuvani pristupni kôd za: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Želite da koristite sačuvane podatke za prijavljivanje za: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Odaberite sačuvano prijavljivanje za: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijavite se na drugi način"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, hvala"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Nastavi"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcije za prijavljivanje"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za: <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Menadžeri zaključanih lozinki"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Dodirnite da biste otključali"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljajte prijavljivanjima"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Sa drugog uređaja"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Koristi drugi uređaj"</string> +</resources> diff --git a/packages/CredentialManager/res/values-be/strings.xml b/packages/CredentialManager/res/values-be/strings.xml new file mode 100644 index 000000000000..afa4d015f918 --- /dev/null +++ b/packages/CredentialManager/res/values-be/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Скасаваць"</string> + <string name="string_continue" msgid="1346732695941131882">"Далей"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Стварыць у іншым месцы"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Захаваць у іншым месцы"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Скарыстаць іншую прыладу"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Захаваць на іншую прыладу"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Просты спосаб бяспечнага ўваходу"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Для ўваходу з унікальным ключом доступу, які нельга згубіць ці ўкрасці, можна скарыстаць адбітак пальца, распазнаванне твару ці разблакіроўку экрана. Даведацца больш"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Выберыце, дзе <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"захаваць пароль"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"захаваць інфармацыю пра спосаб уваходу"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Стварыць ключ доступу ў папцы \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Захаваць пароль у папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Захаваць інфармацыю пра спосаб уваходу ў папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Вы можаце выкарыстоўваць <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> на любой прыладзе. Даныя для \"<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>\" захоўваюцца ў папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\""</string> + <string name="passkey" msgid="632353688396759522">"ключ доступу"</string> + <string name="password" msgid="6738570945182936667">"пароль"</string> + <string name="sign_ins" msgid="4710739369149469208">"спосабы ўваходу"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Выкарыстоўваць папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" для ўсіх спосабаў уваходу?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Выкарыстоўваць стандартна"</string> + <string name="use_once" msgid="9027366575315399714">"Скарыстаць адзін раз"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Пароляў: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, ключоў доступу: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Пароляў: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Ключоў доступу: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Іншая прылада"</string> + <string name="other_password_manager" msgid="565790221427004141">"Іншыя спосабы ўваходу"</string> + <string name="close_sheet" msgid="1393792015338908262">"Закрыць аркуш"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вярнуцца да папярэдняй старонкі"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Скарыстаць захаваны ключ доступу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Скарыстаць захаваныя спосабы ўваходу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Выберыце захаваны спосаб уваходу для праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Увайсці іншым спосабам"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Не, дзякуй"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Далей"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Спосабы ўваходу"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Для карыстальніка <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заблакіраваныя спосабы ўваходу"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Націсніце, каб разблакіраваць"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Кіраваць спосабамі ўваходу"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"З іншай прылады"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Скарыстаць іншую прыладу"</string> +</resources> diff --git a/packages/CredentialManager/res/values-bg/strings.xml b/packages/CredentialManager/res/values-bg/strings.xml new file mode 100644 index 000000000000..1a2f881f6bc5 --- /dev/null +++ b/packages/CredentialManager/res/values-bg/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Отказ"</string> + <string name="string_continue" msgid="1346732695941131882">"Напред"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Създаване другаде"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Запазване на друго място"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Използване на друго устройство"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Запазване на друго устройство"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Лесен начин за безопасно влизане в профил"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Използвайте отпечатъка, лицето или опцията си за заключване на екрана, за да влизате в профила си с помощта на уникален код за достъп, който не може да бъде забравен или откраднат. Научете повече"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Изберете място за <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"запазване на паролата ви"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"запазване на данните ви за вход"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Да се създаде ли код за достъп в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Искате ли да запазите паролата си в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Искате ли да запазите данните си за вход в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Можете да използвате <xliff:g id="TYPE">%2$s</xliff:g> за <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> на всяко устройство. Тези данни се запазват в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> за <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"код за достъп"</string> + <string name="password" msgid="6738570945182936667">"парола"</string> + <string name="sign_ins" msgid="4710739369149469208">"данни за вход"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се използва ли <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за всичките ви данни за вход?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Задаване като основно"</string> + <string name="use_once" msgid="9027366575315399714">"Еднократно използване"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> пароли, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> кода за достъп"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> пароли"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> кода за достъп"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Друго устройство"</string> + <string name="other_password_manager" msgid="565790221427004141">"Други мениджъри на пароли"</string> + <string name="close_sheet" msgid="1393792015338908262">"Затваряне на таблицата"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Назад към предишната страница"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Да се използва ли запазеният ви код за достъп за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Да се използват ли запазените ви данни за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Изберете запазени данни за вход за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Влизане в профила по друг начин"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Не, благодаря"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Напред"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опции за влизане в профила"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"За <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заключени мениджъри на пароли"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Докоснете, за да отключите"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управление на данните за вход"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"От друго устройство"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Използване на друго устройство"</string> +</resources> diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml new file mode 100644 index 000000000000..3257b7837c4b --- /dev/null +++ b/packages/CredentialManager/res/values-bn/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"বাতিল করুন"</string> + <string name="string_continue" msgid="1346732695941131882">"চালিয়ে যান"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"অন্য জায়গায় তৈরি করুন"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"অন্য জায়গায় সেভ করুন"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"অন্য ডিভাইস ব্যবহার করুন"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"অন্য ডিভাইসে সেভ করুন"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"নিরাপদে সাইন-ইন করার একটি সহজ উপায়"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"একটি অনন্য পাসকী ব্যবহার করে সাইন-ইন করতে আপনার ফিঙ্গারপ্রিন্ট, মুখ বা স্ক্রিন লক ব্যবহার করুন যেটি ভুলে যাবেন না বা হারিয়ে যাবেন না। আরও জানুন"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> কোথায় সেভ করবেন তা বেছে নিন"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"আপনার পাসওয়ার্ড সেভ করুন"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"আপনার সাইন-ইন করা সম্পর্কিত তথ্য সেভ করুন"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-এ পাসকী তৈরি করবেন?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"আপনার পাসওয়ার্ড <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-এ সেভ করবেন?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"আপনার সাইন-ইন করা সম্পর্কিত তথ্য <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-এ সেভ করবেন?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"যেকোনও ডিভাইসে নিজের <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ব্যবহার করতে পারবেন। এটি <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>-এর জন্য <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>-এ সেভ করা আছে"</string> + <string name="passkey" msgid="632353688396759522">"পাসকী"</string> + <string name="password" msgid="6738570945182936667">"পাসওয়ার্ড"</string> + <string name="sign_ins" msgid="4710739369149469208">"সাইন-ইন"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"আপনার সব সাইন-ইনের জন্য <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যবহার করবেন?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ডিফল্ট হিসেবে সেট করুন"</string> + <string name="use_once" msgid="9027366575315399714">"একবার ব্যবহার করুন"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>টি পাসওয়ার্ড, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>টি পাসকী"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>টি পাসওয়ার্ড"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>টি পাসকী"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"অন্য ডিভাইস"</string> + <string name="other_password_manager" msgid="565790221427004141">"অন্যান্য Password Manager"</string> + <string name="close_sheet" msgid="1393792015338908262">"শিট বন্ধ করুন"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"আগের পৃষ্ঠায় ফিরে যান"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য আপনার সেভ করা পাসকী ব্যবহার করবেন?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য আপনার সেভ করা সাইন-ইন সম্পর্কিত ক্রেডেনশিয়াল ব্যবহার করবেন?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর জন্য সাইন-ইন করা সম্পর্কিত ক্রেডেনশিয়াল বেছে নিন"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"অন্যভাবে সাইন-ইন করুন"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"না থাক"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"চালিয়ে যান"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"সাইন-ইন করার বিকল্প"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-এর জন্য"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"লক করা Password Manager"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"আনলক করতে ট্যাপ করুন"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"সাইন-ইন করার ক্রেডেনশিয়াল ম্যানেজ করুন"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"অন্য ডিভাইস থেকে"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"আলাদা ডিভাইস ব্যবহার করুন"</string> +</resources> diff --git a/packages/CredentialManager/res/values-bs/strings.xml b/packages/CredentialManager/res/values-bs/strings.xml new file mode 100644 index 000000000000..705cfefcf979 --- /dev/null +++ b/packages/CredentialManager/res/values-bs/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Otkaži"</string> + <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Kreirajte na drugom mjestu"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Sačuvajte na drugom mjestu"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Koristite drugi uređaj"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Sačuvajte na drugom uređaju"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednostavan način za sigurnu prijavu"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Koristite otisak prsta, lice ili zaključavanje ekrana da se prijavite jedinstvenim pristupnim ključem koji se ne može zaboraviti niti ukrasti. Saznajte više"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite gdje <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"sačuvajte lozinku"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"sačuvajte informacije za prijavu"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Kreirati pristupni ključ na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Sačuvati lozinku na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Sačuvati informacije za prijavu na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Možete koristiti <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> na bilo kojem uređaju. Sačuvan je na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> za <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"pristupni ključ"</string> + <string name="password" msgid="6738570945182936667">"lozinka"</string> + <string name="sign_ins" msgid="4710739369149469208">"prijave"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Koristiti uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve vaše prijave?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Postavi kao zadano"</string> + <string name="use_once" msgid="9027366575315399714">"Koristi jednom"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>; broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string> + <string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji lozinki"</string> + <string name="close_sheet" msgid="1393792015338908262">"Zatvaranje tabele"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Povratak na prethodnu stranicu"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Koristiti sačuvani pristupni ključ za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Koristiti sačuvanu prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Odaberite sačuvanu prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijavite se na drugi način"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, hvala"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Nastavi"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcije prijave"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za osobu <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zaključani upravitelji lozinki"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Dodirnite da otključate"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljajte prijavama"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"S drugog uređaja"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Upotrijebite drugi uređaj"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ca/strings.xml b/packages/CredentialManager/res/values-ca/strings.xml new file mode 100644 index 000000000000..d9b5a9039d92 --- /dev/null +++ b/packages/CredentialManager/res/values-ca/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Cancel·la"</string> + <string name="string_continue" msgid="1346732695941131882">"Continua"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Crea en un altre lloc"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Desa en un altre lloc"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Utilitza un altre dispositiu"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Una manera senzilla i segura d\'iniciar la sessió"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilitza l\'empremta digital, la cara o el bloqueig de pantalla per iniciar la sessió amb una clau d\'accés única que no es pot oblidar ni robar. Més informació"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Tria on vols <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"desar la contrasenya"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"desar la teva informació d\'inici de sessió"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vols crear una clau d\'accés a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vols desar la contrasenya a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vols desar la teva informació d\'inici de sessió a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Pots utilitzar <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> en qualsevol dispositiu. Està desat a <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> per a <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"clau d\'accés"</string> + <string name="password" msgid="6738570945182936667">"contrasenya"</string> + <string name="sign_ins" msgid="4710739369149469208">"inicis de sessió"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vols utilitzar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per a tots els teus inicis de sessió?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Estableix com a predeterminada"</string> + <string name="use_once" msgid="9027366575315399714">"Utilitza un cop"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasenyes, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> claus d\'accés"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasenyes"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> claus d\'accés"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Un altre dispositiu"</string> + <string name="other_password_manager" msgid="565790221427004141">"Altres gestors de contrasenyes"</string> + <string name="close_sheet" msgid="1393792015338908262">"Tanca el full"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Torna a la pàgina anterior"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vols utilitzar la clau d\'accés desada per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vols utilitzar l\'inici de sessió desat per a <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Tria un inici de sessió desat per a <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Inicia la sessió d\'una altra manera"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No, gràcies"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continua"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcions d\'inici de sessió"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Per a <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestors de contrasenyes bloquejats"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toca per desbloquejar"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestiona els inicis de sessió"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Des d\'un altre dispositiu"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utilitza un dispositiu diferent"</string> +</resources> diff --git a/packages/CredentialManager/res/values-cs/strings.xml b/packages/CredentialManager/res/values-cs/strings.xml new file mode 100644 index 000000000000..e7fe5365ff26 --- /dev/null +++ b/packages/CredentialManager/res/values-cs/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Zrušit"</string> + <string name="string_continue" msgid="1346732695941131882">"Pokračovat"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Vytvořit na jiném místě"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Uložit na jiné místo"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Použít jiné zařízení"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Uložit do jiného zařízení"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednoduchý způsob, jak se bezpečně přihlásit"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Použijte svůj otisk prstu, obličej nebo zámek obrazovky k přihlášení pomocí jedinečného přístupového klíče, který nemůžete zapomenout a který vám nikdo nemůže odcizit. Další informace"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Zvolte, kde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"uložte si heslo"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"uložte své přihlašovací údaje"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vytvořit přístupový klíč v <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Uložit heslo do <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Uložit přihlašovací údaje do <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Svoje <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> můžete používat na libovolném zařízení. Ukládá se do <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> pro <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"přístupový klíč"</string> + <string name="password" msgid="6738570945182936667">"heslo"</string> + <string name="sign_ins" msgid="4710739369149469208">"přihlášení"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Používat <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pro všechna přihlášení?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Nastavit jako výchozí"</string> + <string name="use_once" msgid="9027366575315399714">"Použít jednou"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Počet hesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, počet přístupových klíčů: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Počet hesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Počet přístupových klíčů: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Jiné zařízení"</string> + <string name="other_password_manager" msgid="565790221427004141">"Další správci hesel"</string> + <string name="close_sheet" msgid="1393792015338908262">"Zavřít list"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Zpět na předchozí stránku"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Použít uložený přístupový klíč pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Použít uložené přihlášení pro aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Vyberte uložené přihlášení pro <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Přihlásit se jinak"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, díky"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Pokračovat"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Možnosti přihlašování"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pro uživatele <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Uzamčení správci hesel"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Klepnutím odemknete"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Spravovat přihlášení"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Z jiného zařízení"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Použít jiné zařízení"</string> +</resources> diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml new file mode 100644 index 000000000000..86cf9ff210d2 --- /dev/null +++ b/packages/CredentialManager/res/values-da/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Annuller"</string> + <string name="string_continue" msgid="1346732695941131882">"Fortsæt"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Opret et andet sted"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Gem et andet sted"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Brug en anden enhed"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Gem på en anden enhed"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"En nemmere og mere sikker måde at logge ind på"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Brug dit fingeraftryk, dit ansigt eller din skærmlås for at logge ind med en unik adgangsnøgle, der hverken kan glemmes eller stjæles. Få flere oplysninger"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Vælg, hvor du vil <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"gem din adgangskode"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"gem dine loginoplysninger"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vil du oprette en adgangsnøgle i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vil du gemme din adgangskode i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vil du gemme dine loginoplysninger i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Du kan bruge din <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> på enhver enhed. Den gemmes i <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"adgangsnøgle"</string> + <string name="password" msgid="6738570945182936667">"adgangskode"</string> + <string name="sign_ins" msgid="4710739369149469208">"loginmetoder"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruge <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> til alle dine loginmetoder?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Angiv som standard"</string> + <string name="use_once" msgid="9027366575315399714">"Brug én gang"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> adgangskoder, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> adgangsnøgler"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> adgangskoder"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> adgangsnøgler"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"En anden enhed"</string> + <string name="other_password_manager" msgid="565790221427004141">"Andre adgangskodeadministratorer"</string> + <string name="close_sheet" msgid="1393792015338908262">"Luk arket"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tilbage til den forrige side"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vil du bruge din gemte adgangsnøgle til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vil du bruge din gemte loginmetode til <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Vælg en gemt loginmetode til <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Log ind på en anden måde"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nej tak"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Fortsæt"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Valgmuligheder for login"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Låste adgangskodeadministratorer"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tryk for at låse op"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrer loginmetoder"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Fra en anden enhed"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Brug en anden enhed"</string> +</resources> diff --git a/packages/CredentialManager/res/values-de/strings.xml b/packages/CredentialManager/res/values-de/strings.xml new file mode 100644 index 000000000000..d239dd3d2664 --- /dev/null +++ b/packages/CredentialManager/res/values-de/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Abbrechen"</string> + <string name="string_continue" msgid="1346732695941131882">"Weiter"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"An anderem Speicherort erstellen"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"An anderem Ort speichern"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Anderes Gerät verwenden"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Auf einem anderen Gerät speichern"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Einfach und sicher anmelden"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Verwende deinen Fingerabdruck oder deine Gesichts- bzw. Displaysperre, um dich mit einem eindeutigen Passkey anzumelden, der nicht vergessen oder gestohlen werden kann. Weitere Informationen"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Ort auswählen für: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"Passwort speichern"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"Anmeldedaten speichern"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Passkey hier erstellen: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Passwort hier speichern: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Anmeldedaten hier speichern: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Du kannst <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> auf jedem beliebigen Gerät verwenden. Gespeichert (<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>) für <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"Passkey"</string> + <string name="password" msgid="6738570945182936667">"Passwort"</string> + <string name="sign_ins" msgid="4710739369149469208">"Anmeldungen"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> für alle Anmeldungen verwenden?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Als Standard festlegen"</string> + <string name="use_once" msgid="9027366575315399714">"Einmal verwenden"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> Passkeys"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> Passkeys"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Ein anderes Gerät"</string> + <string name="other_password_manager" msgid="565790221427004141">"Andere Passwortmanager"</string> + <string name="close_sheet" msgid="1393792015338908262">"Tabellenblatt schließen"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Zurück zur vorherigen Seite"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gespeicherten Passkey für <xliff:g id="APP_NAME">%1$s</xliff:g> verwenden?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gespeicherte Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> verwenden?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Gespeicherte Anmeldedaten für <xliff:g id="APP_NAME">%1$s</xliff:g> auswählen"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Andere Anmeldeoption auswählen"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nein danke"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Weiter"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Anmeldeoptionen"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Für <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gesperrte Passwortmanager"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Zum Entsperren tippen"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Anmeldedaten verwalten"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Von einem anderen Gerät"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Anderes Gerät verwenden"</string> +</resources> diff --git a/packages/CredentialManager/res/values-el/strings.xml b/packages/CredentialManager/res/values-el/strings.xml new file mode 100644 index 000000000000..9b7ccbb24ad1 --- /dev/null +++ b/packages/CredentialManager/res/values-el/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Ακύρωση"</string> + <string name="string_continue" msgid="1346732695941131882">"Συνέχεια"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Δημιουργία σε άλλη θέση"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Αποθήκευση σε άλλη θέση"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Χρήση άλλης συσκευής"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Αποθήκευση σε άλλη συσκευή"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Ένας απλός τρόπος για ασφαλή σύνδεση"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Χρησιμοποιήστε το δακτυλικό αποτύπωμα, το πρόσωπο ή το κλείδωμα οθόνης σας για να συνδεθείτε με ένα μοναδικό κλειδί πρόσβασης που δεν είναι δυνατό να ξεχάσετε ή να κλαπεί. Μάθετε περισσότερα"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Επιλέξτε θέση για <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"αποθήκευση του κωδικού πρόσβασής σας"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"αποθήκευση των στοιχείων σύνδεσής σας"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Να δημιουργηθει κλειδί πρόσβασης στο <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>;"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Να αποθηκευτεί ο κωδικός πρόσβασής σας στο <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>;"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Να αποθηκευτούν τα στοιχεία σύνδεσής σας στο <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>;"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Μπορείτε να χρησιμοποιήσετε το στοιχείο τύπου <xliff:g id="TYPE">%2$s</xliff:g> του τομέα <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> σε οποιαδήποτε συσκευή. Αποθηκεύτηκε στο <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> για <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"κλειδί πρόσβασης"</string> + <string name="password" msgid="6738570945182936667">"κωδικός πρόσβασης"</string> + <string name="sign_ins" msgid="4710739369149469208">"στοιχεία σύνδεσης"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Να χρησιμοποιηθεί το <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> για όλες τις συνδέσεις σας;"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Ορισμός ως προεπιλογής"</string> + <string name="use_once" msgid="9027366575315399714">"Χρήση μία φορά"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> κλειδιά πρόσβασης"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> κλειδιά πρόσβασης"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Άλλη συσκευή"</string> + <string name="other_password_manager" msgid="565790221427004141">"Άλλοι διαχειριστές κωδικών πρόσβασης"</string> + <string name="close_sheet" msgid="1393792015338908262">"Κλείσιμο φύλλου"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Επιστροφή στην προηγούμενη σελίδα"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Να χρησιμοποιηθεί το αποθηκευμένο κλειδί πρόσβασης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Να χρησιμοποιηθούν τα αποθηκευμένα στοιχεία σύνδεσης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Επιλογή αποθηκευμένων στοιχείων σύνδεσης για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Σύνδεση με άλλον τρόπο"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Όχι, ευχαριστώ"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Συνέχεια"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Επιλογές σύνδεσης"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Για τον χρήστη <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Κλειδωμένοι διαχειριστές κωδικών πρόσβασης"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Πατήστε για ξεκλείδωμα"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Διαχείριση στοιχείων σύνδεσης"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Από άλλη συσκευή"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Χρήση διαφορετικής συσκευής"</string> +</resources> diff --git a/packages/CredentialManager/res/values-en-rAU/strings.xml b/packages/CredentialManager/res/values-en-rAU/strings.xml new file mode 100644 index 000000000000..682dffbf4e28 --- /dev/null +++ b/packages/CredentialManager/res/values-en-rAU/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string> + <string name="string_continue" msgid="1346732695941131882">"Continue"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"save your password"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"You can use your <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> on any device. It is saved to <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"passkey"</string> + <string name="password" msgid="6738570945182936667">"password"</string> + <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string> + <string name="use_once" msgid="9027366575315399714">"Use once"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Another device"</string> + <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string> + <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No thanks"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tap to unlock"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string> +</resources> diff --git a/packages/CredentialManager/res/values-en-rCA/strings.xml b/packages/CredentialManager/res/values-en-rCA/strings.xml new file mode 100644 index 000000000000..4ec2872f7c0b --- /dev/null +++ b/packages/CredentialManager/res/values-en-rCA/strings.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string> + <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string> + <string name="string_continue" msgid="1346732695941131882">"Continue"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"save your password"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"You can use your <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> on any device. It is saved to <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"passkey"</string> + <string name="password" msgid="6738570945182936667">"password"</string> + <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string> + <string name="use_once" msgid="9027366575315399714">"Use once"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string> + <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string> + <string name="another_device" msgid="5147276802037801217">"Another device"</string> + <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string> + <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No thanks"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tap to unlock"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string> +</resources> diff --git a/packages/CredentialManager/res/values-en-rGB/strings.xml b/packages/CredentialManager/res/values-en-rGB/strings.xml new file mode 100644 index 000000000000..682dffbf4e28 --- /dev/null +++ b/packages/CredentialManager/res/values-en-rGB/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string> + <string name="string_continue" msgid="1346732695941131882">"Continue"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"save your password"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"You can use your <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> on any device. It is saved to <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"passkey"</string> + <string name="password" msgid="6738570945182936667">"password"</string> + <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string> + <string name="use_once" msgid="9027366575315399714">"Use once"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Another device"</string> + <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string> + <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No thanks"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tap to unlock"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string> +</resources> diff --git a/packages/CredentialManager/res/values-en-rIN/strings.xml b/packages/CredentialManager/res/values-en-rIN/strings.xml new file mode 100644 index 000000000000..682dffbf4e28 --- /dev/null +++ b/packages/CredentialManager/res/values-en-rIN/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string> + <string name="string_continue" msgid="1346732695941131882">"Continue"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"save your password"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"You can use your <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> on any device. It is saved to <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"passkey"</string> + <string name="password" msgid="6738570945182936667">"password"</string> + <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string> + <string name="use_once" msgid="9027366575315399714">"Use once"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Another device"</string> + <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string> + <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No thanks"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tap to unlock"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string> +</resources> diff --git a/packages/CredentialManager/res/values-en-rXC/strings.xml b/packages/CredentialManager/res/values-en-rXC/strings.xml new file mode 100644 index 000000000000..d114a4658e64 --- /dev/null +++ b/packages/CredentialManager/res/values-en-rXC/strings.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string> + <string name="string_cancel" msgid="6369133483981306063">"Cancel"</string> + <string name="string_continue" msgid="1346732695941131882">"Continue"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"save your password"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"You can use your <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> on any device. It is saved to <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"passkey"</string> + <string name="password" msgid="6738570945182936667">"password"</string> + <string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Set as default"</string> + <string name="use_once" msgid="9027366575315399714">"Use once"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string> + <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string> + <string name="another_device" msgid="5147276802037801217">"Another device"</string> + <string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string> + <string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Go back to the previous page"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Use your saved passkey for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Use your saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choose a saved sign-in for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Sign in another way"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No thanks"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continue"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sign-in options"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Locked password managers"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tap to unlock"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Manage sign-ins"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"From another device"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use a different device"</string> +</resources> diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml new file mode 100644 index 000000000000..96e769754101 --- /dev/null +++ b/packages/CredentialManager/res/values-es-rUS/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string> + <string name="string_continue" msgid="1346732695941131882">"Continuar"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Crear en otra ubicación"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Guardar en otra ubicación"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Usar otro dispositivo"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Guardar en otro dispositivo"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un modo simple y seguro de ingresar"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa tu huella dactilar, tu rostro o el bloqueo de pantalla para acceder con una llave de acceso única que no olvidarás ni podrán robarte. Más información"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Elige dónde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"guardar tu contraseña"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"guardar tu información de acceso"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"¿Quieres crear una llave de acceso en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"¿Quieres guardar tu contraseña de <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"¿Quieres guardar tu información de acceso a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Puedes usar tu <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> en cualquier dispositivo. Se guardó en <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string> + <string name="passkey" msgid="632353688396759522">"llave de acceso"</string> + <string name="password" msgid="6738570945182936667">"contraseña"</string> + <string name="sign_ins" msgid="4710739369149469208">"accesos"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Quieres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus accesos?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Establecer como predeterminado"</string> + <string name="use_once" msgid="9027366575315399714">"Usar una vez"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> llaves de acceso, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> contraseñas"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> llaves de acceso"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Otro dispositivo"</string> + <string name="other_password_manager" msgid="565790221427004141">"Otros administradores de contraseñas"</string> + <string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver a la página anterior"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"¿Quieres usar tu llave de acceso guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"¿Quieres usar tu acceso guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Elige un acceso guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Acceder de otra forma"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No, gracias"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opciones de acceso"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Administradores de contraseñas bloqueados"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Presiona para desbloquear"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrar accesos"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Desde otro dispositivo"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otra voz"</string> +</resources> diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml new file mode 100644 index 000000000000..dce1a8ea7656 --- /dev/null +++ b/packages/CredentialManager/res/values-es/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string> + <string name="string_continue" msgid="1346732695941131882">"Continuar"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Crear en otro lugar"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Guardar en otro lugar"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Usar otro dispositivo"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Guardar en otro dispositivo"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Una forma sencilla y segura de iniciar sesión"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa la huella digital, la cara o el bloqueo de pantalla para iniciar sesión con una llave de acceso única que no se puede olvidar ni robar. Más información"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Elige dónde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"guardar tu contraseña"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"guardar tu información de inicio de sesión"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"¿Crear una llave de acceso en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"¿Guardar tu contraseña en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"¿Guardar tu información de inicio de sesión en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Puedes usar tu <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> en cualquier dispositivo. Se guarda en <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string> + <string name="passkey" msgid="632353688396759522">"llave de acceso"</string> + <string name="password" msgid="6738570945182936667">"contraseña"</string> + <string name="sign_ins" msgid="4710739369149469208">"inicios de sesión"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus inicios de sesión?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Fijar como predeterminado"</string> + <string name="use_once" msgid="9027366575315399714">"Usar una vez"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> llaves de acceso"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> llaves de acceso"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Otro dispositivo"</string> + <string name="other_password_manager" msgid="565790221427004141">"Otros gestores de contraseñas"</string> + <string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver a la página anterior"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"¿Usar la llave de acceso guardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"¿Usar el inicio de sesión guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Elige un inicio de sesión guardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Iniciar sesión de otra manera"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No, gracias"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opciones de inicio de sesión"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestores de contraseñas bloqueados"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toca para desbloquear"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestionar inicios de sesión"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De otro dispositivo"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otro dispositivo"</string> +</resources> diff --git a/packages/CredentialManager/res/values-et/strings.xml b/packages/CredentialManager/res/values-et/strings.xml new file mode 100644 index 000000000000..00396e007808 --- /dev/null +++ b/packages/CredentialManager/res/values-et/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Tühista"</string> + <string name="string_continue" msgid="1346732695941131882">"Jätka"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Loo teises kohas"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvesta teise kohta"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Kasuta teist seadet"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvesta teise seadmesse"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Lihtne viis turvaliselt sisselogimiseks"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Kasutage sõrmejälge, nägu või ekraanilukku, et logida sisse unikaalse pääsuvõtmega, mida ei saa unustada ega varastada. Lisateave"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Valige, kus <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"parool salvestada"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"sisselogimisandmed salvestada"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Kas luua teenuses <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pääsuvõti?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Kas salvestada parool teenusesse <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Kas salvestada sisselogimisteave teenusesse <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Saate rakendust <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> kasutada mis tahes seadmes. Salvestatakse teenusesse <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> – <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"pääsukood"</string> + <string name="password" msgid="6738570945182936667">"parool"</string> + <string name="sign_ins" msgid="4710739369149469208">"sisselogimisandmed"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Kas kasutada teenust <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kõigi teie sisselogimisandmete puhul?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Määra vaikeseadeks"</string> + <string name="use_once" msgid="9027366575315399714">"Kasuta ühe korra"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parooli, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> pääsuvõtit"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parooli"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> pääsuvõtit"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Teine seade"</string> + <string name="other_password_manager" msgid="565790221427004141">"Muud paroolihaldurid"</string> + <string name="close_sheet" msgid="1393792015338908262">"Sule leht"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Minge tagasi eelmisele lehele"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Kas kasutada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud pääsuvõtit?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Kas kasutada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud sisselogimisandmeid?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Valige rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> jaoks salvestatud sisselogimisandmed"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Logige sisse muul viisil"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Tänan, ei"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Jätka"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Sisselogimise valikud"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Kasutajale <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Lukustatud paroolihaldurid"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Avamiseks puudutage"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Sisselogimisandmete haldamine"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Muus seadmes"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Kasuta teist seadet"</string> +</resources> diff --git a/packages/CredentialManager/res/values-eu/strings.xml b/packages/CredentialManager/res/values-eu/strings.xml new file mode 100644 index 000000000000..38f659204830 --- /dev/null +++ b/packages/CredentialManager/res/values-eu/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Utzi"</string> + <string name="string_continue" msgid="1346732695941131882">"Egin aurrera"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Sortu beste toki batean"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Gorde beste toki batean"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Erabili beste gailu bat"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Gorde beste gailu batean"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Segurtasun osoz saioa hasteko modu erraza"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Erabili hatz-marka, aurpegia edo pantailaren blokeoa ahaztu edo lapurtu ezin den sarbide-gako baten bidez saioa hasteko. Lortu informazio gehiago"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Aukeratu non <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"gorde pasahitza"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"gorde kredentzialei buruzko informazioa"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Sarbide-gako bat sortu nahi duzu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> aplikazioan?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Pasahitza <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> aplikazioan gorde nahi duzu?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Kredentzialei buruzko informazioa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> aplikazioan gorde nahi duzu?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> aplikazioko <xliff:g id="TYPE">%2$s</xliff:g> edozein gailutan erabil dezakezu. <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> aplikazioan dago gordeta (<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>)."</string> + <string name="passkey" msgid="632353688396759522">"sarbide-gakoa"</string> + <string name="password" msgid="6738570945182936667">"pasahitza"</string> + <string name="sign_ins" msgid="4710739369149469208">"kredentzialak"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> erabili nahi duzu kredentzial guztietarako?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Ezarri lehenetsi gisa"</string> + <string name="use_once" msgid="9027366575315399714">"Erabili behin"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> pasahitz eta <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> sarbide-gako"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> pasahitz"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> sarbide-gako"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Beste gailu bat"</string> + <string name="other_password_manager" msgid="565790221427004141">"Beste pasahitz-kudeatzaile batzuk"</string> + <string name="close_sheet" msgid="1393792015338908262">"Itxi orria"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Itzuli aurreko orrira"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gorde duzun sarbide-gakoa erabili nahi duzu?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gorde dituzun kredentzialak erabili nahi dituzu?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Aukeratu <xliff:g id="APP_NAME">%1$s</xliff:g> aplikaziorako gorde dituzun kredentzialak"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Hasi saioa beste modu batean"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ez, eskerrik asko"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Egin aurrera"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Saioa hasteko aukerak"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> erabiltzailearenak"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Blokeatutako pasahitz-kudeatzaileak"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Desblokeatzeko, sakatu hau"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Kudeatu kredentzialak"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Beste gailu batean gordetakoak"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Erabili beste gailu bat"</string> +</resources> diff --git a/packages/CredentialManager/res/values-fa/strings.xml b/packages/CredentialManager/res/values-fa/strings.xml new file mode 100644 index 000000000000..fdfd1e39eb48 --- /dev/null +++ b/packages/CredentialManager/res/values-fa/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"لغو"</string> + <string name="string_continue" msgid="1346732695941131882">"ادامه"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"ایجاد در مکانی دیگر"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"ذخیره در مکانی دیگر"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"استفاده از دستگاهی دیگر"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"ذخیره در دستگاهی دیگر"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"روشی ساده برای ورود به سیستم ایمن"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"برای ورود به سیستم با گذرکلیدی یکتا که غیرقابل فراموش شدن یا دزدیده شدن باشد، از اثر انگشت، چهره، یا قفل صفحه استفاده کنید. بیشتر بدانید"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"انتخاب محل <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"ذخیره گذرواژه"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"ذخیره اطلاعات ورود به سیستم"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"گذرکلید در <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ایجاد شود؟"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"گذرواژه در <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ذخیره شود؟"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"اطلاعات ورود به سیستم در <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ذخیره شود؟"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"میتوانید از <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> در هر دستگاهی استفاده کنید. در <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> برای <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ذخیره میشود"</string> + <string name="passkey" msgid="632353688396759522">"گذرکلید"</string> + <string name="password" msgid="6738570945182936667">"گذرواژه"</string> + <string name="sign_ins" msgid="4710739369149469208">"ورود به سیستمها"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"از <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> برای همه ورود به سیستمها استفاده شود؟"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"تنظیم بهعنوان پیشفرض"</string> + <string name="use_once" msgid="9027366575315399714">"یکبار استفاده"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> گذرواژه، <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> گذرکلید"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> گذرواژه"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> گذرکلید"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"دستگاهی دیگر"</string> + <string name="other_password_manager" msgid="565790221427004141">"دیگر مدیران گذرواژه"</string> + <string name="close_sheet" msgid="1393792015338908262">"بستن برگ"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"برگشتن به صفحه قبلی"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"گذرکلید ذخیرهشده برای <xliff:g id="APP_NAME">%1$s</xliff:g> استفاده شود؟"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ورود به سیستم ذخیرهشده برای <xliff:g id="APP_NAME">%1$s</xliff:g> استفاده شود؟"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"انتخاب ورود به سیستم ذخیرهشده برای <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ورود به سیستم به روشی دیگر"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"نه متشکرم"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ادامه"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"گزینههای ورود به سیستم"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"برای <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"مدیران گذرواژه قفلشده"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"برای باز کردن قفل ضربه بزنید"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"مدیریت ورود به سیستمها"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"از دستگاهی دیگر"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"استفاده از دستگاه دیگری"</string> +</resources> diff --git a/packages/CredentialManager/res/values-fi/strings.xml b/packages/CredentialManager/res/values-fi/strings.xml new file mode 100644 index 000000000000..26cfbe5fe890 --- /dev/null +++ b/packages/CredentialManager/res/values-fi/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Peru"</string> + <string name="string_continue" msgid="1346732695941131882">"Jatka"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Luo muualla"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Tallenna muualle"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Käytä toista laitetta"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Helppo tapa kirjautua turvallisesti sisään"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Käytä sormenjälkeä, kasvoja tai näytön lukitusta, niin voit kirjautua sisään yksilöllisellä avainkoodilla, jota ei voi unohtaa tai varastaa. Lue lisää"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Valitse paikka: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"tallenna salasanasi"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"tallenna kirjautumistiedot"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Luodaanko avainkoodi (<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>)?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Tallennetaanko salasanasi tänne: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Tallennetaanko kirjautumistietosi tänne: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> (<xliff:g id="TYPE">%2$s</xliff:g>) on käytettävissä millä tahansa laitteella. Se tallennetaan tänne: <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> (<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>)"</string> + <string name="passkey" msgid="632353688396759522">"avainkoodi"</string> + <string name="password" msgid="6738570945182936667">"salasana"</string> + <string name="sign_ins" msgid="4710739369149469208">"sisäänkirjautumiset"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Otetaanko <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> käyttöön kaikissa sisäänkirjautumisissa?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Aseta oletukseksi"</string> + <string name="use_once" msgid="9027366575315399714">"Käytä kerran"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> salasanaa, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> avainkoodia"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> salasanaa"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> avainkoodia"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Toinen laite"</string> + <string name="other_password_manager" msgid="565790221427004141">"Muut salasanojen ylläpitotyökalut"</string> + <string name="close_sheet" msgid="1393792015338908262">"Sulje taulukko"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Takaisin edelliselle sivulle"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Käytetäänkö tallennettua avainkoodiasi täällä: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Käytetäänkö tallennettuja kirjautumistietoja täällä: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Valitse tallennetut kirjautumistiedot (<xliff:g id="APP_NAME">%1$s</xliff:g>)"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Kirjaudu sisään toisella tavalla"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ei kiitos"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Jatka"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Kirjautumisvaihtoehdot"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Käyttäjä: <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Lukitut salasanojen ylläpitotyökalut"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Avaa napauttamalla"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Muuta kirjautumistietoja"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Toiselta laitteelta"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Käytä toista laitetta"</string> +</resources> diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml new file mode 100644 index 000000000000..ef3d3252c698 --- /dev/null +++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Annuler"</string> + <string name="string_continue" msgid="1346732695941131882">"Continuer"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Créer à un autre emplacement"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Enregistrer à un autre emplacement"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Utiliser un autre appareil"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Enregistrer sur un autre appareil"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Une manière simple de se connecter en toute sécurité"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilisez vos empreintes digitales, votre visage ou un écran de verrouillage pour vous connecter avec une clé d\'accès unique qui ne peut pas être oubliée ou volée. En savoir plus"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Choisir où <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"enregistrer votre mot de passe"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"enregistrer vos données de connexion"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Créer une clé d\'accès dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Enregistrer votre mot de passe dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Enregistrer vos données de connexion dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Vous pouvez utiliser votre <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> sur n\'importe quel appareil. Il est enregistré sur <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> pour <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"clé d\'accès"</string> + <string name="password" msgid="6738570945182936667">"mot de passe"</string> + <string name="sign_ins" msgid="4710739369149469208">"connexions"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Définir par défaut"</string> + <string name="use_once" msgid="9027366575315399714">"Utiliser une fois"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> clés d\'accès"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Un autre appareil"</string> + <string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string> + <string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Retourner à la page précédente"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Utiliser votre clé d\'accès enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Utiliser votre connexion enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choisir une connexion enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Se connecter d\'une autre manière"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Non merci"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuer"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Options de connexion"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pour <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestionnaires de mots de passe verrouillés"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toucher pour déverrouiller"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gérer les connexions"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"À partir d\'un autre appareil"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utiliser un autre appareil"</string> +</resources> diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml new file mode 100644 index 000000000000..f660ddeb70df --- /dev/null +++ b/packages/CredentialManager/res/values-fr/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Annuler"</string> + <string name="string_continue" msgid="1346732695941131882">"Continuer"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Créer ailleurs"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Enregistrer ailleurs"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Utiliser un autre appareil"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Enregistrer sur un autre appareil"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Une façon simple et sécurisée de vous connecter"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilisez votre empreinte digitale, votre visage ou le verrouillage de l\'écran pour vous connecter avec une clé d\'accès unique que vous ne pourrez pas oublier ni vous faire voler. En savoir plus"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Choisir où <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"enregistrer votre mot de passe"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"enregistrer vos informations de connexion"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Créer une clé d\'accès dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Enregistrer votre mot de passe dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Enregistrer vos informations de connexion dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Vous pouvez utiliser votre <xliff:g id="TYPE">%2$s</xliff:g> <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> sur n\'importe quel appareil. Il est enregistré dans <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> pour <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string> + <string name="passkey" msgid="632353688396759522">"clé d\'accès"</string> + <string name="password" msgid="6738570945182936667">"mot de passe"</string> + <string name="sign_ins" msgid="4710739369149469208">"connexions"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions ?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Définir par défaut"</string> + <string name="use_once" msgid="9027366575315399714">"Utiliser une fois"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> clés d\'accès"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Un autre appareil"</string> + <string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string> + <string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Revenir à la page précédente"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Utiliser votre clé d\'accès enregistrée pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Utiliser vos informations de connexion enregistrées pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Choisir des informations de connexion enregistrées pour <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Se connecter d\'une autre manière"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Non, merci"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuer"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Options de connexion"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pour <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestionnaires de mots de passe verrouillés"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Appuyer pour déverrouiller"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gérer les connexions"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Depuis un autre appareil"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Utiliser un autre appareil"</string> +</resources> diff --git a/packages/CredentialManager/res/values-gl/strings.xml b/packages/CredentialManager/res/values-gl/strings.xml new file mode 100644 index 000000000000..cacec21c801e --- /dev/null +++ b/packages/CredentialManager/res/values-gl/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string> + <string name="string_continue" msgid="1346732695941131882">"Continuar"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Crear noutro lugar"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Gardar noutro lugar"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un xeito fácil de iniciar sesión de forma segura"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa a impresión dixital, a cara ou o bloqueo de pantalla para iniciar sesión cunha clave de acceso única que non podes esquecer nin cha poden roubar. Máis información"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Escolle onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"gardar o contrasinal"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"gardar a información de inicio de sesión"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Queres crear unha clave de acceso en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Queres gardar o contrasinal en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Queres gardar a información de inicio de sesión en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Podes usar o teu <xliff:g id="TYPE">%2$s</xliff:g> de <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> en calquera dispositivo. Está gardado en <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"clave de acceso"</string> + <string name="password" msgid="6738570945182936667">"contrasinal"</string> + <string name="sign_ins" msgid="4710739369149469208">"métodos de inicio de sesión"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Queres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cada vez que inicies sesión?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Establecer como predeterminado"</string> + <string name="use_once" msgid="9027366575315399714">"Usar unha vez"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> claves de acceso"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> claves de acceso"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string> + <string name="other_password_manager" msgid="565790221427004141">"Outros xestores de contrasinais"</string> + <string name="close_sheet" msgid="1393792015338908262">"Pechar folla"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volver á páxina anterior"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Queres usar a clave de acceso gardada para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Queres usar o método de inicio de sesión gardado para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolle un método de inicio de sesión gardado para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Iniciar sesión doutra forma"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Non, grazas"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcións de inicio de sesión"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Xestores de contrasinais bloqueados"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toca para desbloquear"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Xestionar os métodos de inicio de sesión"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Doutro dispositivo"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar outro dispositivo"</string> +</resources> diff --git a/packages/CredentialManager/res/values-gu/strings.xml b/packages/CredentialManager/res/values-gu/strings.xml new file mode 100644 index 000000000000..7ac70aa91dcc --- /dev/null +++ b/packages/CredentialManager/res/values-gu/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"રદ કરો"</string> + <string name="string_continue" msgid="1346732695941131882">"ચાલુ રાખો"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"કોઈ અન્ય સ્થાન પર બનાવો"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"કોઈ અન્ય સ્થાન પર સાચવો"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"કોઈ અન્ય ડિવાઇસનો ઉપયોગ કરો"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"અન્ય ડિવાઇસ પર સાચવો"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"સલામત રીતે સાઇન ઇન કરવાની સરળ રીત"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ભૂલી ન શકાય કે ચોરાઈ ન જાય, તેવી કોઈ વિશિષ્ટ પાસકી વડે સાઇન ઇન કરવા માટે, તમારી ફિંગરપ્રિન્ટ, ચહેરો અથવા સ્ક્રીન લૉકનો ઉપયોગ કરો. વધુ જાણો"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ક્યાં સાચવવી છે, તે પસંદ કરો"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"તમારો પાસવર્ડ સાચવો"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"તમારી સાઇન-ઇનની માહિતી સાચવો"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"શું <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>માં કોઈ પાસકી બનાવીએ?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"શું તમારો પાસવર્ડ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>માં સાચવીએ?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"શું તમારી સાઇન-ઇનની માહિતી <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>માં સાચવીએ?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"તમે કોઈપણ ડિવાઇસ પર તમારા <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>નો ઉપયોગ કરી શકો છો. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> માટે <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>માં તેને સાચવવામાં આવે છે"</string> + <string name="passkey" msgid="632353688396759522">"પાસકી"</string> + <string name="password" msgid="6738570945182936667">"પાસવર્ડ"</string> + <string name="sign_ins" msgid="4710739369149469208">"સાઇન-ઇન"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"શું તમારા બધા સાઇન-ઇન માટે <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>નો ઉપયોગ કરીએ?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ડિફૉલ્ટ તરીકે સેટ કરો"</string> + <string name="use_once" msgid="9027366575315399714">"એકવાર ઉપયોગ કરો"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> પાસકી"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> પાસકી"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"કોઈ અન્ય ડિવાઇસ"</string> + <string name="other_password_manager" msgid="565790221427004141">"અન્ય પાસવર્ડ મેનેજર"</string> + <string name="close_sheet" msgid="1393792015338908262">"શીટ બંધ કરો"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"પાછલા પેજ પર પરત જાઓ"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે શું તમારી સાચવેલી પાસકીનો ઉપયોગ કરીએ?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે શું તમારા સાચવેલા સાઇન-ઇનનો ઉપયોગ કરીએ?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> માટે કોઈ સાચવેલું સાઇન-ઇન પસંદ કરો"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"કોઈ અન્ય રીતે સાઇન ઇન કરો"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ના, આભાર"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ચાલુ રાખો"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"સાઇન-ઇનના વિકલ્પો"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> માટે"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"લૉક કરેલા પાસવર્ડ મેનેજર"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"અનલૉક કરવા માટે ટૅપ કરો"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"સાઇન-ઇન મેનેજ કરો"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"કોઈ અન્ય ડિવાઇસમાંથી"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"કોઈ અન્ય ડિવાઇસનો ઉપયોગ કરો"</string> +</resources> diff --git a/packages/CredentialManager/res/values-hi/strings.xml b/packages/CredentialManager/res/values-hi/strings.xml new file mode 100644 index 000000000000..8d28e0f90abc --- /dev/null +++ b/packages/CredentialManager/res/values-hi/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"रद्द करें"</string> + <string name="string_continue" msgid="1346732695941131882">"जारी रखें"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"दूसरी जगह पर बनाएं"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"दूसरी जगह पर सेव करें"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"दूसरे डिवाइस का इस्तेमाल करें"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"दूसरे डिवाइस पर सेव करें"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"सुरक्षित तरीके से साइन इन करने का आसान तरीका"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"साइन इन करने के लिए फ़िंगरप्रिंट, फ़ेस या स्क्रीन लॉक जैसी यूनीक पासकी का इस्तेमाल करें. इन्हें, न तो भुलाया जा सकता है न ही चुराया जा सकता है. ज़्यादा जानें"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"चुनें कि <xliff:g id="CREATETYPES">%1$s</xliff:g> कहां पर सेव करना है"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"अपना पासवर्ड सेव करें"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"साइन इन से जुड़ी अपनी जानकारी सेव करें"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"क्या आपको <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> में पासकी बनानी है?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"क्या आपको अपना पासवर्ड <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> में सेव करना है?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"क्या आपको साइन इन करने से जुड़ी जानकारी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> में सेव करनी है?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"अपना <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> किसी भी डिवाइस पर इस्तेमाल किया जा सकता है. इसे <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> के लिए, <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> में सेव किया जाता है"</string> + <string name="passkey" msgid="632353688396759522">"पासकी"</string> + <string name="password" msgid="6738570945182936667">"पासवर्ड"</string> + <string name="sign_ins" msgid="4710739369149469208">"साइन इन"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"क्या आपको साइन इन से जुड़ी सारी जानकारी सेव करने के लिए, <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> का इस्तेमाल करना है?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"डिफ़ॉल्ट के तौर पर सेट करें"</string> + <string name="use_once" msgid="9027366575315399714">"इसका इस्तेमाल एक बार किया जा सकता है"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड और <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> पासकी"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> पासकी"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"दूसरा डिवाइस"</string> + <string name="other_password_manager" msgid="565790221427004141">"दूसरे पासवर्ड मैनेजर"</string> + <string name="close_sheet" msgid="1393792015338908262">"शीट बंद करें"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"पिछले पेज पर वापस जाएं"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"क्या आपको <xliff:g id="APP_NAME">%1$s</xliff:g> पर साइन इन करने के लिए, सेव की गई पासकी का इस्तेमाल करना है?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"क्या आपको <xliff:g id="APP_NAME">%1$s</xliff:g> पर साइन इन करने के लिए, सेव की गई जानकारी का इस्तेमाल करना है?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर साइन इन करने के लिए, सेव की गई जानकारी में से चुनें"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"किसी दूसरे तरीके से साइन इन करें"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"रहने दें"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"जारी रखें"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"साइन इन करने के विकल्प"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> के लिए"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"लॉक किए गए पासवर्ड मैनेजर"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"अनलॉक करने के लिए टैप करें"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"साइन इन करने की सुविधा को मैनेज करें"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"किसी दूसरे डिवाइस से"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"दूसरे डिवाइस का इस्तेमाल करें"</string> +</resources> diff --git a/packages/CredentialManager/res/values-hr/strings.xml b/packages/CredentialManager/res/values-hr/strings.xml new file mode 100644 index 000000000000..06db58393092 --- /dev/null +++ b/packages/CredentialManager/res/values-hr/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Odustani"</string> + <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Izradi na drugom mjestu"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Spremi na drugom mjestu"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Upotrijebite neki drugi uređaj"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Spremi na drugi uređaj"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednostavan način za sigurnu prijavu"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Prijavite se otiskom prsta, licem ili zaključavanjem zaslona kao jedinstvenim pristupnim ključem koji je nemoguće zaboraviti ili ukrasti. Saznajte više"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite mjesto za sljedeće: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"spremi zaporku"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"spremi podatke za prijavu"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Želite li izraditi pristupni ključ na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Želite li spremiti zaporku na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Želite li spremiti podatke o prijavi na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Aplikaciju <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> možete upotrijebiti na bilo kojem uređaju. Sprema se na uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> za: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"pristupni ključ"</string> + <string name="password" msgid="6738570945182936667">"zaporka"</string> + <string name="sign_ins" msgid="4710739369149469208">"prijave"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite li upotrebljavati uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve prijave?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Postavi kao zadano"</string> + <string name="use_once" msgid="9027366575315399714">"Upotrijebi jednom"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Broj zaporki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Broj zaporki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string> + <string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji zaporki"</string> + <string name="close_sheet" msgid="1393792015338908262">"Zatvaranje lista"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vratite se na prethodnu stranicu"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Želite li upotrijebiti spremljeni pristupni ključ za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Želite li upotrijebiti spremljene podatke za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Odaberite spremljene podatke za prijavu za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijavite se na neki drugi način"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, hvala"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Nastavi"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcije prijave"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za korisnika <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Upravitelji zaključanih zaporki"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Dodirnite za otključavanje"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljanje prijavama"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Na drugom uređaju"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Upotrijebite drugi uređaj"</string> +</resources> diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml new file mode 100644 index 000000000000..738de3a5482b --- /dev/null +++ b/packages/CredentialManager/res/values-hu/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Mégse"</string> + <string name="string_continue" msgid="1346732695941131882">"Folytatás"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Létrehozás másik helyen"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Mentés másik helyre"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Másik eszköz használata"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Mentés másik eszközre"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"A biztonságos bejelentkezés egyszerű módja"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Ujjlenyomatát, arcát vagy képernyőzárát használva egyedi azonosítókulccsal jelentkezhet be, amelyet nem lehet elfelejteni vagy ellopni. További információ."</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Válassza ki a(z) <xliff:g id="CREATETYPES">%1$s</xliff:g> helyét"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"jelszó mentése"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"bejelentkezési adatok mentése"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Létrehoz azonosítókulcsot a(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> szolgáltatásban?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Menti jelszavát a(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> szolgáltatásba?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Menti bejelentkezési adatait a(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> szolgáltatásba?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"A(z) <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> bármilyen eszközön használható. A(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> szolgáltatásba van mentve a következő számára: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"azonosítókulcs"</string> + <string name="password" msgid="6738570945182936667">"jelszó"</string> + <string name="sign_ins" msgid="4710739369149469208">"bejelentkezési adatok"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Szeretné a következőt használni az összes bejelentkezési adatához: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Beállítás alapértelmezettként"</string> + <string name="use_once" msgid="9027366575315399714">"Egyszeri használat"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> azonosítókulcs"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> azonosítókulcs"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Másik eszköz"</string> + <string name="other_password_manager" msgid="565790221427004141">"Egyéb jelszókezelők"</string> + <string name="close_sheet" msgid="1393792015338908262">"Munkalap bezárása"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Vissza az előző oldalra"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett azonosítókulcsot használni?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Szeretné a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazáshoz mentett bejelentkezési adatait használni?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Mentett bejelentkezési adatok választása a következő számára: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Bejelentkezés más módon"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Most nem"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Folytatás"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Bejelentkezési beállítások"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zárolt jelszókezelők"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Koppintson a feloldáshoz"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Bejelentkezési adatok kezelése"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Másik eszközről"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Másik eszköz használata"</string> +</resources> diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml new file mode 100644 index 000000000000..7320e6411248 --- /dev/null +++ b/packages/CredentialManager/res/values-hy/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Չեղարկել"</string> + <string name="string_continue" msgid="1346732695941131882">"Շարունակել"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Ստեղծել այլ տեղում"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Պահել այլ տեղում"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Օգտագործել այլ սարք"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Պահել մեկ այլ սարքում"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Մուտք գործելու անվտանգ և պարզ եղանակ"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Օգտագործեք ձեր մատնահետքը, դեմքը կամ էկրանի կողպումը՝ մուտք գործելու հաշիվ եզակի անցաբառի միջոցով, որը հնարավոր չէ կոտրել կամ մոռանալ։ Իմանալ ավելին"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Ընտրեք, թե որտեղ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"պահել գաղտնաբառը"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"պահել մուտքի տվյալները"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Ստեղծե՞լ անցաբառ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածում"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Պահե՞լ ձեր գաղտնաբառը <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածում"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Պահե՞լ ձեր մուտքի տվյալները <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածում"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Դուք կարող եք օգտագործել <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ցանկացած սարքում։ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>-ի տվյալները պահված են <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> հավելվածում։"</string> + <string name="passkey" msgid="632353688396759522">"անցաբառ"</string> + <string name="password" msgid="6738570945182936667">"գաղտնաբառ"</string> + <string name="sign_ins" msgid="4710739369149469208">"մուտք"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Միշտ մուտք գործե՞լ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածի միջոցով"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Նշել որպես կանխադրված"</string> + <string name="use_once" msgid="9027366575315399714">"Օգտագործել մեկ անգամ"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> գաղտնաբառ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> անցաբառ"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> գաղտնաբառ"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> անցաբառ"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Այլ սարք"</string> + <string name="other_password_manager" msgid="565790221427004141">"Գաղտնաբառերի այլ կառավարիչներ"</string> + <string name="close_sheet" msgid="1393792015338908262">"Փակել թերթը"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Անցնել նախորդ էջ"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Օգտագործե՞լ պահված անցաբառը <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Օգտագործե՞լ մուտքի պահված տվյալները <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Ընտրեք մուտքի պահված տվյալներ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի համար"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Մուտք գործել այլ եղանակով"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ոչ, շնորհակալություն"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Շարունակել"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Մուտքի տարբերակներ"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-ի համար"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Գաղտնաբառերի կողպված կառավարիչներ"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Հպեք՝ ապակողպելու համար"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Մուտքի կառավարում"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Մեկ այլ սարքից"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Օգտագործել այլ սարք"</string> +</resources> diff --git a/packages/CredentialManager/res/values-in/strings.xml b/packages/CredentialManager/res/values-in/strings.xml new file mode 100644 index 000000000000..827a4ff2b33d --- /dev/null +++ b/packages/CredentialManager/res/values-in/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Batal"</string> + <string name="string_continue" msgid="1346732695941131882">"Lanjutkan"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Buat di tempat lain"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Simpan ke tempat lain"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Gunakan perangkat lain"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Simpan ke perangkat lain"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Cara mudah untuk login dengan aman"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gunakan sidik jari, wajah, atau kunci layar untuk login dengan kunci sandi unik yang mudah diingat dan tidak dapat dicuri. Pelajari lebih lanjut"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Pilih tempat untuk <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"menyimpan sandi Anda"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"menyimpan info login Anda"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Buat kunci sandi di <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Simpan sandi ke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Simpan info login ke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Anda dapat menggunakan <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> di perangkat mana pun. Disimpan ke <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> untuk <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"kunci sandi"</string> + <string name="password" msgid="6738570945182936667">"sandi"</string> + <string name="sign_ins" msgid="4710739369149469208">"login"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua info login Anda?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Setel sebagai default"</string> + <string name="use_once" msgid="9027366575315399714">"Gunakan sekali"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> kunci sandi"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> kunci sandi"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Perangkat lain"</string> + <string name="other_password_manager" msgid="565790221427004141">"Pengelola sandi lainnya"</string> + <string name="close_sheet" msgid="1393792015338908262">"Tutup sheet"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kembali ke halaman sebelumnya"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gunakan kunci sandi tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gunakan info login tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pilih info login tersimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Login dengan cara lain"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Lain kali"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Lanjutkan"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opsi login"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Untuk <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Pengelola sandi terkunci"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Ketuk untuk membuka kunci"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Kelola login"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Dari perangkat lain"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gunakan perangkat lain"</string> +</resources> diff --git a/packages/CredentialManager/res/values-is/strings.xml b/packages/CredentialManager/res/values-is/strings.xml new file mode 100644 index 000000000000..c52e5f7815f5 --- /dev/null +++ b/packages/CredentialManager/res/values-is/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Hætta við"</string> + <string name="string_continue" msgid="1346732695941131882">"Áfram"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Búa til annarsstaðar"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Vista annarsstaðar"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Nota annað tæki"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Vista í öðru tæki"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Einföld leið við örugga innskráningu"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Notaðu fingrafar, andlit eða skjálás til að skrá þig inn með einkvæmum aðgangslykli sem ekki er hægt að gleyma eða stela. Nánar"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Veldu hvar á að <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"vistaðu aðgangsorðið"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"vistaðu innskráningarupplýsingarnar"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Búa til aðgangslykil í <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vista aðgangsorð í <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vista innskráningarupplýsingar í <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Þú getur notað <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> í hvaða tæki sem er. Það er vistað á <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> fyrir <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"aðgangslykill"</string> + <string name="password" msgid="6738570945182936667">"aðgangsorð"</string> + <string name="sign_ins" msgid="4710739369149469208">"innskráningar"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Nota <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> fyrir allar innskráningar?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Stilla sem sjálfgefið"</string> + <string name="use_once" msgid="9027366575315399714">"Nota einu sinni"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> aðgangsorð, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> aðgangslyklar"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> aðgangsorð"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> aðgangslyklar"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Annað tæki"</string> + <string name="other_password_manager" msgid="565790221427004141">"Önnur aðgangsorðastjórnun"</string> + <string name="close_sheet" msgid="1393792015338908262">"Loka blaði"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Fara aftur á fyrri síðu"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Notað vistaðan aðgangslykil fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Nota vistaða innskráningu fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Veldu vistaða innskráningu fyrir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Skrá inn með öðrum hætti"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nei takk"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Áfram"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Innskráningarkostir"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Fyrir: <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Læst aðgangsorðastjórnun"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Ýttu til að opna"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Stjórna innskráningu"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Úr öðru tæki"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Nota annað tæki"</string> +</resources> diff --git a/packages/CredentialManager/res/values-it/strings.xml b/packages/CredentialManager/res/values-it/strings.xml new file mode 100644 index 000000000000..a06135e0ac0f --- /dev/null +++ b/packages/CredentialManager/res/values-it/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Annulla"</string> + <string name="string_continue" msgid="1346732695941131882">"Continua"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Crea in un altro luogo"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Salva in un altro luogo"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Usa un altro dispositivo"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Salva su un altro dispositivo"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un modo semplice per accedere in sicurezza"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa l\'impronta digitale, il volto o il blocco schermo per accedere con una passkey unica che non può essere dimenticata o rubata. Scopri di più"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Scegli dove <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"salva la password"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"salva le tue informazioni di accesso"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vuoi creare una passkey su <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vuoi salvare la password su <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vuoi salvare le informazioni di accesso su <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Puoi utilizzare <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> su qualsiasi dispositivo. Questa informazione è salvata su <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> per <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"passkey"</string> + <string name="password" msgid="6738570945182936667">"password"</string> + <string name="sign_ins" msgid="4710739369149469208">"accessi"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vuoi usare <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per tutti gli accessi?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Imposta come valore predefinito"</string> + <string name="use_once" msgid="9027366575315399714">"Usa una volta"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkey"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Un altro dispositivo"</string> + <string name="other_password_manager" msgid="565790221427004141">"Altri gestori delle password"</string> + <string name="close_sheet" msgid="1393792015338908262">"Chiudi il foglio"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Torna alla pagina precedente"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vuoi usare la passkey salvata per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vuoi usare l\'accesso salvato per <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Scegli un accesso salvato per <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Accedi in un altro modo"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"No, grazie"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continua"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opzioni di accesso"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Per <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestori delle password bloccati"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tocca per sbloccare"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestisci gli accessi"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Da un altro dispositivo"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usa un dispositivo diverso"</string> +</resources> diff --git a/packages/CredentialManager/res/values-iw/strings.xml b/packages/CredentialManager/res/values-iw/strings.xml new file mode 100644 index 000000000000..e9c6adb744c7 --- /dev/null +++ b/packages/CredentialManager/res/values-iw/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"ביטול"</string> + <string name="string_continue" msgid="1346732695941131882">"המשך"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"יצירה במקום אחר"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"שמירה במקום אחר"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"שימוש במכשיר אחר"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"שמירה במכשיר אחר"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"דרך פשוטה להיכנס לחשבון בבטחה"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"אפשר להשתמש בטביעת אצבע, בזיהוי פנים או בנעילת מסך כדי להיכנס לחשבון עם מפתח גישה ייחודי שאי אפשר לשכוח או לגנוב אותו. מידע נוסף"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"צריך לבחור לאן <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"שמירת הסיסמה שלך"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"שמירת פרטי הכניסה שלך"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"ליצור מפתח גישה ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"לשמור את הסיסמה ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"לשמור את פרטי הכניסה ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"אפשר להשתמש ב-<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> מסוג <xliff:g id="TYPE">%2$s</xliff:g> בכל מכשיר. הוא שמור ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> ל-<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"מפתח גישה"</string> + <string name="password" msgid="6738570945182936667">"סיסמה"</string> + <string name="sign_ins" msgid="4710739369149469208">"פרטי כניסה"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"להשתמש ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> בכל הכניסות?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"הגדרה כברירת מחדל"</string> + <string name="use_once" msgid="9027366575315399714">"שימוש פעם אחת"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> מפתחות גישה"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> מפתחות גישה"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"מכשיר אחר"</string> + <string name="other_password_manager" msgid="565790221427004141">"מנהלי סיסמאות אחרים"</string> + <string name="close_sheet" msgid="1393792015338908262">"סגירת הגיליון"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"חזרה לדף הקודם"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"להשתמש במפתח גישה שנשמר עבור <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"להשתמש בפרטי הכניסה שנשמרו עבור <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"בחירת פרטי כניסה שמורים עבור <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"כניסה בדרך אחרת"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"לא תודה"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"המשך"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"אפשרויות כניסה לחשבון"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"עבור <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"מנהלי סיסמאות נעולים"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"יש להקיש כדי לבטל את הנעילה"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ניהול כניסות"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ממכשיר אחר"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"צריך להשתמש במכשיר אחר"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml new file mode 100644 index 000000000000..8e448eb5d420 --- /dev/null +++ b/packages/CredentialManager/res/values-ja/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"キャンセル"</string> + <string name="string_continue" msgid="1346732695941131882">"続行"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"別の場所で作成"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"別の場所に保存"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"別のデバイスを使用"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"他のデバイスに保存"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"安全にログインする簡単な方法"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"忘れたり盗まれたりする可能性がある一意のパスキーと合わせて、ログインに指紋認証、顔認証、画面ロックを使用できます。詳細"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> の保存場所の選択"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"パスワードを保存"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"ログイン情報を保存"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> でパスキーを作成しますか?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> にパスワードを保存しますか?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> にログイン情報を保存しますか?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> の <xliff:g id="TYPE">%2$s</xliff:g> はどのデバイスでも使用できます。<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> の <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> に保存されます"</string> + <string name="passkey" msgid="632353688396759522">"パスキー"</string> + <string name="password" msgid="6738570945182936667">"パスワード"</string> + <string name="sign_ins" msgid="4710739369149469208">"ログイン"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"ログインのたびに <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> を使用しますか?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"デフォルトに設定"</string> + <string name="use_once" msgid="9027366575315399714">"1 回使用"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード、<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 件のパスキー"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 件のパスキー"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"別のデバイス"</string> + <string name="other_password_manager" msgid="565790221427004141">"他のパスワード マネージャー"</string> + <string name="close_sheet" msgid="1393792015338908262">"シートを閉じます"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"前のページに戻ります"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したパスキーを使用しますか?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したログイン情報を使用しますか?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> の保存したログイン情報の選択"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"別の方法でログイン"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"利用しない"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"続行"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ログイン オプション"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> 用"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"パスワード マネージャー ロック中"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"タップしてロック解除"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ログインを管理"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"別のデバイスを使う"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"別のデバイスを使用"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml new file mode 100644 index 000000000000..853ea19fa0e9 --- /dev/null +++ b/packages/CredentialManager/res/values-ka/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"გაუქმება"</string> + <string name="string_continue" msgid="1346732695941131882">"გაგრძელება"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"სხვა სივრცეში შექმნა"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"სხვა სივრცეში შენახვა"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"სხვა მოწყობილობის გამოყენება"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"სხვა მოწყობილობაზე შენახვა"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"უსაფრთხოდ შესვლის მარტივი გზა"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"გამოიყენეთ თქვენი თითის ანაბეჭდი, სახის ამოცნობა და ეკრანის დაბლოკვა სისტემაში უნიკალური წვდომის გასაღებით შესასვლელად, რომლის დავიწყება ან მოპარვა შეუძლებელია. შეიტყვეთ მეტი"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"აირჩიეთ, სად უნდა <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"შეინახეთ თქვენი პაროლი"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"შეინახეთ თქვენი სისტემაში შესვლის ინფორმაცია"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"გსურთ წვდომის გასაღების შექმნა <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-ში?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"გსურთ თქვენი პაროლის შენახვა <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-ში?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"გსურთ თქვენი სისტემაში შესვლის მონაცემების შენახვა <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-ში?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"შეგიძლიათ გამოიყენოთ თქვენი <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ნებისმიერ მოწყობილობაზე. ის შეინახება <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>-ზე <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>-თვის"</string> + <string name="passkey" msgid="632353688396759522">"წვდომის გასაღები"</string> + <string name="password" msgid="6738570945182936667">"პაროლი"</string> + <string name="sign_ins" msgid="4710739369149469208">"სისტემაში შესვლა"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"გსურთ, გამოიყენოთ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> სისტემაში ყველა შესვლისთვის?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ნაგულისხმევად დაყენება"</string> + <string name="use_once" msgid="9027366575315399714">"ერთხელ გამოყენება"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლი, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> წვდომის გასაღები"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლი"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> წვდომის გასაღები"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"სხვა მოწყობილობა"</string> + <string name="other_password_manager" msgid="565790221427004141">"პაროლების სხვა მმართველები"</string> + <string name="close_sheet" msgid="1393792015338908262">"ფურცლის დახურვა"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"წინა გვერდზე დაბრუნება"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"გსურთ თქვენი დამახსოვრებული წვდომის გასაღების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"გსურთ თქვენი დამახსოვრებული სისტემაში შესვლის მონაცემების გამოყენება აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"აირჩიეთ სისტემაში შესვლის ინფორმაცია აპისთვის: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"სხვა ხერხით შესვლა"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"არა, გმადლობთ"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"გაგრძელება"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"სისტემაში შესვლის ვარიანტები"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-ისთვის"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ჩაკეტილი პაროლის მმართველები"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"შეეხეთ განსაბლოკად"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"სისტემაში შესვლის მონაცემების მართვა"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"სხვა მოწყობილობიდან"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"გამოიყენეთ სხვა მოწყობილობა"</string> +</resources> diff --git a/packages/CredentialManager/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml new file mode 100644 index 000000000000..2271533e650d --- /dev/null +++ b/packages/CredentialManager/res/values-kk/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Бас тарту"</string> + <string name="string_continue" msgid="1346732695941131882">"Жалғастыру"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Басқа орында жасау"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Басқа орынға сақтау"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Басқа құрылғыны пайдалану"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Қауіпсіз кірудің оңай жолы"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Ұмытылмайтын немесе ұрланбайтын бірегей кіру кілтінің көмегімен кіру үшін саусақ ізін, бетті анықтау функциясын немесе экран құлпын пайдаланыңыз. Толық ақпарат"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> таңдау"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"құпия сөзді сақтау"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"тіркелу деректерін сақтау"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> қолданбасында кіру кілті жасалсын ба?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> қолданбасына құпия сөз сақталсын ба?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> қолданбасына тіркелу деректері сақталсын ба?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> кез келген құрылғыда пайдаланылады. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> тіркелу деректері <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> қолданбасында сақталады."</string> + <string name="passkey" msgid="632353688396759522">"кіру кілті"</string> + <string name="password" msgid="6738570945182936667">"құпия сөз"</string> + <string name="sign_ins" msgid="4710739369149469208">"кіру әрекеттері"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Барлық кіру әрекеті үшін <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> пайдаланылсын ба?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Әдепкі етіп орнату"</string> + <string name="use_once" msgid="9027366575315399714">"Бір рет пайдалану"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> құпия сөз, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> кіру кілті"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> құпия сөз"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> кіру кілті"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Басқа құрылғы"</string> + <string name="other_password_manager" msgid="565790221427004141">"Басқа құпия сөз менеджерлері"</string> + <string name="close_sheet" msgid="1393792015338908262">"Парақты жабу"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Алдыңғы бетке оралу"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған кіру кілті пайдаланылсын ба?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған тіркелу деректері пайдаланылсын ба?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> үшін сақталған тіркелу деректерін таңдаңыз"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Басқаша кіру"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Жоқ, рақмет"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Жалғастыру"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Кіру опциялары"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> үшін"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Құлыпталған құпия сөз менеджерлері"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Құлыпты ашу үшін түртіңіз."</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Кіру әрекеттерін басқару"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Басқа құрылғыдан жасау"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Басқа құрылғыны пайдалану"</string> +</resources> diff --git a/packages/CredentialManager/res/values-km/strings.xml b/packages/CredentialManager/res/values-km/strings.xml new file mode 100644 index 000000000000..d51781009950 --- /dev/null +++ b/packages/CredentialManager/res/values-km/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"បោះបង់"</string> + <string name="string_continue" msgid="1346732695941131882">"បន្ត"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"បង្កើតនៅកន្លែងផ្សេងទៀត"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"រក្សាទុកក្នុងកន្លែងផ្សេងទៀត"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"ប្រើឧបករណ៍ផ្សេងទៀត"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"រក្សាទុកទៅក្នុងឧបករណ៍ផ្សេង"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"វិធីដ៏សាមញ្ញ ដើម្បីចូលគណនីដោយសុវត្ថិភាព"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ប្រើស្នាមម្រាមដៃ មុខ ឬការចាក់សោអេក្រង់របស់អ្នក ដើម្បីចូលគណនីដោយប្រើកូដសម្ងាត់ខុសប្លែកពីគេដែលមិនអាចភ្លេច ឬត្រូវគេលួច។ ស្វែងយល់បន្ថែម"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"ជ្រើសរើសកន្លែងដែលត្រូវ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"រក្សាទុកពាក្យសម្ងាត់របស់អ្នក"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"រក្សាទុកព័ត៌មានចូលគណនីរបស់អ្នក"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"បង្កើតកូដសម្ងាត់នៅក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ឬ?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"រក្សាទុកពាក្យសម្ងាត់របស់អ្នកក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ឬ?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"រក្សាទុកព័ត៌មានចូលគណនីរបស់អ្នកក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ឬ?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"អ្នកអាចប្រើ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> របស់អ្នកនៅលើឧបករណ៍ណាក៏បាន។ វាត្រូវបានរក្សាទុកក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> សម្រាប់ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"កូដសម្ងាត់"</string> + <string name="password" msgid="6738570945182936667">"ពាក្យសម្ងាត់"</string> + <string name="sign_ins" msgid="4710739369149469208">"ការចូលគណនី"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"ប្រើ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> សម្រាប់ការចូលគណនីទាំងអស់របស់អ្នកឬ?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"កំណត់ជាលំនាំដើម"</string> + <string name="use_once" msgid="9027366575315399714">"ប្រើម្ដង"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"ពាក្យសម្ងាត់ <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> កូដសម្ងាត់ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"ពាក្យសម្ងាត់ <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"កូដសម្ងាត់ <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"ឧបករណ៍ផ្សេងទៀត"</string> + <string name="other_password_manager" msgid="565790221427004141">"កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ផ្សេងទៀត"</string> + <string name="close_sheet" msgid="1393792015338908262">"បិទសន្លឹក"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ត្រឡប់ទៅទំព័រមុនវិញ"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ប្រើកូដសម្ងាត់ដែលបានរក្សាទុករបស់អ្នកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ប្រើការចូលគណនីដែលបានរក្សាទុករបស់អ្នកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"ជ្រើសរើសការចូលគណនីដែលបានរក្សាទុកសម្រាប់ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ចូលគណនីដោយប្រើវិធីផ្សេងទៀត"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ទេ អរគុណ"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"បន្ត"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ជម្រើសចូលគណនី"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"សម្រាប់ <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ដែលបានចាក់សោ"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ចុចដើម្បីដោះសោ"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"គ្រប់គ្រងការចូលគណនី"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ពីឧបករណ៍ផ្សេងទៀត"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ប្រើឧបករណ៍ផ្សេង"</string> +</resources> diff --git a/packages/CredentialManager/res/values-kn/strings.xml b/packages/CredentialManager/res/values-kn/strings.xml new file mode 100644 index 000000000000..763f8eef9203 --- /dev/null +++ b/packages/CredentialManager/res/values-kn/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"ರದ್ದುಗೊಳಿಸಿ"</string> + <string name="string_continue" msgid="1346732695941131882">"ಮುಂದುವರಿಸಿ"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"ಮತ್ತೊಂದು ಸ್ಥಳದಲ್ಲಿ ರಚಿಸಿ"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"ಮತ್ತೊಂದು ಸ್ಥಳದಲ್ಲಿ ಉಳಿಸಿ"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"ಬೇರೊಂದು ಸಾಧನವನ್ನು ಬಳಸಿ"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"ಬೇರೊಂದು ಸಾಧನದಲ್ಲಿ ಉಳಿಸಿ"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"ಸುರಕ್ಷಿತವಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡುವ ಸುಲಭ ವಿಧಾನ"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ಮರೆಯಲಾಗದ ಅಥವಾ ಕದಿಯಲಾಗದ ಅನನ್ಯ ಪಾಸ್ಕೀ ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಲು ನಿಮ್ಮ ಫಿಂಗರ್ಪ್ರಿಂಟ್, ಫೇಸ್ ಲಾಕ್ ಅಥವಾ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಬಳಸಿ. ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ಅನ್ನು ಎಲ್ಲಿ ಉಳಿಸಬೇಕು ಎಂದು ಆಯ್ಕೆಮಾಡಿ"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"ನಿಮ್ಮ ಪಾಸ್ವರ್ಡ್ ಉಳಿಸಿ"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"ನಿಮ್ಮ ಸೈನ್-ಇನ್ ಮಾಹಿತಿ ಉಳಿಸಿ"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ನಲ್ಲಿ ಪಾಸ್ಕೀ ರಚಿಸಬೇಕೆ?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"ನಿಮ್ಮ ಪಾಸ್ವರ್ಡ್ ಅನ್ನು <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಗೆ ಉಳಿಸಬೇಕೆ?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ನಿಮ್ಮ ಸೈನ್ ಇನ್ ಮಾಹಿತಿಯನ್ನು <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಗೆ ಉಳಿಸಬೇಕೆ?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"ನೀವು ಯಾವುದೇ ಸಾಧನದಲ್ಲಿ ನಿಮ್ಮ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ಅನ್ನು ಬಳಸಬಹುದು. ಇದನ್ನು <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ಗಾಗಿ <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> ಗೆ ಉಳಿಸಲಾಗಿದೆ"</string> + <string name="passkey" msgid="632353688396759522">"ಪಾಸ್ಕೀ"</string> + <string name="password" msgid="6738570945182936667">"ಪಾಸ್ವರ್ಡ್"</string> + <string name="sign_ins" msgid="4710739369149469208">"ಸೈನ್-ಇನ್ಗಳು"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"ನಿಮ್ಮ ಎಲ್ಲಾ ಸೈನ್-ಇನ್ಗಳಿಗಾಗಿ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಅನ್ನು ಬಳಸುವುದೇ?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ಡೀಫಾಲ್ಟ್ ಆಗಿ ಸೆಟ್ ಮಾಡಿ"</string> + <string name="use_once" msgid="9027366575315399714">"ಒಂದು ಬಾರಿ ಬಳಸಿ"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್ವರ್ಡ್ಗಳು, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ಪಾಸ್ಕೀಗಳು"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್ವರ್ಡ್ಗಳು"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ಪಾಸ್ಕೀಗಳು"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"ಮತ್ತೊಂದು ಸಾಧನ"</string> + <string name="other_password_manager" msgid="565790221427004141">"ಇತರ ಪಾಸ್ವರ್ಡ್ ನಿರ್ವಾಹಕರು"</string> + <string name="close_sheet" msgid="1393792015338908262">"ಶೀಟ್ ಮುಚ್ಚಿರಿ"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ಹಿಂದಿನ ಪುಟಕ್ಕೆ ಹಿಂದಿರುಗಿ"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ನಿಮ್ಮ ಪಾಸ್ಕೀ ಅನ್ನು ಬಳಸಬೇಕೆ?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ನಿಮ್ಮ ಸೈನ್-ಇನ್ ಅನ್ನು ಬಳಸಬೇಕೆ?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಗಾಗಿ ಉಳಿಸಲಾದ ಸೈನ್-ಇನ್ ಮಾಹಿತಿಯನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ಬೇರೆ ವಿಧಾನದಲ್ಲಿ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ಬೇಡ"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ಮುಂದುವರಿಸಿ"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ಸೈನ್ ಇನ್ ಆಯ್ಕೆಗಳು"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> ಗಾಗಿ"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ಪಾಸ್ವರ್ಡ್ ನಿರ್ವಾಹಕರನ್ನು ಲಾಕ್ ಮಾಡಲಾಗಿದೆ"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ಅನ್ಲಾಕ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ಸೈನ್-ಇನ್ಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ಮತ್ತೊಂದು ಸಾಧನದಿಂದ"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ಬೇರೆ ಸಾಧನವನ್ನು ಬಳಸಿ"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml new file mode 100644 index 000000000000..246790d1c774 --- /dev/null +++ b/packages/CredentialManager/res/values-ko/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"취소"</string> + <string name="string_continue" msgid="1346732695941131882">"계속"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"다른 위치에 만들기"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"다른 위치에 저장"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"다른 기기 사용"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"다른 기기에 저장"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"안전하게 로그인하는 간단한 방법"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"지문, 얼굴 인식 또는 화면 잠금을 통해 잊어버리거나 분실할 염려가 없는 고유한 패스키로 로그인하세요. 자세히 알아보기"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> 작업을 위한 위치 선택"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"비밀번호 저장"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"로그인 정보 저장"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>에 패스키를 만드시겠습니까?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>에 비밀번호를 저장하시겠습니까?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>에 로그인 정보를 저장하시겠습니까?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"기기에서 <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>을(를) 사용할 수 있습니다. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>을(를) 위해 <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>에 저장되어 있습니다."</string> + <string name="passkey" msgid="632353688396759522">"패스키"</string> + <string name="password" msgid="6738570945182936667">"비밀번호"</string> + <string name="sign_ins" msgid="4710739369149469208">"로그인 정보"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"모든 로그인에 <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>을(를) 사용하시겠습니까?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"기본값으로 설정"</string> + <string name="use_once" msgid="9027366575315399714">"한 번 사용"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"비밀번호 <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>개, 패스키 <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>개"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"비밀번호 <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>개"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"패스키 <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>개"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"다른 기기"</string> + <string name="other_password_manager" msgid="565790221427004141">"기타 비밀번호 관리자"</string> + <string name="close_sheet" msgid="1393792015338908262">"시트 닫기"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"이전 페이지로 돌아가기"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 패스키를 사용하시겠습니까?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 로그인 정보를 사용하시겠습니까?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱용 저장된 로그인 정보 선택"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"다른 방법으로 로그인"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"아니요"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"계속"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"로그인 옵션"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>님의 로그인 정보"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"잠긴 비밀번호 관리자"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"잠금 해제하려면 탭하세요."</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"로그인 관리"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"다른 기기에서"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"다른 기기 사용"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml new file mode 100644 index 000000000000..3dc7deaabd42 --- /dev/null +++ b/packages/CredentialManager/res/values-ky/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Жок"</string> + <string name="string_continue" msgid="1346732695941131882">"Улантуу"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Башка жерде түзүү"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Башка жерге сактоо"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Башка түзмөк колдонуу"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Коопсуз кирүүнүн жөнөкөй жолу"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Унутуп калууга же уурдатууга мүмкүн эмес болгон уникалдуу ачкыч менен манжа изин, жүзүнөн таанып ачуу же экранды кулпулоо функцияларын колдонуп өзүңүздү ырастай аласыз. Кененирээк"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> үчүн жер тандаңыз"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"сырсөзүңүздү сактаңыз"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"кирүү маалыматын сактаңыз"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> колдонмосунда мүмкүндүк алуу ачкычын түзөсүзбү?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Сырсөзүңүздү <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> колдонмосунда сактайсызбы?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Кирүү маалыматын <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> колдонмосунда сактайсызбы?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> каалаган түзмөктө колдонулат. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> байланыштуу маалыматтар <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> колдонмосунда сакталат"</string> + <string name="passkey" msgid="632353688396759522">"мүмкүндүк алуу ачкычы"</string> + <string name="password" msgid="6738570945182936667">"сырсөз"</string> + <string name="sign_ins" msgid="4710739369149469208">"кирүүлөр"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> бардык аккаунттарга кирүү үчүн колдонулсунбу?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Демейки катары коюу"</string> + <string name="use_once" msgid="9027366575315399714">"Бир жолу колдонуу"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> сырсөз, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> мүмкүндүк алуу ачкычы"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> сырсөз"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> мүмкүндүк алуу ачкычы"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Башка түзмөк"</string> + <string name="other_password_manager" msgid="565790221427004141">"Башка сырсөздөрдү башкаргычтар"</string> + <string name="close_sheet" msgid="1393792015338908262">"Баракты жабуу"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Мурунку бетке кайтуу"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган мүмкүндүк алуу ачкычын колдоносузбу?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн сакталган кирүү параметрин колдоносузбу?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> үчүн кирүү маалыматын тандаңыз"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Башка жол менен кирүү"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Жок, рахмат"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Улантуу"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Аккаунтка кирүү параметрлери"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> үчүн"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Кулпуланган сырсөздөрдү башкаргычтар"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Кулпусун ачуу үчүн таптаңыз"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Кирүү параметрлерин тескөө"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Башка түзмөктөн"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Башка түзмөктү колдонуу"</string> +</resources> diff --git a/packages/CredentialManager/res/values-lo/strings.xml b/packages/CredentialManager/res/values-lo/strings.xml new file mode 100644 index 000000000000..8efc8a8d31f1 --- /dev/null +++ b/packages/CredentialManager/res/values-lo/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"ຍົກເລີກ"</string> + <string name="string_continue" msgid="1346732695941131882">"ສືບຕໍ່"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"ສ້າງໃນບ່ອນອື່ນ"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"ບັນທຶກໃສ່ບ່ອນອື່ນ"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"ໃຊ້ອຸປະກອນອື່ນ"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"ບັນທຶກໃສ່ອຸປະກອນອື່ນ"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"ວິທີງ່າຍໆໃນການເຂົ້າສູ່ລະບົບຢ່າງປອດໄພ"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ໃຊ້ລາຍນິ້ວມື, ໃບໜ້າ ຫຼື ລັອກໜ້າຈໍຂອງທ່ານເພື່ອເຂົ້າສູ່ລະບົບດ້ວຍກະແຈຜ່ານທີ່ບໍ່ຊ້ຳກັນເພື່ອບໍ່ໃຫ້ລືມ ຫຼື ຖືກລັກໄດ້. ສຶກສາເພີ່ມເຕີມ"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"ເລືອກບ່ອນທີ່ຈະ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"ບັນທຶກລະຫັດຜ່ານຂອງທ່ານ"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"ບັນທຶກຂໍ້ມູນການເຂົ້າສູ່ລະບົບຂອງທ່ານ"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"ສ້າງກະແຈຜ່ານໃນ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ບໍ?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"ບັນທຶກລະຫັດຜ່ານຂອງທ່ານໃສ່ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ບໍ?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ບັນທຶກຂໍ້ມູນການເຂົ້າສູ່ລະບົບຂອງທ່ານໃສ່ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ບໍ?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"ທ່ານສາມາດໃຊ້ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ຂອງທ່ານຢູ່ອຸປະກອນໃດກໍໄດ້. ມັນຈະຖືກບັນທຶກໃສ່ <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> ສຳລັບ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"ກະແຈຜ່ານ"</string> + <string name="password" msgid="6738570945182936667">"ລະຫັດຜ່ານ"</string> + <string name="sign_ins" msgid="4710739369149469208">"ການເຂົ້າສູ່ລະບົບ"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"ໃຊ້ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ສຳລັບການເຂົ້າສູ່ລະບົບທັງໝົດຂອງທ່ານບໍ?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນ"</string> + <string name="use_once" msgid="9027366575315399714">"ໃຊ້ເທື່ອດຽວ"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ລະຫັດຜ່ານ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ກະແຈຜ່ານ"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ລະຫັດຜ່ານ"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ກະແຈຜ່ານ"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"ອຸປະກອນອື່ນ"</string> + <string name="other_password_manager" msgid="565790221427004141">"ຕົວຈັດການລະຫັດຜ່ານອື່ນໆ"</string> + <string name="close_sheet" msgid="1393792015338908262">"ປິດຊີດ"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ກັບຄືນໄປຫາໜ້າກ່ອນໜ້ານີ້"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ໃຊ້ກະແຈຜ່ານທີ່ບັນທຶກໄວ້ຂອງທ່ານສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ໃຊ້ການເຂົ້າສູ່ລະບົບທີ່ບັນທຶກໄວ້ຂອງທ່ານສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"ເລືອກການເຂົ້າສູ່ລະບົບທີ່ບັນທຶກໄວ້ສຳລັບ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ເຂົ້າສູ່ລະບົບດ້ວຍວິທີອື່ນ"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ບໍ່, ຂອບໃຈ"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ສືບຕໍ່"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ຕົວເລືອກການເຂົ້າສູ່ລະບົບ"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"ສຳລັບ <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ຕົວຈັດການລະຫັດຜ່ານທີ່ລັອກໄວ້"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ແຕະເພື່ອປົດລັອກ"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ຈັດການການເຂົ້າສູ່ລະບົບ"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ຈາກອຸປະກອນອື່ນ"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ໃຊ້ອຸປະກອນອື່ນ"</string> +</resources> diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml new file mode 100644 index 000000000000..02d37837ad43 --- /dev/null +++ b/packages/CredentialManager/res/values-lt/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Atšaukti"</string> + <string name="string_continue" msgid="1346732695941131882">"Tęsti"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Sukurti kitoje vietoje"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Išsaugoti kitoje vietoje"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Naudoti kitą įrenginį"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Išsaugoti kitame įrenginyje"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Paprastas saugaus prisijungimo metodas"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Naudodami piršto atspaudą, veidą ar ekrano užraktą prisijunkite su unikaliu „passkey“, kurio neįmanoma pamiršti ar pavogti. Sužinokite daugiau"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Pasirinkite, kur <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"išsaugoti slaptažodį"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"išsaugoti prisijungimo informaciją"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Sukurti „passkey“ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Išsaugoti slaptažodį <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Išsaugoti prisijungimo informaciją <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Galite naudoti <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> bet kuriame įrenginyje. Jis išsaugomas <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> (<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>)"</string> + <string name="passkey" msgid="632353688396759522">"„passkey“"</string> + <string name="password" msgid="6738570945182936667">"slaptažodis"</string> + <string name="sign_ins" msgid="4710739369149469208">"prisijungimo informacija"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Naudoti <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> visada prisijungiant?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Nustatyti kaip numatytąjį"</string> + <string name="use_once" msgid="9027366575315399714">"Naudoti vieną kartą"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, „passkey“: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"„passkey“: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Kitas įrenginys"</string> + <string name="other_password_manager" msgid="565790221427004141">"Kitos slaptažodžių tvarkyklės"</string> + <string name="close_sheet" msgid="1393792015338908262">"Uždaryti lapą"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Grįžti į ankstesnį puslapį"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Naudoti išsaugotą „passkey“ programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Naudoti išsaugotą prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pasirinkite išsaugotą prisijungimo informaciją programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prisijungti kitu būdu"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, ačiū"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Tęsti"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Prisijungimo parinktys"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Skirta <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Užrakintos slaptažodžių tvarkyklės"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Palieskite, kad atrakintumėte"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Tvarkyti prisijungimo informaciją"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Naudojant kitą įrenginį"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Naudoti kitą įrenginį"</string> +</resources> diff --git a/packages/CredentialManager/res/values-lv/strings.xml b/packages/CredentialManager/res/values-lv/strings.xml new file mode 100644 index 000000000000..cbea91aa917c --- /dev/null +++ b/packages/CredentialManager/res/values-lv/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Atcelt"</string> + <string name="string_continue" msgid="1346732695941131882">"Turpināt"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Izveidot citur"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Saglabāt citur"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Izmantot citu ierīci"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Vienkāršs veids, kā droši pierakstīties"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Izmantojiet pirksta nospiedumu, autorizāciju pēc sejas vai ekrāna bloķēšanu, lai pierakstītos ar unikālu piekļuves atslēgu, ko nevar aizmirst vai nozagt. Uzziniet vairāk."</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Izvēlieties, kur: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"saglabāt paroli"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"saglabāt pierakstīšanās informāciju"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vai izveidot piekļuves atslēgu šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vai saglabāt paroli šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vai saglabāt pierakstīšanās informāciju šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Jebkurā ierīcē varat izmantot šo: <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>. Tas tiek saglabāt šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>; mērķis: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string> + <string name="passkey" msgid="632353688396759522">"piekļuves atslēga"</string> + <string name="password" msgid="6738570945182936667">"parole"</string> + <string name="sign_ins" msgid="4710739369149469208">"pierakstīšanās informācija"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vai vienmēr izmantot <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>, lai pierakstītos?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Iestatīt kā noklusējumu"</string> + <string name="use_once" msgid="9027366575315399714">"Izmantot vienreiz"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> paroles, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> piekļuves atslēgas"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> paroles"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> piekļuves atslēgas"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Cita ierīce"</string> + <string name="other_password_manager" msgid="565790221427004141">"Citi paroļu pārvaldnieki"</string> + <string name="close_sheet" msgid="1393792015338908262">"Aizvērt lapu"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Atgriezties iepriekšējā lapā"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vai izmantot saglabāto piekļuves atslēgu lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vai izmantot saglabāto pierakstīšanās informāciju lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Saglabātas pierakstīšanās informācijas izvēle lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Pierakstīties citā veidā"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nē, paldies"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Turpināt"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Pierakstīšanās opcijas"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Lietotājam <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Paroļu pārvaldnieki, kuros nepieciešams autentificēties"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Pieskarieties, lai atbloķētu"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Pierakstīšanās informācijas pārvaldība"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"No citas ierīces"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Izmantot citu ierīci"</string> +</resources> diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml new file mode 100644 index 000000000000..e98bfc48a864 --- /dev/null +++ b/packages/CredentialManager/res/values-mk/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Откажи"</string> + <string name="string_continue" msgid="1346732695941131882">"Продолжи"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Создајте на друго место"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Зачувајте на друго место"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Употребете друг уред"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Зачувајте на друг уред"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Едноставен начин за безбедно најавување"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користете го отпечатокот, заклучувањето со лик или заклучувањето екран за да се најавите со единствен криптографски клуч што не може да се заборави или украде. Дознајте повеќе"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Изберете каде да <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"се зачува лозинката"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"се зачуваат податоците за најавување"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Да се создаде криптографски клуч во <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Да се зачува вашата лозинка во <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Да се зачуваат вашите податоци за најавување во <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Вашиот <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> од типот <xliff:g id="TYPE">%2$s</xliff:g> може да го користите на секој уред. Зачуван е на <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> за <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"криптографски клуч"</string> + <string name="password" msgid="6738570945182936667">"лозинка"</string> + <string name="sign_ins" msgid="4710739369149469208">"најавувања"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се користи <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за сите ваши најавувања?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Постави како стандардна опција"</string> + <string name="use_once" msgid="9027366575315399714">"Употребете еднаш"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> лозинки, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> криптографски клучеви"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> лозинки"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> криптографски клучеви"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Друг уред"</string> + <string name="other_password_manager" msgid="565790221427004141">"Други управници со лозинки"</string> + <string name="close_sheet" msgid="1393792015338908262">"Затворете го листот"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Врати се на претходната страница"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Да се користи вашиот зачуван криптографски клуч за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Да се користи вашето зачувано најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Изберете зачувано најавување за <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Најавете се на друг начин"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Не, фала"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Продолжи"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опции за најавување"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"За <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заклучени управници со лозинки"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Допрете за да отклучите"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управувајте со најавувањата"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Од друг уред"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Употребете друг уред"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ml/strings.xml b/packages/CredentialManager/res/values-ml/strings.xml new file mode 100644 index 000000000000..34029cec907b --- /dev/null +++ b/packages/CredentialManager/res/values-ml/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"റദ്ദാക്കുക"</string> + <string name="string_continue" msgid="1346732695941131882">"തുടരുക"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"മറ്റൊരു സ്ഥലത്ത് സൃഷ്ടിക്കുക"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"മറ്റൊരു സ്ഥലത്തേക്ക് സംരക്ഷിക്കുക"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"മറ്റൊരു ഉപകരണം ഉപയോഗിക്കുക"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"മറ്റൊരു ഉപകരണത്തിലേക്ക് സംരക്ഷിക്കുക"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"സുരക്ഷിതമായി സൈൻ ഇൻ ചെയ്യാനുള്ള ലളിതമായ മാർഗ്ഗം"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"മറന്നുപോകാനും മോഷ്ടിക്കാനും സാധ്യതയില്ലാത്ത തനത് പാസ്കീ ഉപയോഗിച്ച് സൈൻ ഇൻ ചെയ്യാൻ നിങ്ങളുടെ ഫിംഗർപ്രിന്റോ മുഖമോ സ്ക്രീൻ ലോക്കോ ഉപയോഗിക്കുക. കൂടുതലറിയുക"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"എവിടെ <xliff:g id="CREATETYPES">%1$s</xliff:g> എന്ന് തിരഞ്ഞെടുക്കുക"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"നിങ്ങളുടെ പാസ്വേഡ് സംരക്ഷിക്കുക"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"നിങ്ങളുടെ സൈൻ ഇൻ വിവരങ്ങൾ സംരക്ഷിക്കുക"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> എന്നതിൽ ഒരു പാസ്കീ സൃഷ്ടിക്കണോ?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"പാസ്വേഡ് <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> എന്നതിലേക്ക് സംരക്ഷിക്കണോ?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"സൈൻ ഇൻ വിവരങ്ങൾ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> എന്നതിലേക്ക് സംരക്ഷിക്കണോ?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"നിങ്ങളുടെ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ഏത് ഉപകരണത്തിലും നിങ്ങൾക്ക് ഉപയോഗിക്കാം. ഇത് <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> എന്നതിനായി <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> എന്നതിലേക്ക് സംരക്ഷിച്ചു"</string> + <string name="passkey" msgid="632353688396759522">"പാസ്കീ"</string> + <string name="password" msgid="6738570945182936667">"പാസ്വേഡ്"</string> + <string name="sign_ins" msgid="4710739369149469208">"സൈൻ ഇന്നുകൾ"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"നിങ്ങളുടെ എല്ലാ സൈൻ ഇന്നുകൾക്കും <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ഉപയോഗിക്കണോ?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ഡിഫോൾട്ടായി സജ്ജീകരിക്കുക"</string> + <string name="use_once" msgid="9027366575315399714">"ഒരു തവണ ഉപയോഗിക്കുക"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്വേഡുകൾ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> പാസ്കീകൾ"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്വേഡുകൾ"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> പാസ്കീകൾ"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"മറ്റൊരു ഉപകരണം"</string> + <string name="other_password_manager" msgid="565790221427004141">"മറ്റ് പാസ്വേഡ് മാനേജർമാർ"</string> + <string name="close_sheet" msgid="1393792015338908262">"ഷീറ്റ് അടയ്ക്കുക"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"മുമ്പത്തെ പേജിലേക്ക് മടങ്ങുക"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി നിങ്ങൾ സംരക്ഷിച്ച പാസ്കീ ഉപയോഗിക്കണോ?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി നിങ്ങൾ സംരക്ഷിച്ച സൈൻ ഇൻ ഉപയോഗിക്കണോ?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനായി ഒരു സംരക്ഷിച്ച സൈൻ ഇൻ തിരഞ്ഞെടുക്കുക"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"മറ്റൊരു രീതിയിൽ സൈൻ ഇൻ ചെയ്യുക"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"വേണ്ട"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"തുടരുക"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"സൈൻ ഇൻ ഓപ്ഷനുകൾ"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> എന്നയാൾക്ക്"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ലോക്ക് ചെയ്ത പാസ്വേഡ് സൈൻ ഇൻ മാനേജർമാർ"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"അൺലോക്ക് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"സൈൻ ഇന്നുകൾ മാനേജ് ചെയ്യുക"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"മറ്റൊരു ഉപകരണത്തിൽ നിന്ന്"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"മറ്റൊരു ഉപകരണം ഉപയോഗിക്കുക"</string> +</resources> diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml new file mode 100644 index 000000000000..f0909316755a --- /dev/null +++ b/packages/CredentialManager/res/values-mn/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Цуцлах"</string> + <string name="string_continue" msgid="1346732695941131882">"Үргэлжлүүлэх"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Өөр газар үүсгэх"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Өөр газар хадгалах"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Өөр төхөөрөмж ашиглах"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Өөр төхөөрөмжид хадгалах"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Аюулгүй нэвтрэх энгийн арга"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Мартах эсвэл хулгайд алдах боломжгүй өвөрмөц passkey-н хамт нэвтрэх хурууны хээ, царай эсвэл дэлгэцийн түгжээгээ ашиглана уу. Нэмэлт мэдээлэл авах"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Хаана <xliff:g id="CREATETYPES">%1$s</xliff:g>-г сонгоно уу"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"нууц үгээ хадгалах"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"нэвтрэх мэдээллээ хадгалах"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-д passkey үүсгэх үү?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Нууц үгээ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-д хадгалах уу?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Нэвтрэх мэдээллээ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-д хадгалах уу?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Та өөрийн <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>-г дурын төхөөрөмж дээр ашиглах боломжтой. Үүнийг <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>-д <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>-д зориулж хадгалсан"</string> + <string name="passkey" msgid="632353688396759522">"passkey"</string> + <string name="password" msgid="6738570945182936667">"нууц үг"</string> + <string name="sign_ins" msgid="4710739369149469208">"нэвтрэлт"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-г бүх нэвтрэлтдээ ашиглах уу?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Өгөгдмөлөөр тохируулах"</string> + <string name="use_once" msgid="9027366575315399714">"Нэг удаа ашиглах"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkey"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Өөр төхөөрөмж"</string> + <string name="other_password_manager" msgid="565790221427004141">"Нууц үгний бусад менежер"</string> + <string name="close_sheet" msgid="1393792015338908262">"Хүснэгтийг хаах"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Өмнөх хуудас руу буцах"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д өөрийн хадгалсан passkey-г ашиглах уу?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д хадгалсан нэвтрэх мэдээллээ ашиглах уу?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д зориулж хадгалсан нэвтрэх мэдээллийг сонгоно уу"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Өөр аргаар нэвтрэх"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Үгүй, баярлалаа"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Үргэлжлүүлэх"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Нэвтрэх сонголт"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>-д"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Түгжээтэй нууц үгний менежерүүд"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Түгжээг тайлахын тулд товшино уу"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Нэвтрэлтийг удирдах"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Өөр төхөөрөмжөөс"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Өөр төхөөрөмж ашиглах"</string> +</resources> diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml new file mode 100644 index 000000000000..c4d12f54f0aa --- /dev/null +++ b/packages/CredentialManager/res/values-mr/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"रद्द करा"</string> + <string name="string_continue" msgid="1346732695941131882">"पुढे सुरू ठेवा"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"दुसऱ्या ठिकाणी तयार करा"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"दुसऱ्या ठिकाणी सेव्ह करा"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"दुसरे डिव्हाइस वापरा"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"दुसऱ्या डिव्हाइसवर सेव्ह करा"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"सुरक्षितपणे साइन इन करण्याचा सोपा मार्ग"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"युनिक पासकीसह साइन इन करण्यासाठी तुमचे फिंगरप्रिंट, फेस किंवा स्क्रीन लॉक वापरा, जे विसरता येणार नाही किंवा चोरीला जाणार नाही. अधिक जाणून घ्या"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> कुठे करायचे ते निवडा"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"तुमचा पासवर्ड सेव्ह करा"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"तुमची साइन-इन माहिती सेव्ह करा"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मध्ये पासकी तयार करायची का?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"तुमचा पासवर्ड <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> वर सेव्ह करायचा का?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"तुमची साइन-इन माहिती <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> वर सेव्ह करायची का?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"तुम्ही कोणत्याही डिव्हाइसवर तुमचे <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> वापरू शकता. ते <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> साठी <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> वर सेव्ह केले जाते"</string> + <string name="passkey" msgid="632353688396759522">"पासकी"</string> + <string name="password" msgid="6738570945182936667">"पासवर्ड"</string> + <string name="sign_ins" msgid="4710739369149469208">"साइन-इन"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"तुमच्या सर्व साइन-इन साठी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>वापरायचे का?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"डिफॉल्ट म्हणून सेट करा"</string> + <string name="use_once" msgid="9027366575315399714">"एकदा वापरा"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> पासकी"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> पासकी"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"इतर डिव्हाइस"</string> + <string name="other_password_manager" msgid="565790221427004141">"इतर पासवर्ड व्यवस्थापक"</string> + <string name="close_sheet" msgid="1393792015338908262">"शीट बंद करा"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"मागील पेजवर परत जा"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी तुमची सेव्ह केलेली पासकी वापरायची का?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी तुमचे सेव्ह केलेले साइन-इन वापरायचे का?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> साठी सेव्ह केलेले साइन-इन निवडा"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"दुसऱ्या मार्गाने साइन इन करा"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"नाही, नको"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"पुढे सुरू ठेवा"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"साइन इन पर्याय"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> साठी"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"लॉक केलेले पासवर्ड व्यवस्थापक"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"अनलॉक करण्यासाठी टॅप करा"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"साइन-इन व्यवस्थापित करा"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"दुसऱ्या डिव्हाइस वरून"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"वेगळे डिव्हाइस वापरा"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ms/strings.xml b/packages/CredentialManager/res/values-ms/strings.xml new file mode 100644 index 000000000000..fb130feb39b8 --- /dev/null +++ b/packages/CredentialManager/res/values-ms/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Batal"</string> + <string name="string_continue" msgid="1346732695941131882">"Teruskan"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Buat di tempat lain"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Simpan di tempat lain"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Gunakan peranti lain"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Simpan kepada peranti lain"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Cara mudah untuk log masuk dengan selamat"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gunakan cap jari, wajah atau kunci skrin anda untuk log masuk menggunakan kunci laluan unik yang tidak boleh dilupakan atau dicuri. Ketahui lebih lanjut"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Pilih tempat untuk <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"simpan kata laluan anda"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"simpan maklumat log masuk anda"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Buat kunci laluan dalam <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Simpan kata laluan anda pada <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Simpan maklumat log masuk anda pada <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Anda boleh menggunakan <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> anda pada mana-mana peranti. Ia disimpan pada <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> untuk <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"kunci laluan"</string> + <string name="password" msgid="6738570945182936667">"kata laluan"</string> + <string name="sign_ins" msgid="4710739369149469208">"log masuk"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua log masuk anda?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Tetapkan sebagai lalai"</string> + <string name="use_once" msgid="9027366575315399714">"Gunakan sekali"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Kata laluan <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, kunci laluan <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Kata laluan <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Kunci laluan <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Peranti lain"</string> + <string name="other_password_manager" msgid="565790221427004141">"Password Manager lain"</string> + <string name="close_sheet" msgid="1393792015338908262">"Tutup helaian"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kembali ke halaman sebelumnya"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gunakan kunci laluan anda yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gunakan maklumat log masuk anda yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pilih log masuk yang telah disimpan untuk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Log masuk menggunakan cara lain"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Tidak perlu"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Teruskan"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Pilihan log masuk"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Untuk <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Password Manager dikunci"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Ketik untuk membuka kunci"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Urus log masuk"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Daripada peranti lain"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gunakan peranti yang lain"</string> +</resources> diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml new file mode 100644 index 000000000000..b14960ab0858 --- /dev/null +++ b/packages/CredentialManager/res/values-my/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"မလုပ်တော့"</string> + <string name="string_continue" msgid="1346732695941131882">"ရှေ့ဆက်ရန်"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"နောက်တစ်နေရာတွင် ပြုလုပ်ရန်"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"နောက်တစ်နေရာတွင် သိမ်းရန်"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"စက်နောက်တစ်ခု သုံးရန်"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"စက်နောက်တစ်ခုတွင် သိမ်းရန်"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"လုံခြုံစွာလက်မှတ်ထိုးဝင်ရန် ရိုးရှင်းသောနည်း"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"မေ့မသွား (သို့) ခိုးမသွားနိုင်သော သီးခြားလျှို့ဝှက်ကီးဖြင့် လက်မှတ်ထိုးဝင်ရန် သင့်လက်ဗွေ၊ မျက်နှာ (သို့) ဖန်သားပြင်လော့ခ် သုံးနိုင်သည်။ ပိုမိုလေ့လာရန်"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ရန် နေရာရွေးပါ"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"သင့်စကားဝှက် သိမ်းရန်"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"သင်၏ လက်မှတ်ထိုးဝင်သည့်အချက်အလက်ကို သိမ်းရန်"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> တွင် လျှို့ဝှက်ကီး ပြုလုပ်မလား။"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"သင့်စကားဝှက်ကို <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> တွင် သိမ်းမလား။"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"သင်၏ လက်မှတ်ထိုးဝင်သည့်အချက်အလက်ကို <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> တွင် သိမ်းမလား။"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"မည်သည့်စက်တွင်မဆို သင်၏ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ကို သုံးနိုင်သည်။ ၎င်းကို <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> အတွက် <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> တွင် သိမ်းလိုက်သည်"</string> + <string name="passkey" msgid="632353688396759522">"လျှို့ဝှက်ကီး"</string> + <string name="password" msgid="6738570945182936667">"စကားဝှက်"</string> + <string name="sign_ins" msgid="4710739369149469208">"လက်မှတ်ထိုးဝင်မှုများ"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"သင်၏လက်မှတ်ထိုးဝင်မှု အားလုံးအတွက် <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> သုံးမလား။"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"မူရင်းအဖြစ် သတ်မှတ်ရန်"</string> + <string name="use_once" msgid="9027366575315399714">"တစ်ကြိမ်သုံးရန်"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"စကားဝှက် <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ခု၊ လျှို့ဝှက်ကီး <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ခု"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"စကားဝှက် <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ခု"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"လျှို့ဝှက်ကီး <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ခု"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"စက်နောက်တစ်ခု"</string> + <string name="other_password_manager" msgid="565790221427004141">"အခြားစကားဝှက်မန်နေဂျာများ"</string> + <string name="close_sheet" msgid="1393792015338908262">"စာမျက်နှာ ပိတ်ရန်"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ယခင်စာမျက်နှာကို ပြန်သွားပါ"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသောလျှို့ဝှက်ကီး သုံးမလား။"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသောလက်မှတ်ထိုးဝင်မှု သုံးမလား။"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> အတွက် သိမ်းထားသော လက်မှတ်ထိုးဝင်မှုကို ရွေးပါ"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"နောက်တစ်နည်းဖြင့် လက်မှတ်ထိုးဝင်ရန်"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"မလိုပါ"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ရှေ့ဆက်ရန်"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"လက်မှတ်ထိုးဝင်ရန် နည်းလမ်းများ"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> အတွက်"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"လော့ခ်ချထားသည့် စကားဝှက်မန်နေဂျာများ"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ဖွင့်ရန် တို့ပါ"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"လက်မှတ်ထိုးဝင်မှုများ စီမံခြင်း"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"စက်နောက်တစ်ခုမှ"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"အခြားစက်သုံးရန်"</string> +</resources> diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml new file mode 100644 index 000000000000..d53bc7e45cc6 --- /dev/null +++ b/packages/CredentialManager/res/values-nb/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Avbryt"</string> + <string name="string_continue" msgid="1346732695941131882">"Fortsett"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Opprett på et annet sted"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Lagre på et annet sted"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Bruk en annen enhet"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"En enkel og trygg påloggingsmåte"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Bruk fingeravtrykk, ansiktet eller en skjermlås til å logge på med en unik tilgangsnøkkel du verken kan glemme eller miste. Finn ut mer"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Velg hvor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"lagre passordet ditt"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"lagre påloggingsinformasjonen din"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vil du opprette en tilgangsnøkkel i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vil du lagre passordet i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vil du lagre påloggingsinformasjonen i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Du kan bruke <xliff:g id="TYPE">%2$s</xliff:g>-elementet for <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> på alle slags enheter. Den lagres i <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> for <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"tilgangsnøkkel"</string> + <string name="password" msgid="6738570945182936667">"passord"</string> + <string name="sign_ins" msgid="4710739369149469208">"pålogginger"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for alle pålogginger?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Angi som standard"</string> + <string name="use_once" msgid="9027366575315399714">"Bruk én gang"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passord, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> tilgangsnøkler"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passord"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> tilgangsnøkler"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"En annen enhet"</string> + <string name="other_password_manager" msgid="565790221427004141">"Andre løsninger for passordlagring"</string> + <string name="close_sheet" msgid="1393792015338908262">"Lukk arket"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tilbake til den forrige siden"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vil du bruke den lagrede tilgangsnøkkelen for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vil du bruke den lagrede påloggingen for <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Velg en lagret pålogging for <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Bruk en annen påloggingsmetode"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nei takk"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Fortsett"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Påloggingsalternativer"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"For <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Låste løsninger for passordlagring"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Trykk for å låse opp"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrer pålogginger"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Fra en annen enhet"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Bruk en annen enhet"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ne/strings.xml b/packages/CredentialManager/res/values-ne/strings.xml new file mode 100644 index 000000000000..77b095944f39 --- /dev/null +++ b/packages/CredentialManager/res/values-ne/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"रद्द गर्नुहोस्"</string> + <string name="string_continue" msgid="1346732695941131882">"जारी राख्नुहोस्"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"अर्को ठाउँमा बनाउनुहोस्"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"अर्को ठाउँमा सेभ गर्नुहोस्"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"अर्को डिभाइस प्रयोग गर्नुहोस्"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"अर्को डिभाइसमा सेभ गर्नुहोस्"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"सुरक्षित तरिकाले साइन इन गर्ने सरल तरिका"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"नभुलिने वा चोरी नहुने खालको अद्वितीय पासकीका साथै आफ्ना फिंगरप्रिन्ट, अनुहार वा स्क्रिन लक प्रयोग गरी साइन इन गर्नुहोस्। थप जान्नुहोस्"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> सेभ गर्ने ठाउँ छनौट गर्नुहोस्"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"आफ्नो पासवर्ड सेभ गर्नुहोस्"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"आफ्नो साइन इनसम्बन्धी जानकारी सेभ गर्नुहोस्"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मा पासकी बनाउने हो?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"तपाईंको पासवर्ड <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मा सेभ गर्ने हो?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"तपाईंको साइन इनसम्बन्धी जानकारी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मा सेभ गर्ने हो?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"तपाईं जुनसुकै डिभाइसमा आफ्नो <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> प्रयोग गर्न सक्नुहुन्छ। यसलाई <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> का लागि <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> मा सेभ गरिएको छ"</string> + <string name="passkey" msgid="632353688396759522">"पासकी"</string> + <string name="password" msgid="6738570945182936667">"पासवर्ड"</string> + <string name="sign_ins" msgid="4710739369149469208">"साइन इनसम्बन्धी जानकारी"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"तपाईंले साइन इन गर्ने सबै डिभाइसहरूमा <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> प्रयोग गर्ने हो?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"डिफल्ट जानकारीका रूपमा सेट गर्नुहोस्"</string> + <string name="use_once" msgid="9027366575315399714">"एक पटक प्रयोग गर्नुहोस्"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> वटा पासवर्ड, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> वटा पासकी"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> वटा पासवर्ड"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> वटा पासकी"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"अर्को डिभाइस"</string> + <string name="other_password_manager" msgid="565790221427004141">"अन्य पासवर्ड म्यानेजरहरू"</string> + <string name="close_sheet" msgid="1393792015338908262">"पाना बन्द गर्नुहोस्"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"अघिल्लो पेजमा फर्कनुहोस्"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"आफूले सेभ गरेको पासकी प्रयोग गरी <xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्ने हो?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"आफूले सेभ गरेको साइन इनसम्बन्धी जानकारी प्रयोग गरी <xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्ने हो?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> मा साइन इन गर्नका लागि सेभ गरिएका साइन इनसम्बन्धी जानकारी छनौट गर्नुहोस्"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"अर्कै विधि प्रयोग गरी साइन इन गर्नुहोस्"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"पर्दैन, धन्यवाद"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"जारी राख्नुहोस्"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"साइन इनसम्बन्धी विकल्पहरू"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> का लागि"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"लक गरिएका पासवर्ड म्यानेजरहरू"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"अनलक गर्न ट्याप गर्नुहोस्"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"साइन इनसम्बन्धी विकल्पहरू व्यवस्थापन गर्नुहोस्"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"अर्को डिभाइसका लागि"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"अर्कै डिभाइस प्रयोग गरी हेर्नुहोस्"</string> +</resources> diff --git a/packages/CredentialManager/res/values-nl/strings.xml b/packages/CredentialManager/res/values-nl/strings.xml new file mode 100644 index 000000000000..a80c2883f2fb --- /dev/null +++ b/packages/CredentialManager/res/values-nl/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Annuleren"</string> + <string name="string_continue" msgid="1346732695941131882">"Doorgaan"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Op een andere locatie maken"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Op een andere locatie opslaan"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Een ander apparaat gebruiken"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Een makkelijke manier om beveiligd in te loggen"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gebruik je vingerafdruk, gezichtsvergrendeling of schermvergrendeling om in te loggen met een unieke toegangssleutel die je niet kunt vergeten en die anderen niet kunnen stelen. Meer informatie"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Een locatie kiezen voor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"je wachtwoord opslaan"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"je inloggegevens opslaan"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Een toegangssleutel maken in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Je wachtwoord opslaan in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Je inloggegevens opslaan in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Je kunt je <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> op elk apparaat gebruiken. Het wordt opgeslagen in <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> voor <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"toegangssleutel"</string> + <string name="password" msgid="6738570945182936667">"wachtwoord"</string> + <string name="sign_ins" msgid="4710739369149469208">"inloggegevens"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> elke keer gebruiken als je inlogt?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Instellen als standaard"</string> + <string name="use_once" msgid="9027366575315399714">"Eén keer gebruiken"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> toegangssleutels"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> toegangssleutels"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Een ander apparaat"</string> + <string name="other_password_manager" msgid="565790221427004141">"Andere wachtwoordmanagers"</string> + <string name="close_sheet" msgid="1393792015338908262">"Blad sluiten"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Ga terug naar de vorige pagina"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Je opgeslagen toegangssleutel voor <xliff:g id="APP_NAME">%1$s</xliff:g> gebruiken?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Je opgeslagen inloggegevens voor <xliff:g id="APP_NAME">%1$s</xliff:g> gebruiken?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Opgeslagen inloggegevens kiezen voor <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Inloggen via een andere methode"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nee, bedankt"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Doorgaan"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opties voor inloggen"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Voor <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Vergrendelde wachtwoordmanagers"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tik om te ontgrendelen"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Inloggegevens beheren"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Via een ander apparaat"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Een ander apparaat gebruiken"</string> +</resources> diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml new file mode 100644 index 000000000000..5b401db5d90b --- /dev/null +++ b/packages/CredentialManager/res/values-or/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"ବାତିଲ କରନ୍ତୁ"</string> + <string name="string_continue" msgid="1346732695941131882">"ଜାରି ରଖନ୍ତୁ"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"ଅନ୍ୟ ଏକ ସ୍ଥାନରେ ତିଆରି କରନ୍ତୁ"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"ଅନ୍ୟ ଏକ ସ୍ଥାନରେ ସେଭ କରନ୍ତୁ"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"ଅନ୍ୟ ଏହି ଡିଭାଇସ ବ୍ୟବହାର କରନ୍ତୁ"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"ଅନ୍ୟ ଏକ ଡିଭାଇସରେ ସେଭ କରନ୍ତୁ"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"ସୁରକ୍ଷିତ ଭାବେ ସାଇନ ଇନ କରିବାର ଏକ ସରଳ ଉପାୟ"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ଏକ ସ୍ୱତନ୍ତ୍ର ପାସକୀ ମାଧ୍ୟମରେ ସାଇନ ଇନ କରିବା ପାଇଁ ଆପଣଙ୍କ ଟିପଚିହ୍ନ, ଫେସ କିମ୍ବା ସ୍କ୍ରିନ ଲକ ବ୍ୟବହାର କରନ୍ତୁ ଯାହାକୁ ଭୁଲି ପାରିବେ ନାହିଁ କିମ୍ବା ଚୋରି ହୋଇପାରିବ ନାହିଁ। ଅଧିକ ଜାଣନ୍ତୁ"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"କେଉଁଠି <xliff:g id="CREATETYPES">%1$s</xliff:g> କରିବେ, ତାହା ବାଛନ୍ତୁ"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"ଆପଣଙ୍କ ପାସୱାର୍ଡ ସେଭ କରନ୍ତୁ"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"ଆପଣଙ୍କ ସାଇନ-ଇନ ସୂଚନା ସେଭ କରନ୍ତୁ"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ରେ ଏକ ପାସକୀ ତିଆରି କରିବେ?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"ଆପଣଙ୍କ ପାସୱାର୍ଡକୁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ରେ ସେଭ କରିବେ?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ଆପଣଙ୍କ ସାଇନ-ଇନ ସୂଚନାକୁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ରେ ସେଭ କରିବେ?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"ଆପଣ ଯେ କୌଣସି ଡିଭାଇସରେ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>କୁ ବ୍ୟବହାର କରିପାରିବେ। ଏହାକୁ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ପାଇଁ <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>ରେ ସେଭ କରାଯାଏ"</string> + <string name="passkey" msgid="632353688396759522">"ପାସକୀ"</string> + <string name="password" msgid="6738570945182936667">"ପାସୱାର୍ଡ"</string> + <string name="sign_ins" msgid="4710739369149469208">"ସାଇନ-ଇନ"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"ଆପଣଙ୍କ ସମସ୍ତ ସାଇନ-ଇନ ପାଇଁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ବ୍ୟବହାର କରିବେ?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ଡିଫଲ୍ଟ ଭାବେ ସେଟ କରନ୍ତୁ"</string> + <string name="use_once" msgid="9027366575315399714">"ଥରେ ବ୍ୟବହାର କରନ୍ତୁ"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ଟି ପାସକୀ"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>ଟି ପାସକୀ"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"ଅନ୍ୟ ଏକ ଡିଭାଇସ"</string> + <string name="other_password_manager" msgid="565790221427004141">"ଅନ୍ୟ Password Manager"</string> + <string name="close_sheet" msgid="1393792015338908262">"ସିଟ ବନ୍ଦ କରନ୍ତୁ"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ପୂର୍ବବର୍ତ୍ତୀ ପୃଷ୍ଠାକୁ ଫେରନ୍ତୁ"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଆପଣଙ୍କ ପାସକୀ ବ୍ୟବହାର କରିବେ?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଆପଣଙ୍କ ସାଇନ-ଇନ ବ୍ୟବହାର କରିବେ?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ପାଇଁ ସେଭ କରାଯାଇଥିବା ଏକ ସାଇନ-ଇନ ବାଛନ୍ତୁ"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ଅନ୍ୟ ଏକ ଉପାୟରେ ସାଇନ ଇନ କରନ୍ତୁ"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ନା, ଧନ୍ୟବାଦ"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ଜାରି ରଖନ୍ତୁ"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ସାଇନ ଇନ ବିକଳ୍ପଗୁଡ଼ିକ"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>ରେ"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ଲକ ଥିବା Password Manager"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ଅନଲକ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ସାଇନ-ଇନ ପରିଚାଳନା କରନ୍ତୁ"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ଅନ୍ୟ ଏକ ଡିଭାଇସରୁ"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ଏକ ଭିନ୍ନ ଡିଭାଇସ ବ୍ୟବହାର କରନ୍ତୁ"</string> +</resources> diff --git a/packages/CredentialManager/res/values-pa/strings.xml b/packages/CredentialManager/res/values-pa/strings.xml new file mode 100644 index 000000000000..f8f5ba159099 --- /dev/null +++ b/packages/CredentialManager/res/values-pa/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"ਰੱਦ ਕਰੋ"</string> + <string name="string_continue" msgid="1346732695941131882">"ਜਾਰੀ ਰੱਖੋ"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"ਕਿਸੇ ਹੋਰ ਥਾਂ \'ਤੇ ਬਣਾਓ"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"ਕਿਸੇ ਹੋਰ ਥਾਂ \'ਤੇ ਰੱਖਿਅਤ ਕਰੋ"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"ਕੋਈ ਹੋਰ ਡੀਵਾਈਸ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਦਾ ਆਸਾਨ ਤਰੀਕਾ"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ਵਿਲੱਖਣ ਪਾਸਕੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਵਾਸਤੇ ਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ, ਚਿਹਰੇ ਜਾਂ ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿਸਨੂੰ ਭੁੱਲਿਆ ਜਾਂ ਚੋਰੀ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਹੋਰ ਜਾਣੋ"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ਲਈ ਕੋਈ ਥਾਂ ਚੁਣੋ"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"ਆਪਣਾ ਪਾਸਵਰਡ ਰੱਖਿਅਤ ਕਰੋ"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"ਆਪਣੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਰੱਖਿਅਤ ਕਰੋ"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"ਕੀ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਵਿੱਚ ਪਾਸਕੀ ਨੂੰ ਬਣਾਉਣਾ ਹੈ?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"ਕੀ ਆਪਣੇ ਪਾਸਵਰਡ ਨੂੰ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਵਿੱਚ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ਕੀ ਆਪਣੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਨੂੰ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਵਿੱਚ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"ਤੁਸੀਂ ਕਿਸੇ ਵੀ ਡੀਵਾਈਸ \'ਤੇ ਆਪਣੀ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੇ ਹੋ। ਇਸਨੂੰ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ਲਈ <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> ਵਿੱਚ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string> + <string name="passkey" msgid="632353688396759522">"ਪਾਸਕੀ"</string> + <string name="password" msgid="6738570945182936667">"ਪਾਸਵਰਡ"</string> + <string name="sign_ins" msgid="4710739369149469208">"ਸਾਈਨ-ਇਨਾਂ ਦੀ ਜਾਣਕਾਰੀ"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"ਕੀ ਆਪਣੇ ਸਾਰੇ ਸਾਈਨ-ਇਨਾਂ ਲਈ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਵਜੋਂ ਸੈੱਟ ਕਰੋ"</string> + <string name="use_once" msgid="9027366575315399714">"ਇੱਕ ਵਾਰ ਵਰਤੋ"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ਪਾਸਵਰਡ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ਪਾਸਕੀਆਂ"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ਪਾਸਵਰਡ"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ਪਾਸਕੀਆਂ"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"ਹੋਰ ਡੀਵਾਈਸ"</string> + <string name="other_password_manager" msgid="565790221427004141">"ਹੋਰ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ"</string> + <string name="close_sheet" msgid="1393792015338908262">"ਸ਼ੀਟ ਬੰਦ ਕਰੋ"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ਪਿਛਲੇ ਪੰਨੇ \'ਤੇ ਵਾਪਸ ਜਾਓ"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਆਪਣੀ ਰੱਖਿਅਤ ਕੀਤੀ ਪਾਸਕੀ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਆਪਣੀ ਰੱਖਿਅਤ ਕੀਤੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਲਈ ਰੱਖਿਅਤ ਕੀਤੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਚੁਣੋ"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ਕਿਸੇ ਹੋਰ ਤਰੀਕੇ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰੋ"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ਨਹੀਂ ਧੰਨਵਾਦ"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ਜਾਰੀ ਰੱਖੋ"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ਸਾਈਨ-ਇਨ ਕਰਨ ਦੇ ਵਿਕਲਪ"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> ਲਈ"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"ਲਾਕ ਕੀਤੇ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"ਸਾਈਨ-ਇਨਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ਹੋਰ ਡੀਵਾਈਸ ਤੋਂ"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ਵੱਖਰੇ ਡੀਵਾਈਸ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string> +</resources> diff --git a/packages/CredentialManager/res/values-pl/strings.xml b/packages/CredentialManager/res/values-pl/strings.xml new file mode 100644 index 000000000000..e684f232b00f --- /dev/null +++ b/packages/CredentialManager/res/values-pl/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Anuluj"</string> + <string name="string_continue" msgid="1346732695941131882">"Dalej"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Utwórz w innym miejscu"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Zapisz w innym miejscu"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Użyj innego urządzenia"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Zapisz na innym urządzeniu"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Prosty sposób na bezpieczne logowanie"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Używaj odcisku palca, rozpoznawania twarzy lub blokady ekranu, aby logować się z wykorzystaniem unikalnego klucza, którego nie można zapomnieć ani utracić w wyniku kradzieży. Więcej informacji"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Wybierz, gdzie chcesz <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"zapisać hasło"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"zapisać dane logowania"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Utworzyć klucz w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Zapisać hasło w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Zapisać dane logowania w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Elementu <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> typu <xliff:g id="TYPE">%2$s</xliff:g> możesz używać na każdym urządzeniu. Jest zapisany w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> (<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>)"</string> + <string name="passkey" msgid="632353688396759522">"klucz"</string> + <string name="password" msgid="6738570945182936667">"hasło"</string> + <string name="sign_ins" msgid="4710739369149469208">"dane logowania"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Używać usługi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> w przypadku wszystkich danych logowania?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Ustaw jako domyślną"</string> + <string name="use_once" msgid="9027366575315399714">"Użyj raz"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Hasła: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, klucze: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Hasła: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Klucze: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Inne urządzenie"</string> + <string name="other_password_manager" msgid="565790221427004141">"Inne menedżery haseł"</string> + <string name="close_sheet" msgid="1393792015338908262">"Zamknij arkusz"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Wróć do poprzedniej strony"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Użyć zapisanego klucza dla aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Użyć zapisanych danych logowania dla aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Wybierz zapisane dane logowania dla aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Zaloguj się w inny sposób"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nie, dziękuję"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Dalej"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opcje logowania"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zablokowane menedżery haseł"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Kliknij, aby odblokować"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Zarządzanie danymi logowania"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Na innym urządzeniu"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Użyj innego urządzenia"</string> +</resources> diff --git a/packages/CredentialManager/res/values-pt-rBR/strings.xml b/packages/CredentialManager/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000000..570d0e6dbbfa --- /dev/null +++ b/packages/CredentialManager/res/values-pt-rBR/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string> + <string name="string_continue" msgid="1346732695941131882">"Continuar"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Criar em outro lugar"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvar em outro lugar"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvar em outro dispositivo"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma maneira simples de fazer login com segurança"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a impressão digital, o reconhecimento facial ou um bloqueio de tela para fazer login com uma única chave de acesso que não pode ser esquecida ou perdida. Saiba mais"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"salvar sua senha"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"salvar suas informações de login"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Criar uma chave de acesso em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Salvar sua senha em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Salvar suas informações de login em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Você pode usar seu <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> em qualquer dispositivo. Ele está salvo em <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"chave de acesso"</string> + <string name="password" msgid="6738570945182936667">"senha"</string> + <string name="sign_ins" msgid="4710739369149469208">"logins"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Definir como padrão"</string> + <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chaves de acesso"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string> + <string name="other_password_manager" msgid="565790221427004141">"Outros gerenciadores de senha"</string> + <string name="close_sheet" msgid="1393792015338908262">"Fechar página"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Voltar à página anterior"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar sua chave de acesso salva para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Usar suas informações de login salvas para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolher um login salvo para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Fazer login de outra forma"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Agora não"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opções de login"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gerenciadores de senha bloqueados"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toque para desbloquear"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gerenciar logins"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar um dispositivo diferente"</string> +</resources> diff --git a/packages/CredentialManager/res/values-pt-rPT/strings.xml b/packages/CredentialManager/res/values-pt-rPT/strings.xml new file mode 100644 index 000000000000..8a105cd1d7b9 --- /dev/null +++ b/packages/CredentialManager/res/values-pt-rPT/strings.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string> + <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string> + <string name="string_continue" msgid="1346732695941131882">"Continuar"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Criar noutro lugar"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Guardar noutro lugar"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Guardar noutro dispositivo"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma forma simples de iniciar sessão em segurança"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a sua impressão digital, rosto ou bloqueio de ecrã para iniciar sessão com uma chave de acesso única que não pode ser esquecida nem perdida. Saiba mais"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde quer guardar <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"guardar a sua palavra-passe"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"guardar as suas informações de início de sessão"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Criar uma chave de acesso em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Guardar a sua palavra-passe em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Guardar as suas informações de início de sessão em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Pode usar <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> em qualquer dispositivo. É guardado em <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"chave de acesso"</string> + <string name="password" msgid="6738570945182936667">"palavra-passe"</string> + <string name="sign_ins" msgid="4710739369149469208">"inícios de sessão"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus inícios de sessão?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Definir como predefinição"</string> + <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> palavras-passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> palavras-passe"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chaves de acesso"</string> + <string name="passkey_before_subtitle" msgid="2448119456208647444">"Chave de acesso"</string> + <string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string> + <string name="other_password_manager" msgid="565790221427004141">"Outros gestores de palavras-passe"</string> + <string name="close_sheet" msgid="1393792015338908262">"Fechar folha"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Volte à página anterior"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar a sua chave de acesso guardada na app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Usar o seu início de sessão guardado na app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolha um início de sessão guardado para a app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Iniciar sessão de outra forma"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Não"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opções de início de sessão"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gestores de palavras-passe bloqueados"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toque para desbloquear"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Faça a gestão dos inícios de sessão"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use um dispositivo diferente"</string> +</resources> diff --git a/packages/CredentialManager/res/values-pt/strings.xml b/packages/CredentialManager/res/values-pt/strings.xml new file mode 100644 index 000000000000..570d0e6dbbfa --- /dev/null +++ b/packages/CredentialManager/res/values-pt/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string> + <string name="string_continue" msgid="1346732695941131882">"Continuar"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Criar em outro lugar"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvar em outro lugar"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvar em outro dispositivo"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma maneira simples de fazer login com segurança"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a impressão digital, o reconhecimento facial ou um bloqueio de tela para fazer login com uma única chave de acesso que não pode ser esquecida ou perdida. Saiba mais"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"salvar sua senha"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"salvar suas informações de login"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Criar uma chave de acesso em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Salvar sua senha em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Salvar suas informações de login em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Você pode usar seu <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> em qualquer dispositivo. Ele está salvo em <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"chave de acesso"</string> + <string name="password" msgid="6738570945182936667">"senha"</string> + <string name="sign_ins" msgid="4710739369149469208">"logins"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Definir como padrão"</string> + <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chaves de acesso"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string> + <string name="other_password_manager" msgid="565790221427004141">"Outros gerenciadores de senha"</string> + <string name="close_sheet" msgid="1393792015338908262">"Fechar página"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Voltar à página anterior"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Usar sua chave de acesso salva para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Usar suas informações de login salvas para <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Escolher um login salvo para <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Fazer login de outra forma"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Agora não"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuar"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opções de login"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Gerenciadores de senha bloqueados"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Toque para desbloquear"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gerenciar logins"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar um dispositivo diferente"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml new file mode 100644 index 000000000000..51955d4b70f2 --- /dev/null +++ b/packages/CredentialManager/res/values-ro/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Anulează"</string> + <string name="string_continue" msgid="1346732695941131882">"Continuă"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Creează în alt loc"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvează în alt loc"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Folosește alt dispozitiv"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvează pe alt dispozitiv"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un mod simplu de a te conecta în siguranță"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Folosește-ți amprenta, fața sau blocarea ecranului ca să te conectezi cu o cheie de acces unică, pe care nu o poți uita și care nu poate fi furată. Află mai multe"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Alege unde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"salvează parola"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"salvează informațiile de conectare"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Creezi o cheie de acces în <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Salvezi parola în <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Salvezi informațiile de conectare în <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Poți folosi <xliff:g id="TYPE">%2$s</xliff:g> <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> pe orice dispozitiv. Se salvează în <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> pentru <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"cheie de acces"</string> + <string name="password" msgid="6738570945182936667">"parolă"</string> + <string name="sign_ins" msgid="4710739369149469208">"date de conectare"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Folosești <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pentru toate conectările?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Setează ca prestabilite"</string> + <string name="use_once" msgid="9027366575315399714">"Folosește o dată"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parole, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chei de acces"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parole"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chei de acces"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Alt dispozitiv"</string> + <string name="other_password_manager" msgid="565790221427004141">"Alți manageri de parole"</string> + <string name="close_sheet" msgid="1393792015338908262">"Închide foaia"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Revino la pagina precedentă"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Folosești cheia de acces salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Folosești datele de conectare salvate pentru <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Alege o conectare salvată pentru <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Conectează-te altfel"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nu, mulțumesc"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Continuă"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opțiuni de conectare"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pentru <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Manageri de parole blocate"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Atinge pentru a debloca"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Gestionează acreditările"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De pe alt dispozitiv"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Folosește alt dispozitiv"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ru/strings.xml b/packages/CredentialManager/res/values-ru/strings.xml new file mode 100644 index 000000000000..2a459c1d98bb --- /dev/null +++ b/packages/CredentialManager/res/values-ru/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Отмена"</string> + <string name="string_continue" msgid="1346732695941131882">"Продолжить"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Создать в другом месте"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Сохранить в другом месте"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Использовать другое устройство"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Простой и безопасный способ входа"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"С уникальным ключом доступа, который невозможно украсть или забыть, вы можете подтверждать свою личность по отпечатку пальца, с помощью фейсконтроля или блокировки экрана. Подробнее…"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Выберите, где <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"сохранить пароль"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"сохранить данные для входа"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Создать ключ доступа в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Сохранить пароль в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Сохранить учетные данные в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Вы можете использовать <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> на любом устройстве. Данные <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> сохраняются в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\"."</string> + <string name="passkey" msgid="632353688396759522">"ключ доступа"</string> + <string name="password" msgid="6738570945182936667">"пароль"</string> + <string name="sign_ins" msgid="4710739369149469208">"входы"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Всегда входить с помощью приложения \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Использовать по умолчанию"</string> + <string name="use_once" msgid="9027366575315399714">"Использовать один раз"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>) и ключи доступа (<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>)"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>)"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Ключи доступа (<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>)"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Другое устройство"</string> + <string name="other_password_manager" msgid="565790221427004141">"Другие менеджеры паролей"</string> + <string name="close_sheet" msgid="1393792015338908262">"Закрыть лист"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вернуться на предыдущую страницу"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Использовать сохраненный ключ доступа для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Использовать сохраненные учетные данные для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Выберите сохраненные данные для приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Войти другим способом"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Нет, спасибо"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Продолжить"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Варианты входа"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Для пользователя <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заблокированные менеджеры паролей"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Нажмите для разблокировки"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управление входом"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"С другого устройства"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Использовать другое устройство"</string> +</resources> diff --git a/packages/CredentialManager/res/values-si/strings.xml b/packages/CredentialManager/res/values-si/strings.xml new file mode 100644 index 000000000000..de5a5a2c95a4 --- /dev/null +++ b/packages/CredentialManager/res/values-si/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"අවලංගු කරන්න"</string> + <string name="string_continue" msgid="1346732695941131882">"ඉදිරියට යන්න"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"වෙනත් ස්ථානයක තනන්න"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"වෙනත් ස්ථානයකට සුරකින්න"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"වෙනත් උපාංගයක් භාවිතා කරන්න"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"සුරක්ෂිතව පුරනය වීමට සරල ක්රමයක්"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"අමතක කළ නොහැකි හෝ සොරකම් කළ නොහැකි අනන්ය මුරයතුරක් සමග පුරනය වීමට ඔබේ ඇඟිලි සලකුණ, මුහුණ හෝ තිර අගුල භාවිතා කරන්න. තව දැන ගන්න"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> කොතැනක ද යන්න තෝරා ගන්න"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"ඔබේ මුරපදය සුරකින්න"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"ඔබේ පුරනය වීමේ තතු සුරකින්න"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> තුළ මුරයතුරක් තනන්න ද?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"ඔබේ මුරපදය <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> වෙත සුරකින්න ද?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ඔබේ පුරනය වීමේ තතු <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> වෙත සුරකින්න ද?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"ඔබට ඕනෑම උපාංගයක ඔබේ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> භාවිතා කළ හැක. එය <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> සඳහා <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> වෙත සුරැකෙයි"</string> + <string name="passkey" msgid="632353688396759522">"මුරයතුර"</string> + <string name="password" msgid="6738570945182936667">"මුරපදය"</string> + <string name="sign_ins" msgid="4710739369149469208">"පුරනය වීම්"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"ඔබේ සියලු පුරනය වීම් සඳහා <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> භාවිතා කරන්න ද?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"පෙරනිමි ලෙස සකසන්න"</string> + <string name="use_once" msgid="9027366575315399714">"වරක් භාවිතා කරන්න"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"මුරපද <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ක්, මුරයතුරු <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ක්"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"මුරපද <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ක්"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"මුරයතුරු <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>ක්"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"වෙනත් උපාංගයක්"</string> + <string name="other_password_manager" msgid="565790221427004141">"වෙනත් මුරපද කළමනාකරුවන්"</string> + <string name="close_sheet" msgid="1393792015338908262">"පත්රය වසන්න"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"පෙර පිටුවට ආපසු යන්න"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා ඔබේ සුරැකි මුරයතුර භාවිතා කරන්න ද?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා ඔබේ සුරැකි පුරනය භාවිතා කරන්න ද?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> සඳහා සුරැකි පුරනයක් තෝරා ගන්න"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"වෙනත් ආකාරයකින් පුරන්න"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"එපා ස්තුතියි"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ඉදිරියට යන්න"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"පුරනය වීමේ විකල්ප"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> සඳහා"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"අගුළු දැමූ මුරපද කළමනාකරුවන්"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"අගුළු හැරීමට තට්ටු කරන්න"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"පුරනය වීම් කළමනාකරණය කරන්න"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"වෙනත් උපාංගයකින්"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"වෙනස් උපාංගයක් භාවිතා කරන්න"</string> +</resources> diff --git a/packages/CredentialManager/res/values-sk/strings.xml b/packages/CredentialManager/res/values-sk/strings.xml new file mode 100644 index 000000000000..4545868ba76c --- /dev/null +++ b/packages/CredentialManager/res/values-sk/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Zrušiť"</string> + <string name="string_continue" msgid="1346732695941131882">"Pokračovať"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Vytvoriť inde"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Uložiť inde"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Použiť iné zariadenie"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednoduchý spôsob bezpečného prihlasovania"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Použite odtlačok prsta, tvár alebo zámku obrazovky a prihláste sa jedinečným prístupovým kľúčom, ktorý sa nedá zabudnúť ani ukradnúť. Ďalšie informácie"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Vyberte, kam <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"uložiť heslo"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"uložiť prihlasovacie údaje"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Chcete vytvoriť prístupový kľúč v službe <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Chcete uložiť heslo do služby <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Chcete uložiť svoje prihlasovacie údaje do služby <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> môžete používať v ľubovoľnom zariadení. Ukladá sa do služby <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> pre <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string> + <string name="passkey" msgid="632353688396759522">"prístupový kľúč"</string> + <string name="password" msgid="6738570945182936667">"heslo"</string> + <string name="sign_ins" msgid="4710739369149469208">"prihlasovacie údaje"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Chcete pre všetky svoje prihlasovacie údaje použiť <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Nastaviť ako predvolené"</string> + <string name="use_once" msgid="9027366575315399714">"Použiť raz"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Počet hesiel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, počet prístupových kľúčov <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Počet hesiel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Počet prístupových kľúčov: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Iné zariadenie"</string> + <string name="other_password_manager" msgid="565790221427004141">"Iní správcovia hesiel"</string> + <string name="close_sheet" msgid="1393792015338908262">"Zavrieť hárok"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Prejsť späť na predchádzajúcu stránku"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Chcete pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g> použiť uložený prístupový kľúč?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Chcete pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g> použiť uložené prihlasovacie údaje?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Vyberte uložené prihlasovacie údaje pre aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prihláste sa inak"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nie, vďaka"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Pokračovať"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Možnosti prihlásenia"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Pre používateľa <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Správcovia uzamknutých hesiel"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Odomknite klepnutím"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Spravovať prihlasovacie údaje"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Z iného zariadenia"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Použiť iné zariadenie"</string> +</resources> diff --git a/packages/CredentialManager/res/values-sl/strings.xml b/packages/CredentialManager/res/values-sl/strings.xml new file mode 100644 index 000000000000..94edf669434a --- /dev/null +++ b/packages/CredentialManager/res/values-sl/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Prekliči"</string> + <string name="string_continue" msgid="1346732695941131882">"Naprej"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Ustvarjanje na drugem mestu"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Shranjevanje na drugo mesto"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Uporabi drugo napravo"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Shrani v drugo napravo"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Preprost način za varno prijavo"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Če se želite prijaviti z enoličnim ključem za dostop, ki ga ni mogoče pozabiti ali ukrasti, uporabite prstni odtis, obraz ali nastavljeni način za odklepanje zaslona. Več o tem"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Izberite mesto za <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"shranjevanje gesla"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"shranjevanje podatkov za prijavo"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Želite ustvariti ključ za dostop pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Želite shraniti geslo pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Želite shraniti podatke za prijavo pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="TYPE">%2$s</xliff:g> za <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> lahko uporabite v kateri koli napravi. Shranjeno je pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>« za <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>."</string> + <string name="passkey" msgid="632353688396759522">"ključ za dostop"</string> + <string name="password" msgid="6738570945182936667">"geslo"</string> + <string name="sign_ins" msgid="4710739369149469208">"prijave"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite za vse prijave uporabiti »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Nastavi kot privzeto"</string> + <string name="use_once" msgid="9027366575315399714">"Uporabi enkrat"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, št. ključev za dostop: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Št. ključev za dostop: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Druga naprava"</string> + <string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji gesel"</string> + <string name="close_sheet" msgid="1393792015338908262">"Zapri list"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Nazaj na prejšnjo stran"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Želite uporabiti shranjeni ključ za dostop do aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Želite uporabiti shranjene podatke za prijavo v aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Izberite shranjene podatke za prijavo v aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Prijava na drug način"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ne, hvala"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Naprej"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Možnosti prijave"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Za uporabnika <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Zaklenjeni upravitelji gesel"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Dotaknite se, da odklenete"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Upravljanje podatkov za prijavo"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Iz druge naprave"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Uporaba druge naprave"</string> +</resources> diff --git a/packages/CredentialManager/res/values-sq/strings.xml b/packages/CredentialManager/res/values-sq/strings.xml new file mode 100644 index 000000000000..6b85a90a1e0c --- /dev/null +++ b/packages/CredentialManager/res/values-sq/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Anulo"</string> + <string name="string_continue" msgid="1346732695941131882">"Vazhdo"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Krijo në një vend tjetër"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Ruaj në një vend tjetër"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Përdor një pajisje tjetër"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Një mënyrë e thjeshtë për t\'u identifikuar në mënyrë të sigurt"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Përdor gjurmën e gishtit, fytyrën ose kyçjen e ekranit për t\'u identifikuar me një çelës unik kalimi i cili nuk mund të harrohet ose të vidhet. Mëso më shumë"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Zgjidh se ku të <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"ruaj fjalëkalimin"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"ruaj informacionet e tua të identifikimit"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Të krijohet një çelës kalimi në <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Të ruhet fjalëkalimi në <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Të ruhen informacionet e tua të identifikimit në <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Mund të përdorësh <xliff:g id="TYPE">%2$s</xliff:g> të <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> në çdo pajisje. Ai është i ruajtur te <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> për <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"çelësi i kalimit"</string> + <string name="password" msgid="6738570945182936667">"fjalëkalimi"</string> + <string name="sign_ins" msgid="4710739369149469208">"identifikimet"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Të përdoret <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> për të gjitha identifikimet?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Cakto si parazgjedhje"</string> + <string name="use_once" msgid="9027366575315399714">"Përdor një herë"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> fjalëkalime, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> çelësa kalimi"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> fjalëkalime"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> çelësa kalimi"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Një pajisje tjetër"</string> + <string name="other_password_manager" msgid="565790221427004141">"Menaxherët e tjerë të fjalëkalimeve"</string> + <string name="close_sheet" msgid="1393792015338908262">"Mbyll fletën"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Kthehu te faqja e mëparshme"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Të përdoret fjalëkalimi yt i ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Të përdoret identifikimi yt i ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Zgjidh një identifikim të ruajtur për <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Identifikohu me një mënyrë tjetër"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Jo, faleminderit"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Vazhdo"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Opsionet e identifikimit"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Për <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Menaxherët e fjalëkalimeve të kyçura"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Trokit për të shkyçur"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Identifikimet e menaxhimit"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Nga një pajisje tjetër"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Përdor një pajisje tjetër"</string> +</resources> diff --git a/packages/CredentialManager/res/values-sr/strings.xml b/packages/CredentialManager/res/values-sr/strings.xml new file mode 100644 index 000000000000..79c2eef82b6f --- /dev/null +++ b/packages/CredentialManager/res/values-sr/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Откажи"</string> + <string name="string_continue" msgid="1346732695941131882">"Настави"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Направи на другом месту"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Сачувај на другом месту"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Користи други уређај"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Сачувај на други уређај"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Једноставан начин да се безбедно пријављујете"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користите отисак прста, закључавање лицем или закључавање екрана да бисте се пријавили помоћу јединственог приступног кода који не може да се заборави или украде. Сазнајте више"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Одаберите локацију за: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"сачувајте лозинку"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"сачувајте податке о пријављивању"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Желите да направите приступни кôд код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Желите да сачувате лозинку код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Желите да сачувате податке о пријављивању код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Можете да користите тип домена <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> на било ком уређају. Чува се код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> за: <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"приступни кôд"</string> + <string name="password" msgid="6738570945182936667">"лозинка"</string> + <string name="sign_ins" msgid="4710739369149469208">"пријављивања"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Желите да за сва пријављивања користите: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Подеси као подразумевано"</string> + <string name="use_once" msgid="9027366575315399714">"Користи једном"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, приступних кодова:<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Приступних кодова: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Други уређај"</string> + <string name="other_password_manager" msgid="565790221427004141">"Други менаџери лозинки"</string> + <string name="close_sheet" msgid="1393792015338908262">"Затворите табелу"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Вратите се на претходну страницу"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Желите да користите сачувани приступни кôд за: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Желите да користите сачуване податке за пријављивање за: <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Одаберите сачувано пријављивање за: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Пријавите се на други начин"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Не, хвала"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Настави"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опције за пријављивање"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"За: <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Менаџери закључаних лозинки"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Додирните да бисте откључали"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Управљајте пријављивањима"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Са другог уређаја"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Користи други уређај"</string> +</resources> diff --git a/packages/CredentialManager/res/values-sv/strings.xml b/packages/CredentialManager/res/values-sv/strings.xml new file mode 100644 index 000000000000..7b250561e077 --- /dev/null +++ b/packages/CredentialManager/res/values-sv/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Avbryt"</string> + <string name="string_continue" msgid="1346732695941131882">"Fortsätt"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Skapa på en annan plats"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Spara på en annan plats"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Använd en annan enhet"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Ett enkelt sätt att logga in säkert på"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Använd ditt fingeravtryck, ansikte eller skärmlås om du vill logga in med en unik nyckel som inte kan glömmas bort eller bli stulen. Läs mer"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Välj var du <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"spara lösenordet"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"spara inloggningsuppgifterna"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vill du skapa en nyckel i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vill du spara ditt lösenord i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vill du spara dina inloggningsuppgifter i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Du kan använda <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> på alla enheter. Du kan spara det i <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> för <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"nyckel"</string> + <string name="password" msgid="6738570945182936667">"lösenord"</string> + <string name="sign_ins" msgid="4710739369149469208">"inloggningar"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vill du använda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> för alla dina inloggningar?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Ange som standard"</string> + <string name="use_once" msgid="9027366575315399714">"Använd en gång"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> nycklar"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> nycklar"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"En annan enhet"</string> + <string name="other_password_manager" msgid="565790221427004141">"Andra lösenordshanterare"</string> + <string name="close_sheet" msgid="1393792015338908262">"Stäng kalkylarket"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Gå tillbaka till föregående sida"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Vill du använda din sparade nyckel för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Vill du använda dina sparade inloggningsuppgifter för <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Välj en sparad inloggning för <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Logga in på ett annat sätt"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Nej tack"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Fortsätt"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Inloggningsalternativ"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"För <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Låsta lösenordshanterare"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Tryck för att låsa upp"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Hantera inloggningar"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Via en annan enhet"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Använd en annan enhet"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ta/strings.xml b/packages/CredentialManager/res/values-ta/strings.xml new file mode 100644 index 000000000000..646c4699e215 --- /dev/null +++ b/packages/CredentialManager/res/values-ta/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"ரத்துசெய்"</string> + <string name="string_continue" msgid="1346732695941131882">"தொடர்க"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"மற்றொரு இடத்தில் உருவாக்கவும்"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"மற்றொரு இடத்தில் சேமிக்கவும்"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"மற்றொரு சாதனத்தைப் பயன்படுத்தவும்"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"வேறொரு சாதனத்தில் சேமியுங்கள்"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"பாதுகாப்பாக உள்நுழைவதற்கான எளிய வழி"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"தனித்துவமான கடவுக்குறியீடு (மறக்காதவை அல்லது திருடமுடியாதவை) மூலம் உள்நுழைய, உங்கள் கைரேகை, முகம் அல்லது திரைப்பூட்டைப் பயன்படுத்தி உள்நுழையவும். மேலும் அறிக"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> எங்கே காட்டப்பட வேண்டும் என்பதைத் தேர்வுசெய்தல்"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"உங்கள் கடவுச்சொல்லைச் சேமிக்கவும்"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"உங்கள் உள்நுழைவு தகவலைச் சேமிக்கவும்"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ல் கடவுக்குறியீட்டை உருவாக்க வேண்டுமா?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"உங்கள் கடவுச்சொல்லை <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ல் சேமிக்க வேண்டுமா?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"உங்கள் உள்நுழைவுத் தகவலை <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ல் சேமிக்க வேண்டுமா?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"உங்கள் <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ஐ எந்தச் சாதனத்தில் வேண்டுமானாலும் பயன்படுத்தலாம். <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>ல் <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>க்காகச் சேமிக்கப்பட்டது"</string> + <string name="passkey" msgid="632353688396759522">"கடவுக்குறியீடு"</string> + <string name="password" msgid="6738570945182936667">"கடவுச்சொல்"</string> + <string name="sign_ins" msgid="4710739369149469208">"உள்நுழைவுகள்"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"உங்கள் அனைத்து உள்நுழைவுகளுக்கும் <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ஐப் பயன்படுத்தவா?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"இயல்பானதாக அமை"</string> + <string name="use_once" msgid="9027366575315399714">"ஒருமுறை பயன்படுத்தவும்"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> கடவுச்சொற்கள், <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> கடவுக்குறியீடுகள்"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> கடவுச்சொற்கள்"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> கடவுக்குறியீடுகள்"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"மற்றொரு சாதனம்"</string> + <string name="other_password_manager" msgid="565790221427004141">"பிற கடவுச்சொல் நிர்வாகிகள்"</string> + <string name="close_sheet" msgid="1393792015338908262">"ஷீட்டை மூடும்"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"முந்தைய பக்கத்திற்குச் செல்லும்"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கு ஏற்கெனவே சேமிக்கப்பட்ட கடவுக்குறியீட்டைப் பயன்படுத்தவா?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கு ஏற்கெனவே சேமிக்கப்பட்ட உள்நுழைவுத் தகவலைப் பயன்படுத்தவா?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸுக்கு ஏற்கெனவே சேமிக்கப்பட்ட உள்நுழைவுத் தகவலைத் தேர்வுசெய்யவும்"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"வேறு முறையில் உள்நுழைக"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"வேண்டாம்"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"தொடர்க"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"உள்நுழைவு விருப்பங்கள்"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g>க்கு"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"பூட்டப்பட்ட கடவுச்சொல் நிர்வாகிகள்"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"அன்லாக் செய்ய தட்டவும்"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"உள்நுழைவுகளை நிர்வகித்தல்"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"மற்றொரு சாதனத்திலிருந்து பயன்படுத்து"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"வேறு சாதனத்தைப் பயன்படுத்து"</string> +</resources> diff --git a/packages/CredentialManager/res/values-te/strings.xml b/packages/CredentialManager/res/values-te/strings.xml new file mode 100644 index 000000000000..d94f3d35e87b --- /dev/null +++ b/packages/CredentialManager/res/values-te/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"రద్దు చేయండి"</string> + <string name="string_continue" msgid="1346732695941131882">"కొనసాగించండి"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"మరొక స్థలంలో క్రియేట్ చేయండి"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"మరొక స్థలంలో సేవ్ చేయండి"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"మరొక పరికరాన్ని ఉపయోగించండి"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"మరొక పరికరంలో సేవ్ చేయండి"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"సురక్షితంగా సైన్ ఇన్ చేయడానికి సులభమైన మార్గం"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"మర్చిపోలేని లేదా దొంగిలించలేని ప్రత్యేకమైన పాస్-కీతో సైన్ ఇన్ చేయడానికి మీ వేలిముద్ర, ముఖం లేదా స్క్రీన్ లాక్ను ఉపయోగించండి. మరింత తెలుసుకోండి"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"ఎక్కడ <xliff:g id="CREATETYPES">%1$s</xliff:g> చేయాలో ఎంచుకోండి"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"మీ పాస్వర్డ్ను సేవ్ చేయండి"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"మీ సైన్ ఇన్ సమాచారాన్ని సేవ్ చేయండి"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>లో పాస్-కీని క్రియేట్ చేయాలా?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"మీ పాస్వర్డ్ను <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>కు సేవ్ చేయాలా?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"మీ సైన్ ఇన్ సమాచారాన్ని <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>కు సేవ్ చేయాలా?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"మీరు మీ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>ని ఏ పరికరంలోనైనా ఉపయోగించవచ్చు. ఇది <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> కోసం <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>లో సేవ్ చేయబడింది"</string> + <string name="passkey" msgid="632353688396759522">"పాస్-కీ"</string> + <string name="password" msgid="6738570945182936667">"పాస్వర్డ్"</string> + <string name="sign_ins" msgid="4710739369149469208">"సైన్ ఇన్లు"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"మీ అన్ని సైన్-ఇన్ వివరాల కోసం <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ను ఉపయోగించాలా?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ఆటోమేటిక్ సెట్టింగ్గా సెట్ చేయండి"</string> + <string name="use_once" msgid="9027366575315399714">"ఒకసారి ఉపయోగించండి"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> పాస్వర్డ్లు, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> పాస్-కీలు"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> పాస్వర్డ్లు"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> పాస్-కీలు"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"మరొక పరికరం"</string> + <string name="other_password_manager" msgid="565790221427004141">"ఇతర పాస్వర్డ్ మేనేజర్లు"</string> + <string name="close_sheet" msgid="1393792015338908262">"షీట్ను మూసివేయండి"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"మునుపటి పేజీకి తిరిగి వెళ్లండి"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీ సేవ్ చేసిన పాస్-కీ వివరాలను ఉపయోగించాలా?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం మీరు సేవ్ చేసిన సైన్ ఇన్ వివరాలను ఉపయోగించాలా?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> కోసం సేవ్ చేసిన సైన్ ఇన్ వివరాలను ఎంచుకోండి"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"మరొక పద్ధతిలో సైన్ ఇన్ చేయండి"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"వద్దు, థ్యాంక్స్"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"కొనసాగించండి"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"సైన్ ఇన్ ఆప్షన్లు"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> కోసం"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"లాక్ చేయబడిన పాస్వర్డ్ మేనేజర్లు"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"అన్లాక్ చేయడానికి ట్యాప్ చేయండి"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"సైన్ ఇన్లను మేనేజ్ చేయండి"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"మరొక పరికరం నుండి"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"వేరే పరికరాన్ని ఉపయోగించండి"</string> +</resources> diff --git a/packages/CredentialManager/res/values-th/strings.xml b/packages/CredentialManager/res/values-th/strings.xml new file mode 100644 index 000000000000..43f3f0fcd2bc --- /dev/null +++ b/packages/CredentialManager/res/values-th/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"ยกเลิก"</string> + <string name="string_continue" msgid="1346732695941131882">"ต่อไป"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"สร้างในตำแหน่งอื่น"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"บันทึกลงในตำแหน่งอื่น"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"ใช้อุปกรณ์อื่น"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"ย้ายไปยังอุปกรณ์อื่น"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"วิธีง่ายๆ ในการลงชื่อเข้าใช้อย่างปลอดภัย"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ใช้ลายนิ้วมือ ใบหน้า หรือล็อกหน้าจอในการลงชื่อเข้าใช้ด้วยพาสคีย์ที่ไม่ซ้ำกันเพื่อไม่ให้ลืมหรือถูกขโมยได้ ดูข้อมูลเพิ่มเติม"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"เลือกตำแหน่งที่จะ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"บันทึกรหัสผ่าน"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"บันทึกข้อมูลการลงชื่อเข้าใช้"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"สร้างพาสคีย์ใน <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ใช่ไหม"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"บันทึกรหัสผ่านลงใน <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ใช่ไหม"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"บันทึกข้อมูลการลงชื่อเข้าใช้ลงใน <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ใช่ไหม"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"คุณใช้ <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> ในอุปกรณ์ใดก็ได้ โดยจะบันทึกลงใน <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> สำหรับ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"พาสคีย์"</string> + <string name="password" msgid="6738570945182936667">"รหัสผ่าน"</string> + <string name="sign_ins" msgid="4710739369149469208">"การลงชื่อเข้าใช้"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"ใช้ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> สำหรับการลงชื่อเข้าใช้ทั้งหมดใช่ไหม"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"ตั้งเป็นค่าเริ่มต้น"</string> + <string name="use_once" msgid="9027366575315399714">"ใช้ครั้งเดียว"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ พาสคีย์ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> รายการ"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"พาสคีย์ <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> รายการ"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"อุปกรณ์อื่น"</string> + <string name="other_password_manager" msgid="565790221427004141">"เครื่องมือจัดการรหัสผ่านอื่นๆ"</string> + <string name="close_sheet" msgid="1393792015338908262">"ปิดชีต"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"กลับไปยังหน้าก่อนหน้า"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"ใช้พาสคีย์ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" ใช่ไหม"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"ใช้การลงชื่อเข้าใช้ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" ใช่ไหม"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"เลือกการลงชื่อเข้าใช้ที่บันทึกไว้สำหรับ \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"ลงชื่อเข้าใช้ด้วยวิธีอื่น"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"ไม่เป็นไร"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ต่อไป"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"ตัวเลือกการลงชื่อเข้าใช้"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"สำหรับ <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"เครื่องมือจัดการรหัสผ่านที่ล็อกไว้"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"แตะเพื่อปลดล็อก"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"จัดการการลงชื่อเข้าใช้"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"จากอุปกรณ์อื่น"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ใช้อุปกรณ์อื่น"</string> +</resources> diff --git a/packages/CredentialManager/res/values-tl/strings.xml b/packages/CredentialManager/res/values-tl/strings.xml new file mode 100644 index 000000000000..4dae03722dcc --- /dev/null +++ b/packages/CredentialManager/res/values-tl/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Kanselahin"</string> + <string name="string_continue" msgid="1346732695941131882">"Magpatuloy"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Gumawa sa ibang lugar"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"I-save sa ibang lugar"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Gumamit ng ibang device"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"I-save sa ibang device"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Simpleng paraan para mag-sign in lang ligtas"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gamitin ang iyong fingerprint, mukha, o lock ng screen para mag-sign in gamit ang natatanging passkey na hindi makakalimutan o mananakaw. Matuto pa"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Piliin kung saan <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"i-save ang iyong password"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"i-save ang iyong impormasyon sa pag-sign in"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Gumawa ng passkey sa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"I-save ang iyong password sa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"I-save ang iyong impormasyon sa pag-sign in sa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Magagamit mo ang iyong <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> sa anumang device. Naka-save ito sa <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> para sa <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"passkey"</string> + <string name="password" msgid="6738570945182936667">"password"</string> + <string name="sign_ins" msgid="4710739369149469208">"mga sign-in"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gamitin ang <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para sa lahat ng iyong pag-sign in?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Itakda bilang default"</string> + <string name="use_once" msgid="9027366575315399714">"Gamitin nang isang beses"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> (na) passkey"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> (na) passkey"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Ibang device"</string> + <string name="other_password_manager" msgid="565790221427004141">"Iba pang password manager"</string> + <string name="close_sheet" msgid="1393792015338908262">"Isara ang sheet"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Bumalik sa nakaraang page"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Gamitin ang iyong naka-save na passkey para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Gamitin ang iyong naka-save na sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Pumili ng naka-save na sign-in para sa <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Mag-sign in sa ibang paraan"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Hindi, salamat na lang"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Magpatuloy"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Mga opsyon sa pag-sign in"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Para kay <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Mga naka-lock na password manager"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"I-tap para i-unlock"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Pamahalaan ang mga sign-in"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Mula sa ibang device"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Gumamit ng ibang device"</string> +</resources> diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml new file mode 100644 index 000000000000..c1ccd984b375 --- /dev/null +++ b/packages/CredentialManager/res/values-tr/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"İptal"</string> + <string name="string_continue" msgid="1346732695941131882">"Devam"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Başka bir yerde oluşturun"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Başka bir yere kaydedin"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Başka bir cihaz kullan"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Başka bir cihaza kaydedin"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Güvenli bir şekilde oturum açmanın basit yolu"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Parmak iziniz, yüzünüz ya da ekran kilidinizi kullanarak unutması veya çalınması mümkün olmayan benzersiz bir şifre anahtarıyla oturum açın. Daha fazla bilgi"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> yerini seçin"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"şifrenizi kaydedin"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"oturum açma bilgilerinizi kaydedin"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> içinde şifre anahtarı oluşturulsun mu?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Şifreniz <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> içine kaydedilsin mi?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Oturum açma bilgileriniz <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> içine kaydedilsin mi?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> herhangi bir cihazda kullanılabilir. <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> için <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> içine kaydedilir"</string> + <string name="passkey" msgid="632353688396759522">"şifre anahtarı"</string> + <string name="password" msgid="6738570945182936667">"şifre"</string> + <string name="sign_ins" msgid="4710739369149469208">"oturum aç"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Tüm oturum açma işlemlerinizde <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kullanılsın mı?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Varsayılan olarak ayarla"</string> + <string name="use_once" msgid="9027366575315399714">"Bir kez kullanın"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> şifre anahtarı"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> şifre anahtarı"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Başka bir cihaz"</string> + <string name="other_password_manager" msgid="565790221427004141">"Diğer şifre yöneticileri"</string> + <string name="close_sheet" msgid="1393792015338908262">"Sayfayı kapat"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Önceki sayfaya geri dön"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifre anahtarınız kullanılsın mı?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı oturum açma bilgileriniz kullanılsın mı?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı oturum açma bilgilerini kullanın"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Başka bir yöntemle oturum aç"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Hayır, teşekkürler"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Devam"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Oturum açma seçenekleri"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> için"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Kilitli şifre yöneticileri"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Kilidi açmak için dokunun"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Oturum açma bilgilerini yönetin"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Başka bir cihazdan"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Farklı bir cihaz kullan"</string> +</resources> diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml new file mode 100644 index 000000000000..396da4d1da87 --- /dev/null +++ b/packages/CredentialManager/res/values-uk/strings.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Скасувати"</string> + <string name="string_continue" msgid="1346732695941131882">"Продовжити"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Створити в іншому місці"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Зберегти в іншому місці"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Скористатись іншим пристроєм"</string> + <!-- no translation found for string_save_to_another_device (1959562542075194458) --> + <skip /> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Зручний спосіб для безпечного входу"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користуйтеся відбитком пальця, фейсконтролем або іншим способом розблокування екрана, щоб входити в обліковий запис за допомогою унікального ключа доступу, який неможливо забути чи викрасти. Докладніше"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Виберіть, де <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"зберегти пароль"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"зберегти дані для входу"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Створити ключ доступу в сервісі <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Зберегти ваш пароль у сервісі <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Зберегти ваші дані для входу в сервіс <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Ви можете використовувати <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> на будь-якому пристрої. Його збережено в постачальника послуг \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\" для <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"ключ доступу"</string> + <string name="password" msgid="6738570945182936667">"пароль"</string> + <string name="sign_ins" msgid="4710739369149469208">"дані для входу"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Використовувати сервіс <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> в усіх випадках входу?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Вибрати за умовчанням"</string> + <string name="use_once" msgid="9027366575315399714">"Скористатися раз"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Кількість паролів: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>; кількість ключів доступу: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Кількість паролів: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Кількість ключів доступу: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Інший пристрій"</string> + <string name="other_password_manager" msgid="565790221427004141">"Інші менеджери паролів"</string> + <string name="close_sheet" msgid="1393792015338908262">"Закрити аркуш"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Повернутися на попередню сторінку"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Використати збережений ключ доступу для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Використати збережені дані для входу для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Виберіть збережені дані для входу в додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Увійти іншим способом"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Ні, дякую"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Продовжити"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Опції входу"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Для користувача <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Заблоковані менеджери паролів"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Торкніться, щоб розблокувати"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Керування даними для входу"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"З іншого пристрою"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Використовувати інший пристрій"</string> +</resources> diff --git a/packages/CredentialManager/res/values-ur/strings.xml b/packages/CredentialManager/res/values-ur/strings.xml new file mode 100644 index 000000000000..e67b94c7853b --- /dev/null +++ b/packages/CredentialManager/res/values-ur/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"منسوخ کریں"</string> + <string name="string_continue" msgid="1346732695941131882">"جاری رکھیں"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"دوسرے مقام میں تخلیق کریں"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"دوسرے مقام میں محفوظ کریں"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"کوئی دوسرا آلہ استعمال کریں"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"دوسرے آلے میں محفوظ کریں"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"محفوظ طریقے سے سائن ان کرنے کا آسان طریقہ"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"اپنے فنگر پرنٹ، چہرے یا اسکرین لاک کا استعمال کریں تاکہ ایک ایسی منفرد پاس کی سے سائن ان کیا جا سکے جسے بھولا یا چوری نہیں کیا جا سکتا۔ مزید جانیں"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> کی جگہ منتخب کریں"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"اپنا پاس ورڈ محفوظ کریں"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"اپنے سائن ان کی معلومات محفوظ کریں"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> میں پاس کی تخلیق کریں؟"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"اپنا پاس ورڈ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> میں محفوظ کریں؟"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"اپنے سائن ان کی معلومات کو <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> میں محفوظ کریں؟"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"آپ کسی بھی آلے پر اپنا <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> استعمال کر سکتے ہیں۔ یہ <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> کے <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> میں محفوظ ہو جاتا ہے"</string> + <string name="passkey" msgid="632353688396759522">"پاس کی"</string> + <string name="password" msgid="6738570945182936667">"پاس ورڈ"</string> + <string name="sign_ins" msgid="4710739369149469208">"سائن انز"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"اپنے سبھی سائن انز کے لیے <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> کا استعمال کریں؟"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"بطور ڈیفالٹ سیٹ کریں"</string> + <string name="use_once" msgid="9027366575315399714">"ایک بار استعمال کریں"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز، <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> پاس کیز"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> پاس کیز"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"دوسرا آلہ"</string> + <string name="other_password_manager" msgid="565790221427004141">"دیگر پاس ورڈ مینیجرز"</string> + <string name="close_sheet" msgid="1393792015338908262">"شیٹ بند کریں"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"گزشتہ صفحے پر واپس جائیں"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے اپنی محفوظ کردہ پاس کی استعمال کریں؟"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے اپنے محفوظ کردہ سائن ان کو استعمال کریں؟"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے لیے محفوظ کردہ سائن انز منتخب کریں"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"دوسرے طریقے سے سائن ان کریں"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"نہیں شکریہ"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"جاری رکھیں"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"سائن ان کے اختیارات"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> کے لیے"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"مقفل کردہ پاس ورڈ مینیجرز"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"غیر مقفل کرنے کے لیے تھپتھپائیں"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"سائن انز کا نظم کریں"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"دوسرے آلے سے"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"ایک مختلف آلہ استعمال کریں"</string> +</resources> diff --git a/packages/CredentialManager/res/values-uz/strings.xml b/packages/CredentialManager/res/values-uz/strings.xml new file mode 100644 index 000000000000..6c3e211b9d6a --- /dev/null +++ b/packages/CredentialManager/res/values-uz/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Bekor qilish"</string> + <string name="string_continue" msgid="1346732695941131882">"Davom etish"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Boshqa joyda yaratish"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Boshqa joyga saqlash"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Boshqa qurilmadan foydalaning"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Boshqa qurilmaga saqlash"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Xavfsiz kirishning oddiy usuli"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Esda qoladigan maxsus kalit bilan kirishda barmoq izi, yuz axboroti yoki ekran qulfidan foydalaning. Batafsil"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> joyini tanlang"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"Parolni saqlash"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"kirish axborotini saqlang"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Kalit <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xizmatida yaratilsinmi?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Parol <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xizmatida saqlansinmi?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Hisob maʼlumotlari <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xizmatida saqlansinmi?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> istalgan qurilmada ishlatilishi mumkin. U <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> uchun <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> xizmatiga saqlandi"</string> + <string name="passkey" msgid="632353688396759522">"kalit"</string> + <string name="password" msgid="6738570945182936667">"parol"</string> + <string name="sign_ins" msgid="4710739369149469208">"kirishlar"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Hamma kirishlarda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ishlatilsinmi?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Birlamchi deb belgilash"</string> + <string name="use_once" msgid="9027366575315399714">"Bir marta ishlatish"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ta parol, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ta kalit"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ta parol"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ta kalit"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Boshqa qurilma"</string> + <string name="other_password_manager" msgid="565790221427004141">"Boshqa parol menejerlari"</string> + <string name="close_sheet" msgid="1393792015338908262">"Varaqni yopish"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Avvalgi sahifaga qaytish"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun saqlangan kalit ishlatilsinmi?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun saqlangan maʼlumotlar ishlatilsinmi?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"<xliff:g id="APP_NAME">%1$s</xliff:g> hisob maʼlumotlarini tanlang"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Boshqa usul orqali kirish"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Kerak emas"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Davom etish"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Kirish parametrlari"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> uchun"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Qulfli parol menejerlari"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Qulfni ochish uchun bosing"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Hisob maʼlumotlarini boshqarish"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Boshqa qurilmada"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Boshqa qurilmadan foydalanish"</string> +</resources> diff --git a/packages/CredentialManager/res/values-vi/strings.xml b/packages/CredentialManager/res/values-vi/strings.xml new file mode 100644 index 000000000000..d4703f394c89 --- /dev/null +++ b/packages/CredentialManager/res/values-vi/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Huỷ"</string> + <string name="string_continue" msgid="1346732695941131882">"Tiếp tục"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Tạo ở vị trí khác"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Lưu vào vị trí khác"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Dùng thiết bị khác"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Lưu vào thiết bị khác"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Cách đơn giản để đăng nhập an toàn"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Dùng vân tay, khuôn mặt hoặc phương thức khoá màn hình để đăng nhập bằng một mã xác thực duy nhất mà bạn không lo sẽ quên hay bị đánh cắp. Tìm hiểu thêm"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Chọn vị trí <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"lưu mật khẩu của bạn"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"lưu thông tin đăng nhập của bạn"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Tạo một mã xác thực trong <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Lưu mật khẩu của bạn vào <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Lưu thông tin đăng nhập của bạn vào <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Bạn có thể sử dụng <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> trên mọi thiết bị. Thông tin này được lưu vào <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> cho <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"mã xác thực"</string> + <string name="password" msgid="6738570945182936667">"mật khẩu"</string> + <string name="sign_ins" msgid="4710739369149469208">"thông tin đăng nhập"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Dùng <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cho mọi thông tin đăng nhập của bạn?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Đặt làm mặc định"</string> + <string name="use_once" msgid="9027366575315399714">"Dùng một lần"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mật khẩu, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> mã xác thực"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mật khẩu"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> mã xác thực"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Thiết bị khác"</string> + <string name="other_password_manager" msgid="565790221427004141">"Trình quản lý mật khẩu khác"</string> + <string name="close_sheet" msgid="1393792015338908262">"Đóng trang tính"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Quay lại trang trước"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Dùng mã xác thực bạn đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Dùng thông tin đăng nhập bạn đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Chọn thông tin đăng nhập đã lưu cho <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Đăng nhập bằng cách khác"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Không, cảm ơn"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Tiếp tục"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Tuỳ chọn đăng nhập"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Cho <xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Trình quản lý mật khẩu đã khoá"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Nhấn để mở khoá"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Quản lý thông tin đăng nhập"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Từ một thiết bị khác"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Dùng thiết bị khác"</string> +</resources> diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000000..145eac2701ba --- /dev/null +++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"取消"</string> + <string name="string_continue" msgid="1346732695941131882">"继续"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"在另一位置创建"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"保存到另一位置"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"使用另一台设备"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"保存到其他设备"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"简单又安全的登录方式"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"借助指纹、人脸识别或屏幕锁定功能,使用不会被忘记或被盗且具有唯一性的通行密钥登录。了解详情"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"选择<xliff:g id="CREATETYPES">%1$s</xliff:g>的位置"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"保存您的密码"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"保存您的登录信息"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"在“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”中创建通行密钥?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"将您的密码保存至“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"将您的登录信息保存至“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"您可以在任意设备上使用 <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g>。它会保存到“<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>”的<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"通行密钥"</string> + <string name="password" msgid="6738570945182936667">"密码"</string> + <string name="sign_ins" msgid="4710739369149469208">"登录"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"将“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”用于您的所有登录信息?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"设为默认项"</string> + <string name="use_once" msgid="9027366575315399714">"使用一次"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 个密码,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 个通行密钥"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 个密码"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 个通行密钥"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"另一台设备"</string> + <string name="other_password_manager" msgid="565790221427004141">"其他密码管理工具"</string> + <string name="close_sheet" msgid="1393792015338908262">"关闭工作表"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一页"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"将您已保存的通行密钥用于<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"将您已保存的登录信息用于<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"为<xliff:g id="APP_NAME">%1$s</xliff:g>选择已保存的登录信息"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"以另一种方式登录"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"不用了"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"继续"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"登录选项"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"用户:<xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"已锁定的密码管理工具"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"点按即可解锁"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"管理登录信息"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"通过另一台设备"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他设备"</string> +</resources> diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml new file mode 100644 index 000000000000..f277c22e92c6 --- /dev/null +++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"取消"</string> + <string name="string_continue" msgid="1346732695941131882">"繼續"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"在其他位置建立"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"儲存至其他位置"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"改用其他裝置"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"儲存至其他裝置"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"安全又簡便的登入方式"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"使用指紋、面孔或螢幕鎖定配合密鑰登入。密鑰獨一無二,您不用擔心忘記密鑰或密鑰被盜。瞭解詳情"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"選擇「<xliff:g id="CREATETYPES">%1$s</xliff:g>」的位置"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"儲存密碼"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"儲存登入資料"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"要在「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」建立密鑰嗎?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"要將密碼儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"要將登入資料儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"您可以在任何裝置上使用「<xliff:g id="APPDOMAINNAME">%1$s</xliff:g>」<xliff:g id="TYPE">%2$s</xliff:g>。系統會將此資料儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>」,供「<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>」使用"</string> + <string name="passkey" msgid="632353688396759522">"密鑰"</string> + <string name="password" msgid="6738570945182936667">"密碼"</string> + <string name="sign_ins" msgid="4710739369149469208">"登入資料"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資料嗎?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"設定為預設"</string> + <string name="use_once" msgid="9027366575315399714">"單次使用"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密鑰"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 個密鑰"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"其他裝置"</string> + <string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string> + <string name="close_sheet" msgid="1393792015338908262">"閂工作表"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一頁"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密鑰嗎?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料嗎?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"使用其他方式登入"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"不用了,謝謝"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"繼續"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"登入選項"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> 專用"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"已鎖定的密碼管理工具"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"輕按即可解鎖"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"管理登入資料"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"透過其他裝置"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他裝置"</string> +</resources> diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000000..fd1dfc8e29e1 --- /dev/null +++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"取消"</string> + <string name="string_continue" msgid="1346732695941131882">"繼續"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"在其他位置建立"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"儲存至其他位置"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"改用其他裝置"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"儲存至其他裝置"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"安全又簡單的登入方式"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"登入帳戶時,你可以使用指紋、人臉或螢幕鎖定功能搭配不重複的密碼金鑰,不必擔心忘記密碼金鑰或遭人竊取。瞭解詳情"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"選擇「<xliff:g id="CREATETYPES">%1$s</xliff:g>」的位置"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"儲存密碼"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"儲存登入資訊"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"要在「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」建立密碼金鑰嗎?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"要將密碼儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"要將登入資訊儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"你可以在任何裝置上使用「<xliff:g id="APPDOMAINNAME">%1$s</xliff:g>」<xliff:g id="TYPE">%2$s</xliff:g>。系統會將這項資料儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>」,以供「<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>」使用"</string> + <string name="passkey" msgid="632353688396759522">"密碼金鑰"</string> + <string name="password" msgid="6738570945182936667">"密碼"</string> + <string name="sign_ins" msgid="4710739369149469208">"登入資訊"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資訊嗎?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"設為預設"</string> + <string name="use_once" msgid="9027366575315399714">"單次使用"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密碼金鑰"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 個密碼金鑰"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"其他裝置"</string> + <string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string> + <string name="close_sheet" msgid="1393792015338908262">"關閉功能表"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"返回上一頁"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼金鑰嗎?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"要使用已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資訊嗎?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資訊"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"使用其他方式登入"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"不用了,謝謝"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"繼續"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"登入選項"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"<xliff:g id="USERNAME">%1$s</xliff:g> 專用"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"已鎖定的密碼管理工具"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"輕觸即可解鎖"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"管理登入資訊"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"透過其他裝置"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"使用其他裝置"</string> +</resources> diff --git a/packages/CredentialManager/res/values-zu/strings.xml b/packages/CredentialManager/res/values-zu/strings.xml new file mode 100644 index 000000000000..fd2b83e7bfcc --- /dev/null +++ b/packages/CredentialManager/res/values-zu/strings.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (4539824758261855508) --> + <skip /> + <string name="string_cancel" msgid="6369133483981306063">"Khansela"</string> + <string name="string_continue" msgid="1346732695941131882">"Qhubeka"</string> + <string name="string_create_in_another_place" msgid="1033635365843437603">"Sungula kwenye indawo"</string> + <string name="string_save_to_another_place" msgid="7590325934591079193">"Londoloza kwenye indawo"</string> + <string name="string_use_another_device" msgid="8754514926121520445">"Sebenzisa enye idivayisi"</string> + <string name="string_save_to_another_device" msgid="1959562542075194458">"Londoloza kwenye idivayisi"</string> + <string name="passkey_creation_intro_title" msgid="402553911484409884">"Indlela elula yokungena ngemvume ngokuphephile"</string> + <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Sebenzisa isigxivizo somunwe, ubuso noma ukukhiya isikrini ukuze ungene ngemvume ngokhiye wokudlula oyingqayizivele ongenakulibaleka noma owebiwe. Funda kabanzi"</string> + <string name="choose_provider_title" msgid="7245243990139698508">"Khetha lapho onga-<xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> + <!-- no translation found for create_your_passkeys (8901224153607590596) --> + <skip /> + <string name="save_your_password" msgid="6597736507991704307">"Londoloza iphasiwedi yakho"</string> + <string name="save_your_sign_in_info" msgid="7213978049817076882">"londoloza ulwazi lwakho lokungena ngemvume"</string> + <!-- no translation found for choose_provider_body (8045759834416308059) --> + <skip /> + <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Sungula ukhiye wokungena ku-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_password_title" msgid="8812546498357380545">"Londoloza ulwazi lwakho lwephasiwedi ku-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Londoloza ulwazi lwakho lokungena ngemvume ku-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string> + <string name="choose_create_option_description" msgid="4419171903963100257">"Ungasebenzisa i-<xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> kunoma iyiphi idivayisi. Ilondolozelwe i-<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ku-<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>"</string> + <string name="passkey" msgid="632353688396759522">"ukhiye wokudlula"</string> + <string name="password" msgid="6738570945182936667">"iphasiwedi"</string> + <string name="sign_ins" msgid="4710739369149469208">"ukungena ngemvume"</string> + <!-- no translation found for create_passkey_in_title (2714306562710897785) --> + <skip /> + <!-- no translation found for save_password_to_title (3450480045270186421) --> + <skip /> + <!-- no translation found for save_sign_in_to_title (8328143607671760232) --> + <skip /> + <string name="use_provider_for_all_title" msgid="4201020195058980757">"Sebenzisa i-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kukho konke ukungena kwakho ngemvume?"</string> + <!-- no translation found for use_provider_for_all_description (6560593199974037820) --> + <skip /> + <string name="set_as_default" msgid="4415328591568654603">"Setha njengokuzenzakalelayo"</string> + <string name="use_once" msgid="9027366575315399714">"Sebenzisa kanye"</string> + <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Amaphasiwedi angu-<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, okhiye bokudlula abangu-<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"Amaphasiwedi angu-<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Okhiye bokudlula abangu-<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string> + <!-- no translation found for passkey_before_subtitle (2448119456208647444) --> + <skip /> + <string name="another_device" msgid="5147276802037801217">"Enye idivayisi"</string> + <string name="other_password_manager" msgid="565790221427004141">"Abanye abaphathi bephasiwedi"</string> + <string name="close_sheet" msgid="1393792015338908262">"Vala ishidi"</string> + <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Buyela emuva ekhasini langaphambilini"</string> + <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Sebenzisa ukhiye wakho wokungena olondoloziwe <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Sebenzisa ukungena kwakho ngemvume okulondoloziwe <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string> + <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Khetha ukungena ngemvume okulondoloziwe kwakho <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Ngena ngemvume ngenye indlela"</string> + <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Cha ngiyabonga"</string> + <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Qhubeka"</string> + <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Okungakhethwa kukho kokungena ngemvume"</string> + <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Okuka-<xliff:g id="USERNAME">%1$s</xliff:g>"</string> + <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Abaphathi bephasiwedi abakhiyiwe"</string> + <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Thepha ukuze uvule"</string> + <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Phatha ukungena ngemvume"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Kusukela kwenye idivayisi"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Sebenzisa idivayisi ehlukile"</string> +</resources> diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 252ecf4a6005..8c9023c55eff 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 530f1c467a76..e3ed3d925566 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.ActiveEntry 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 @@ class CredentialManagerRepo( ) { 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 @@ class CredentialManagerRepo( 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 @@ class CredentialManagerRepo( ) } - 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 @@ class CredentialManagerRepo( " \"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 @@ class CredentialManagerRepo( Binder(), CreateCredentialRequest( TYPE_PASSWORD_CREDENTIAL, - data + data, + // TODO: populate with actual data + /*candidateQueryData=*/ Bundle(), + /*requireSystemProvider=*/ false ), /*isFirstUsage=*/false, "tribank" @@ -490,7 +495,9 @@ class CredentialManagerRepo( Binder(), CreateCredentialRequest( "other-sign-ins", - data + data, + /*candidateQueryData=*/ Bundle(), + /*requireSystemProvider=*/ false ), /*isFirstUsage=*/false, "tribank" @@ -502,11 +509,46 @@ class CredentialManagerRepo( 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 b96f686c02fb..357c55dc2770 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -209,12 +209,12 @@ class CreateFlowUtils { } 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 fde32792e8f8..5552d0509355 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 @@ fun CreateCredentialScreen( viewModel.onEntrySelected(it, providerActivityLauncher) } val confirmEntryCallback: () -> Unit = { - viewModel.onConfirmCreationSelected(providerActivityLauncher) + viewModel.onConfirmEntrySelected(providerActivityLauncher) } val state = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Expanded, @@ -108,9 +108,16 @@ fun CreateCredentialScreen( 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 @@ fun CreationSelectionCard( ) { PrimaryCreateOptionRow( requestDisplayInfo = requestDisplayInfo, - createOptionInfo = createOptionInfo, + entryInfo = createOptionInfo, onOptionSelected = onOptionSelected ) } @@ -562,16 +569,85 @@ fun CreationSelectionCard( @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 0f685a104329..393cf7d9ae01 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 @@ class CreateCredentialViewModel( } } - 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 31d0365a821f..9ac524a4e6d9 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 @@ enum class CreateScreenState { 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 8ccdf4cf972a..720f231609c3 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.CancelButton 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 @@ fun GetCredentialScreen( ) } }, - 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 7e7dbde8655a..008e1b6317de 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 @@ open class CreateCredentialRequest( 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/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp index 47575137558a..fe640ad5974e 100644 --- a/packages/PackageInstaller/Android.bp +++ b/packages/PackageInstaller/Android.bp @@ -58,6 +58,7 @@ android_app { privileged: true, platform_apis: true, rename_resources_package: false, + overrides: ["PackageInstaller"], static_libs: [ "xz-java", @@ -76,6 +77,7 @@ android_app { privileged: true, platform_apis: true, rename_resources_package: false, + overrides: ["PackageInstaller"], static_libs: [ "xz-java", 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 0c9a0430bc1e..238204a66bb4 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.SpaEnvironmentFactory 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 @@ object PreferencePageProvider : SettingsPageProvider { 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 @@ object PreferencePageProvider : SettingsPageProvider { searchKeywords = SIMPLE_PREFERENCE_KEYWORDS, ) } + .setStatusDataFn { EntryStatusData(isDisabled = true) } .build() ) entryList.add(singleLineSummaryEntry()) @@ -269,7 +272,7 @@ object PreferencePageProvider : SettingsPageProvider { ) } .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 238268a9ee08..f7cbdae6909f 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.localNavController 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 @@ class DebugActivity : ComponentActivity() { 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 @@ class DebugActivity : ComponentActivity() { 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 @@ class DebugActivity : ComponentActivity() { @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 @@ class DebugActivity : ComponentActivity() { 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 @@ class DebugActivity : ComponentActivity() { 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 @@ class DebugActivity : ComponentActivity() { @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 @@ class DebugActivity : ComponentActivity() { } } -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 3df77277c205..59ec985ba253 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.QueryEnum 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 @@ class DebugProvider : ContentProvider() { 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 @@ class DebugProvider : ContentProvider() { 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 @@ class DebugProvider : ContentProvider() { 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 @@ class DebugProvider : ContentProvider() { 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 cd3ec96cd78d..aa10cc82a14e 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 @@ -42,6 +42,9 @@ 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" @@ -78,12 +81,6 @@ open class BrowseActivity : ComponentActivity() { } } } - - companion object { - const val KEY_DESTINATION = "spaActivityDestination" - const val KEY_HIGHLIGHT_ENTRY = "highlightEntry" - const val KEY_SESSION_SOURCE_NAME = "sessionSource" - } } @VisibleForTesting @@ -126,11 +123,10 @@ private fun NavControllerWrapperImpl.InitialDestination( if (destinationNavigated.value) return destinationNavigated.value = true - val initialDestination = initialIntent?.getStringExtra(BrowseActivity.KEY_DESTINATION) - ?: defaultDestination + val initialDestination = initialIntent?.getDestination() ?: defaultDestination if (initialDestination.isEmpty()) return - val initialEntryId = initialIntent?.getStringExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY) - val sessionSourceName = initialIntent?.getStringExtra(BrowseActivity.KEY_SESSION_SOURCE_NAME) + val initialEntryId = initialIntent?.getEntryId() + val sessionSourceName = initialIntent?.getSessionName() LaunchedEffect(Unit) { highlightId = initialEntryId 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 bc5dca8778d8..7a39b730342c 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,45 +90,8 @@ data class SettingsPage( return false } - 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 00a0362abd91..6ecb7fa94c3c 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 @@ enum class LogEvent { // 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 @@ interface SpaLogger { id: String, event: LogEvent, category: LogCategory = LogCategory.DEFAULT, - details: String? = null + extraData: Bundle = Bundle.EMPTY ) { } } @@ -64,8 +68,8 @@ class LocalLogger : SpaLogger { 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/util/EntryLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt index 8d0a35c371e3..1c881878f751 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.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 @@ fun wrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? { if (onClick == null) return null val logEvent = logEntryEvent() return { - logEvent(LogEvent.ENTRY_CLICK) + logEvent(LogEvent.ENTRY_CLICK, Bundle.EMPTY) onClick() } } @@ -45,8 +50,7 @@ fun wrapOnSwitchWithLog(onSwitch: ((checked: Boolean) -> Unit)?): ((checked: Boo 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 d801840565ed..97e3ac2147ca 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.filterNotNull 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 index b9e4b782455d..a88125472b52 100644 --- 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 @@ -21,8 +21,11 @@ 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 @@ -37,21 +40,21 @@ internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) { val navController = LocalNavController.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> - val spaLogger = SpaEnvironmentFactory.instance.logger - if (event == Lifecycle.Event.ON_START) { - spaLogger.event( - page.id, - LogEvent.PAGE_ENTER, + val logPageEvent: (event: LogEvent) -> Unit = { + SpaEnvironmentFactory.instance.logger.event( + id = page.id, + event = it, category = LogCategory.FRAMEWORK, - details = navController.sessionSourceName ?: page.displayName, + 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) { - spaLogger.event( - page.id, - LogEvent.PAGE_LEAVE, - category = LogCategory.FRAMEWORK, - details = navController.sessionSourceName ?: page.displayName, - ) + logPageEvent(LogEvent.PAGE_LEAVE) } } 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 000000000000..2c3c2e003832 --- /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/search/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt index 7f2f4fda44a9..02aed1ca3399 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt @@ -33,6 +33,8 @@ 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" @@ -162,20 +164,19 @@ class SpaSearchProvider : ContentProvider() { 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 14855a8aed59..7a4750dfb134 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.net.Uri 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 ff143ed864c8..f3628903dc6d 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.Context 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 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory // 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.getDestination(): String? { 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 @@ fun SliceUri.getSliceId(): String? { 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 @@ fun Uri.Builder.appendSliceParams( 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 SliceUri.createBrowsePendingIntent(): PendingIntent? { 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 @@ private fun createBrowsePendingIntent( 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 @@ private fun createBroadcastPendingIntent( 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/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 934b8f599be5..c0b7464e69e6 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.SpaEnvironmentForTest 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 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 a343f6c30cdb..f98963c869de 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.runtime.Composable 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 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 15c2db50c234..1f5de2d97245 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,8 +22,8 @@ import androidx.navigation.NavType 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.getUniquePageId import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -43,9 +43,7 @@ class SettingsPageTest { 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 @@ -58,11 +56,7 @@ class SettingsPageTest { 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 @@ -85,11 +79,7 @@ class SettingsPageTest { 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 @@ -114,8 +104,6 @@ class SettingsPageTest { 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() + 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 000000000000..18547286eee0 --- /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/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt index 7fc09ff254af..1bdba299dc98 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,16 +18,16 @@ package com.android.settingslib.spa.slice 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 @@ -50,7 +50,7 @@ class SettingsSliceDataRepositoryTest { // 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) @@ -59,7 +59,7 @@ class SettingsSliceDataRepositoryTest { // 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() @@ -69,7 +69,7 @@ class SettingsSliceDataRepositoryTest { 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 16a87f603447..d1c4e5110f60 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 @@ class SliceUtilTest { // 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 @@ class SliceUtilTest { // 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 @@ class SliceUtilTest { // 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 63859546d5f6..f38bd088060a 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 @@ -51,7 +51,7 @@ class SpaLoggerForTest : SpaLogger { 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 } 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 index 93f9afe16fb6..7e51fea69041 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 48df569f1c54..de87ddedc122 100644 --- a/packages/SettingsLib/Spa/testutils/Android.bp +++ b/packages/SettingsLib/Spa/testutils/Android.bp @@ -24,7 +24,7 @@ android_library { 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 be8df436712d..81e54c13a625 100644 --- a/packages/SettingsLib/Spa/testutils/build.gradle +++ b/packages/SettingsLib/Spa/testutils/build.gradle @@ -47,7 +47,7 @@ android { } 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 43c18d4a3e93..000000000000 --- 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 a7122d0eb03a..69999089b280 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 @@ interface AppListModel<T : AppRecord> { * * @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 @@ interface AppListModel<T : AppRecord> { * @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/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 65c547a97fd3..b9c875ba803a 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 android.content.pm.ApplicationInfo 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 @@ -116,16 +116,7 @@ private class TestAppListModel : AppListModel<TestAppRecord> { 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/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt index d5564877f681..ada4016bea13 100644 --- 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 @@ -17,8 +17,7 @@ package com.android.settingslib.spaprivileged.tests.testutils import android.content.pm.ApplicationInfo -import androidx.compose.runtime.Composable -import com.android.settingslib.spa.framework.util.asyncMapItem +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 @@ -35,16 +34,7 @@ class TestAppListModel( override fun getSpinnerOptions() = options 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 fun getGroupTitle(option: Int, record: TestAppRecord) = if (enableGrouping) record.group else null diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp index 3baef4b9f11b..e9c6aed53d0f 100644 --- a/packages/SettingsLib/TwoTargetPreference/Android.bp +++ b/packages/SettingsLib/TwoTargetPreference/Android.bp @@ -23,5 +23,6 @@ android_library { apex_available: [ "//apex_available:platform", "com.android.permission", + "com.android.healthconnect", ], } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index caaa88d6aea8..b6939962676d 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1104,7 +1104,7 @@ <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration --> <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string> <!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited --> - <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string> + <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging paused</string> <!-- [CHAR_LIMIT=80] Label for battery charging future pause --> <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging to <xliff:g id="dock_defender_threshold">%2$s</xliff:g></string> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 7c3948a19792..87354c7b01f4 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -168,25 +168,14 @@ java_library { } 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 @@ android_library { "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 @@ android_library { "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_library { }, } +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/Android.bp b/packages/SystemUI/animation/Android.bp index 5df79e1bee94..e6ac48ff5af8 100644 --- a/packages/SystemUI/animation/Android.bp +++ b/packages/SystemUI/animation/Android.bp @@ -34,10 +34,7 @@ android_library { "res", ], - static_libs: [ - "PluginCoreLib", - "androidx.core_core-animation-nodeps", - ], + static_libs: ["androidx.core_core-animation-nodeps"], manifest: "AndroidManifest.xml", kotlincflags: ["-Xjvm-default=all"], diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index fdfad2bc2fa1..54aa3516d867 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -75,7 +75,7 @@ constructor( */ interface Controller { /** The [ViewRootImpl] of this controller. */ - val viewRoot: ViewRootImpl + val viewRoot: ViewRootImpl? /** * The identity object of the source animated by this controller. This animator will ensure @@ -807,15 +807,17 @@ private class AnimatedDialog( * inversely, removed from the overlay when the source is moved back to its original position). */ private fun synchronizeNextDraw(then: () -> Unit) { - if (forceDisableSynchronization) { - // Don't synchronize when inside an automated test. + val controllerRootView = controller.viewRoot?.view + if (forceDisableSynchronization || controllerRootView == null) { + // Don't synchronize when inside an automated test or if the controller root view is + // detached. then() return } - ViewRootSync.synchronizeNextDraw(controller.viewRoot.view, decorView, then) + ViewRootSync.synchronizeNextDraw(controllerRootView, decorView, then) decorView.invalidate() - controller.viewRoot.view.invalidate() + controllerRootView.invalidate() } private fun findFirstViewGroupWithBackground(view: View): ViewGroup? { 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 f9c6841f96b5..43bfa74119b3 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 @@ class RemoteTransitionAdapter { 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 @@ class RemoteTransitionAdapter { null /* wct */, finishTransaction ) + finishTransaction.close() } catch (e: RemoteException) { Log.e( "ActivityOptionsCompat", @@ -364,6 +363,9 @@ class RemoteTransitionAdapter { ) { // 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/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt index ecee598afe4e..964ef8c88098 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt @@ -28,7 +28,7 @@ internal constructor( private val source: View, override val cuj: DialogCuj?, ) : DialogLaunchAnimator.Controller { - override val viewRoot: ViewRootImpl + override val viewRoot: ViewRootImpl? get() = source.viewRootImpl override val sourceIdentity: Any = source diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt index 50c3d7e1e76b..d6db574a34ae 100644 --- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt @@ -262,7 +262,7 @@ internal class ExpandableControllerImpl( private fun dialogController(cuj: DialogCuj?): DialogLaunchAnimator.Controller { return object : DialogLaunchAnimator.Controller { - override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl + override val viewRoot: ViewRootImpl? = composeViewRoot.viewRootImpl override val sourceIdentity: Any = this@ExpandableControllerImpl override val cuj: DialogCuj? = cuj diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 869884474ffe..e1f21742bf93 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -20,7 +20,6 @@ import android.graphics.Rect import android.icu.text.NumberFormat import android.util.TypedValue import android.view.LayoutInflater -import android.view.View import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import com.android.systemui.customization.R @@ -152,15 +151,9 @@ class DefaultClockController( view: AnimatableClockView, ) : DefaultClockFaceController(view) { override fun recomputePadding(targetRegion: Rect?) { - // We center the view within the targetRegion instead of within the parent - // view by computing the difference and adding that to the padding. - val parent = view.parent - val yDiff = - if (targetRegion != null && parent is View && parent.isLaidOut()) - targetRegion.centerY() - parent.height / 2f - else 0f + // Ignore Target Region until top padding fixed in aod val lp = view.getLayoutParams() as FrameLayout.LayoutParams - lp.topMargin = (-0.5f * view.bottom + yDiff).toInt() + lp.topMargin = (-0.5f * view.bottom).toInt() view.setLayoutParams(lp) } 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 000000000000..f490c5459d46 --- /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 000000000000..3213b2e97ac9 --- /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 index 98d8d3eb9a4a..17be74b08690 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 index 2dc7a280e423..2dc7a280e423 100644 --- 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 diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index 69767867ebd7..e598afe72f79 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -26,14 +26,11 @@ -keep class ** extends androidx.preference.PreferenceFragment -keep class com.android.systemui.tuner.* -# The plugins and animation subpackages both act as shared libraries that might be referenced in +# The plugins subpackage acts as a shared library that might be referenced in # dynamically-loaded plugin APKs. -keep class com.android.systemui.plugins.** { *; } --keep class !com.android.systemui.animation.R$**,com.android.systemui.animation.** { - *; -} -keep class com.android.systemui.fragments.FragmentService$FragmentCreator { *; } diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index b49afeef09f3..218c5cc9b7fe 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -35,6 +35,7 @@ android:visibility="invisible" /> <FrameLayout android:id="@+id/lockscreen_clock_view_large" + android:layout_marginTop="@dimen/keyguard_large_clock_top_margin" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" diff --git a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml index 669f8fb642de..e5e17b7d1554 100644 --- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml @@ -17,4 +17,7 @@ <resources> <dimen name="widget_big_font_size">54dp</dimen> + + <!-- Margin above the ambient indication container --> + <dimen name="ambient_indication_container_margin_top">10dp</dimen> </resources> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index e6593b16b731..6cc5b9d7b7e8 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -38,6 +38,9 @@ <!-- Minimum bottom margin under the security view --> <dimen name="keyguard_security_view_bottom_margin">60dp</dimen> + <!-- Margin above the ambient indication container --> + <dimen name="ambient_indication_container_margin_top">0dp</dimen> + <dimen name="keyguard_eca_top_margin">18dp</dimen> <dimen name="keyguard_eca_bottom_margin">12dp</dimen> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index a129fb650ba6..da485a99c29b 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -53,7 +53,7 @@ <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string> <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. --> - <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging is paused to protect battery</string> + <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging paused to protect battery</string> <!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock. This is shown in small font at the bottom. --> <string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml index 2d67d95ab17e..efcb6f3435b9 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml +++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml @@ -14,25 +14,32 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.shared.shadow.DoubleShadowTextClock +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/time_view" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:fontFamily="@*android:string/config_clockFontFamily" - android:textColor="@android:color/white" - android:format12Hour="@string/dream_time_complication_12_hr_time_format" - android:format24Hour="@string/dream_time_complication_24_hr_time_format" - android:fontFeatureSettings="pnum, lnum" - android:letterSpacing="0.02" - android:textSize="@dimen/dream_overlay_complication_clock_time_text_size" - app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius" - app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx" - app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy" - app:keyShadowAlpha="0.3" - app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius" - app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx" - app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy" - app:ambientShadowAlpha="0.3" -/> + android:layout_height="wrap_content"> + + <com.android.systemui.shared.shadow.DoubleShadowTextClock + android:id="@+id/time_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@*android:string/config_clockFontFamily" + android:textColor="@android:color/white" + android:format12Hour="@string/dream_time_complication_12_hr_time_format" + android:format24Hour="@string/dream_time_complication_24_hr_time_format" + android:fontFeatureSettings="pnum, lnum" + android:letterSpacing="0.02" + android:textSize="@dimen/dream_overlay_complication_clock_time_text_size" + android:translationY="@dimen/dream_overlay_complication_clock_time_translation_y" + app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius" + app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx" + app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy" + app:keyShadowAlpha="0.3" + app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius" + app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx" + app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy" + app:ambientShadowAlpha="0.3" + /> + +</FrameLayout> diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml index 4f0a78e9c35d..de96e9765668 100644 --- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml +++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml @@ -14,16 +14,21 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<ImageView +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/home_controls_chip" - android:layout_height="@dimen/keyguard_affordance_fixed_height" - android:layout_width="@dimen/keyguard_affordance_fixed_width" - android:layout_gravity="bottom|start" - android:scaleType="center" - android:tint="?android:attr/textColorPrimary" - android:src="@drawable/controls_icon" - android:background="@drawable/keyguard_bottom_affordance_bg" - android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset" - android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset" - android:contentDescription="@string/quick_controls_title" /> + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding"> + + <ImageView + android:id="@+id/home_controls_chip" + android:layout_height="@dimen/keyguard_affordance_fixed_height" + android:layout_width="@dimen/keyguard_affordance_fixed_width" + android:layout_gravity="bottom|start" + android:scaleType="center" + android:tint="?android:attr/textColorPrimary" + android:src="@drawable/controls_icon" + android:background="@drawable/keyguard_bottom_affordance_bg" + android:contentDescription="@string/quick_controls_title" /> + +</FrameLayout> diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/res/values-h700dp/dimens.xml new file mode 100644 index 000000000000..055308f17776 --- /dev/null +++ b/packages/SystemUI/res/values-h700dp/dimens.xml @@ -0,0 +1,20 @@ +<!-- + ~ 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> + <!-- Margin above the ambient indication container --> + <dimen name="ambient_indication_container_margin_top">15dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml index 8efd6f0b7995..3a71994e07e2 100644 --- a/packages/SystemUI/res/values-h800dp/dimens.xml +++ b/packages/SystemUI/res/values-h800dp/dimens.xml @@ -17,4 +17,7 @@ <resources> <!-- With the large clock, move up slightly from the center --> <dimen name="keyguard_large_clock_top_margin">-112dp</dimen> + + <!-- Margin above the ambient indication container --> + <dimen name="ambient_indication_container_margin_top">20dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 569b661cc522..d04e2b14cf4f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1523,13 +1523,15 @@ <dimen name="dream_overlay_status_bar_extra_margin">8dp</dimen> <!-- Dream overlay complications related dimensions --> - <dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen> + <dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen> + <dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen> <dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen> <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen> <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen> <dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen> <dimen name="dream_overlay_complication_shadow_padding">2dp</dimen> <dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen> + <dimen name="dream_overlay_complication_smartspace_max_width">408dp</dimen> <!-- The position of the end guide, which dream overlay complications can align their start with if their end is aligned with the parent end. Represented as the percentage over from the diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2e5e11c03a65..7597c6219011 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -200,6 +200,8 @@ <!-- Informs the user that a screenshot is being saved. [CHAR LIMIT=50] --> <string name="screenshot_saving_title">Saving screenshot\u2026</string> + <!-- Informs the user that a screenshot is being saved. [CHAR LIMIT=50] --> + <string name="screenshot_saving_work_profile_title">Saving screenshot to work profile\u2026</string> <!-- Notification title displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=50] --> <string name="screenshot_saved_title">Screenshot saved</string> <!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java index f6c75a2d2752..c9ea79432360 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java @@ -39,6 +39,7 @@ public class PreviewPositionHelper { private boolean mIsOrientationChanged; private SplitBounds mSplitBounds; private int mDesiredStagePosition; + private boolean mTaskbarInApp; public Matrix getMatrix() { return mMatrix; @@ -57,6 +58,10 @@ public class PreviewPositionHelper { mDesiredStagePosition = desiredStagePosition; } + public void setTaskbarInApp(boolean taskbarInApp) { + mTaskbarInApp = taskbarInApp; + } + /** * Updates the matrix based on the provided parameters */ @@ -83,8 +88,18 @@ public class PreviewPositionHelper { ? mSplitBounds.topTaskPercent : (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent)); // Scale portrait height to that of (actual screen - taskbar inset) - fullscreenTaskHeight = (screenHeightPx - taskbarSize) * taskPercent; - canvasScreenRatio = canvasHeight / fullscreenTaskHeight; + fullscreenTaskHeight = (screenHeightPx) * taskPercent; + if (mTaskbarInApp) { + canvasScreenRatio = canvasHeight / fullscreenTaskHeight; + } else { + if (mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) { + // Top app isn't cropped at all by taskbar + canvasScreenRatio = 0; + } else { + // Same as fullscreen ratio + canvasScreenRatio = (float) canvasWidth / screenWidthPx; + } + } } else { // For landscape, scale the width taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt index 3748eba47be5..19d0a3d6bf32 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt @@ -71,7 +71,7 @@ class DoubleShadowIconDrawable( mKeyShadowInfo.offsetY, mKeyShadowInfo.alpha ) - val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DARKEN) + val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DST_ATOP) renderNode.setRenderEffect(blend) return renderNode } 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 93c807352521..1b0dacc327c1 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 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner 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 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner 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 d4d3d2579b10..b7e2494ab839 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 class RemoteTransitionCompat { 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 @@ public class RemoteTransitionCompat { } // 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 @@ public class RemoteTransitionCompat { 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 @@ public class RemoteTransitionCompat { } // 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/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java index 458d22efd206..a25b281f807c 100644 --- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java +++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java @@ -25,7 +25,6 @@ import android.widget.Button; import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.widget.LockPatternUtils; -import com.android.settingslib.Utils; /** * This class implements a smart emergency button that updates itself based @@ -91,17 +90,6 @@ public class EmergencyButton extends Button { return super.onTouchEvent(event); } - /** - * Reload colors from resources. - **/ - public void reloadColors() { - int color = Utils.getColorAttrDefaultColor(getContext(), - com.android.internal.R.attr.textColorOnAccent); - setTextColor(color); - setBackground(getContext() - .getDrawable(com.android.systemui.R.drawable.kg_emergency_button_background)); - } - @Override public boolean performLongClick() { return super.performLongClick(); diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java index c5190e828e35..ea808eb19b90 100644 --- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java +++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java @@ -135,7 +135,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { 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/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 3e32cf5521ff..860c8e3a9f77 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -20,7 +20,6 @@ import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT; -import android.annotation.CallSuper; import android.content.res.ColorStateList; import android.os.AsyncTask; import android.os.CountDownTimer; @@ -117,13 +116,6 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey } } - @CallSuper - @Override - public void reloadColors() { - super.reloadColors(); - mMessageAreaController.reloadColors(); - } - @Override public boolean needsInput() { return false; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index faaba63938bf..2e9ad5868eba 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -16,7 +16,6 @@ package com.android.keyguard; -import android.annotation.CallSuper; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -142,16 +141,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> public void showMessage(CharSequence message, ColorStateList colorState) { } - /** - * Reload colors from resources. - **/ - @CallSuper - public void reloadColors() { - if (mEmergencyButton != null) { - mEmergencyButton.reloadColors(); - } - } - public void startAppearAnimation() { if (TextUtils.isEmpty(mMessageAreaController.getMessage())) { mMessageAreaController.setMessage(getInitialMessageResId()); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index c29f632b88d3..6a9216218d07 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -116,13 +116,6 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> return mView.getText(); } - /** - * Reload colors from resources. - **/ - public void reloadColors() { - mView.reloadColor(); - } - /** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */ public static class Factory { private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 0025986c0e5c..195e8f92754d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -16,7 +16,6 @@ package com.android.keyguard; -import android.content.res.ColorStateList; import android.content.res.Resources; import android.os.UserHandle; import android.text.Editable; @@ -39,7 +38,6 @@ import android.widget.TextView.OnEditorActionListener; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; @@ -95,18 +93,6 @@ public class KeyguardPasswordViewController } }; - @Override - public void reloadColors() { - super.reloadColors(); - int textColor = Utils.getColorAttr(mView.getContext(), - android.R.attr.textColorPrimary).getDefaultColor(); - mPasswordEntry.setTextColor(textColor); - mPasswordEntry.setHighlightColor(textColor); - mPasswordEntry.setBackgroundTintList(ColorStateList.valueOf(textColor)); - mPasswordEntry.setForegroundTintList(ColorStateList.valueOf(textColor)); - mSwitchImeButton.setImageTintList(ColorStateList.valueOf(textColor)); - } - protected KeyguardPasswordViewController(KeyguardPasswordView view, KeyguardUpdateMonitor keyguardUpdateMonitor, SecurityMode securityMode, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index cdbfb2492e27..571d2740773d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -35,7 +35,6 @@ import com.android.internal.widget.LockPatternView.Cell; import com.android.internal.widget.LockscreenCredential; import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.classifier.FalsingClassifier; import com.android.systemui.classifier.FalsingCollector; @@ -272,16 +271,6 @@ public class KeyguardPatternViewController } @Override - public void reloadColors() { - super.reloadColors(); - mMessageAreaController.reloadColors(); - int textColor = Utils.getColorAttr(mLockPatternView.getContext(), - android.R.attr.textColorSecondary).getDefaultColor(); - int errorColor = Utils.getColorError(mLockPatternView.getContext()).getDefaultColor(); - mLockPatternView.setColors(textColor, textColor, errorColor); - } - - @Override public void onPause() { super.onPause(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 7876f071fdf5..f51ac325c9c1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -70,12 +70,6 @@ public class KeyguardPinViewController } @Override - public void reloadColors() { - super.reloadColors(); - mView.reloadColors(); - } - - @Override public boolean startDisappearAnimation(Runnable finishRunnable) { return mView.startDisappearAnimation( mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 4d0a273a2189..a72a484fb6f1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -730,16 +730,20 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } private void reloadColors() { - mSecurityViewFlipperController.reloadColors(); + resetViewFlipper(); mView.reloadColors(); } /** Handles density or font scale changes. */ private void onDensityOrFontScaleChanged() { - mSecurityViewFlipperController.onDensityOrFontScaleChanged(); + resetViewFlipper(); + mView.onDensityOrFontScaleChanged(); + } + + private void resetViewFlipper() { + mSecurityViewFlipperController.clearViews(); mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback); - mView.onDensityOrFontScaleChanged(); } static class Factory { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java index 25afe11ac536..a5c8c7881e3b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -74,17 +74,8 @@ public class KeyguardSecurityViewFlipperController } } - /** - * Reload colors of ui elements upon theme change. - */ - public void reloadColors() { - for (KeyguardInputViewController<KeyguardInputView> child : mChildren) { - child.reloadColors(); - } - } - /** Handles density or font scale changes. */ - public void onDensityOrFontScaleChanged() { + public void clearViews() { mView.removeAllViews(); mChildren.clear(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 91bf20f90690..a16f30475654 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -133,12 +133,6 @@ public class KeyguardSimPinViewController } @Override - public void reloadColors() { - super.reloadColors(); - mView.reloadColors(); - } - - @Override protected void verifyPasswordAndUnlock() { String entry = mPasswordEntry.getText(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 5995e859c786..e9405eb79901 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -121,12 +121,6 @@ public class KeyguardSimPukViewController } @Override - public void reloadColors() { - super.reloadColors(); - mView.reloadColors(); - } - - @Override protected void verifyPasswordAndUnlock() { mStateMachine.next(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 993d80f49182..64fa15e0cd20 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -735,8 +735,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void requestFaceAuthOnOccludingApp(boolean request) { mOccludingAppRequestingFace = request; - updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED); + int action = mOccludingAppRequestingFace ? BIOMETRIC_ACTION_UPDATE : BIOMETRIC_ACTION_STOP; + updateFaceListeningState(action, FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED); } /** @@ -1378,16 +1378,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !mFingerprintLockedOut; } - private boolean isUnlockingWithFaceAllowed() { - return mStrongAuthTracker.isUnlockingWithBiometricAllowed(false); - } - /** * Whether fingerprint is allowed ot be used for unlocking based on the strongAuthTracker * and temporary lockout state (tracked by FingerprintManager via error codes). */ public boolean isUnlockingWithFingerprintAllowed() { - return isUnlockingWithBiometricAllowed(true); + return isUnlockingWithBiometricAllowed(FINGERPRINT); } /** @@ -1397,9 +1393,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @NonNull BiometricSourceType biometricSourceType) { switch (biometricSourceType) { case FINGERPRINT: - return isUnlockingWithFingerprintAllowed(); + return isUnlockingWithBiometricAllowed(true); case FACE: - return isUnlockingWithFaceAllowed(); + return isUnlockingWithBiometricAllowed(false); default: return false; } diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java index 9a8d53228e3f..104b71f29219 100644 --- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java @@ -17,24 +17,25 @@ package com.android.systemui; import android.app.AlertDialog; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.UserInfo; import android.os.UserHandle; -import android.util.Log; + +import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.settings.SecureSettings; +import java.util.concurrent.Executor; + import javax.inject.Inject; import dagger.assisted.Assisted; @@ -44,31 +45,66 @@ import dagger.assisted.AssistedInject; /** * Manages notification when a guest session is resumed. */ -public class GuestResumeSessionReceiver extends BroadcastReceiver { - - private static final String TAG = GuestResumeSessionReceiver.class.getSimpleName(); +public class GuestResumeSessionReceiver { @VisibleForTesting public static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in"; @VisibleForTesting public AlertDialog mNewSessionDialog; + private final Executor mMainExecutor; private final UserTracker mUserTracker; private final SecureSettings mSecureSettings; - private final BroadcastDispatcher mBroadcastDispatcher; private final ResetSessionDialog.Factory mResetSessionDialogFactory; private final GuestSessionNotification mGuestSessionNotification; + @VisibleForTesting + public final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + cancelDialog(); + + UserInfo currentUser = mUserTracker.getUserInfo(); + if (!currentUser.isGuest()) { + return; + } + + int guestLoginState = mSecureSettings.getIntForUser( + SETTING_GUEST_HAS_LOGGED_IN, 0, newUser); + + if (guestLoginState == 0) { + // set 1 to indicate, 1st login + guestLoginState = 1; + mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, + newUser); + } else if (guestLoginState == 1) { + // set 2 to indicate, 2nd or later login + guestLoginState = 2; + mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, + newUser); + } + + mGuestSessionNotification.createPersistentNotification(currentUser, + (guestLoginState <= 1)); + + if (guestLoginState > 1) { + mNewSessionDialog = mResetSessionDialogFactory.create(newUser); + mNewSessionDialog.show(); + } + } + }; + @Inject public GuestResumeSessionReceiver( + @Main Executor mainExecutor, UserTracker userTracker, SecureSettings secureSettings, - BroadcastDispatcher broadcastDispatcher, GuestSessionNotification guestSessionNotification, ResetSessionDialog.Factory resetSessionDialogFactory) { + mMainExecutor = mainExecutor; mUserTracker = userTracker; mSecureSettings = secureSettings; - mBroadcastDispatcher = broadcastDispatcher; mGuestSessionNotification = guestSessionNotification; mResetSessionDialogFactory = resetSessionDialogFactory; } @@ -77,49 +113,7 @@ public class GuestResumeSessionReceiver extends BroadcastReceiver { * Register this receiver with the {@link BroadcastDispatcher} */ public void register() { - IntentFilter f = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(this, f, null /* handler */, UserHandle.SYSTEM); - } - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - cancelDialog(); - - int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - if (userId == UserHandle.USER_NULL) { - Log.e(TAG, intent + " sent to " + TAG + " without EXTRA_USER_HANDLE"); - return; - } - - UserInfo currentUser = mUserTracker.getUserInfo(); - if (!currentUser.isGuest()) { - return; - } - - int guestLoginState = mSecureSettings.getIntForUser( - SETTING_GUEST_HAS_LOGGED_IN, 0, userId); - - if (guestLoginState == 0) { - // set 1 to indicate, 1st login - guestLoginState = 1; - mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId); - } else if (guestLoginState == 1) { - // set 2 to indicate, 2nd or later login - guestLoginState = 2; - mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId); - } - - mGuestSessionNotification.createPersistentNotification(currentUser, - (guestLoginState <= 1)); - - if (guestLoginState > 1) { - mNewSessionDialog = mResetSessionDialogFactory.create(userId); - mNewSessionDialog.show(); - } - } + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); } private void cancelDialog() { diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 7e3b1389792c..02a6d7be7143 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -26,10 +26,7 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import android.annotation.IdRes; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -45,7 +42,6 @@ import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Handler; import android.os.SystemProperties; import android.os.Trace; -import android.os.UserHandle; import android.provider.Settings.Secure; import android.util.DisplayUtils; import android.util.Log; @@ -68,7 +64,6 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.settingslib.Utils; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.decor.CutoutDecorProviderFactory; @@ -128,7 +123,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { private DisplayManager mDisplayManager; @VisibleForTesting protected boolean mIsRegistered; - private final BroadcastDispatcher mBroadcastDispatcher; private final Context mContext; private final Executor mMainExecutor; private final TunerService mTunerService; @@ -302,7 +296,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { public ScreenDecorations(Context context, @Main Executor mainExecutor, SecureSettings secureSettings, - BroadcastDispatcher broadcastDispatcher, TunerService tunerService, UserTracker userTracker, PrivacyDotViewController dotViewController, @@ -312,7 +305,6 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mContext = context; mMainExecutor = mainExecutor; mSecureSettings = secureSettings; - mBroadcastDispatcher = broadcastDispatcher; mTunerService = tunerService; mUserTracker = userTracker; mDotViewController = dotViewController; @@ -598,10 +590,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mColorInversionSetting.onChange(false); updateColorInversion(mColorInversionSetting.getValue()); - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(mUserSwitchIntentReceiver, filter, - mExecutor, UserHandle.ALL); + mUserTracker.addCallback(mUserChangedCallback, mExecutor); mIsRegistered = true; } else { mMainExecutor.execute(() -> mTunerService.removeTunable(this)); @@ -610,7 +599,7 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { mColorInversionSetting.setListening(false); } - mBroadcastDispatcher.unregisterReceiver(mUserSwitchIntentReceiver); + mUserTracker.removeCallback(mUserChangedCallback); mIsRegistered = false; } } @@ -897,18 +886,18 @@ public class ScreenDecorations implements CoreStartable, Tunable , Dumpable { } } - private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - int newUserId = mUserTracker.getUserId(); - if (DEBUG) { - Log.d(TAG, "UserSwitched newUserId=" + newUserId); - } - // update color inversion setting to the new user - mColorInversionSetting.setUserId(newUserId); - updateColorInversion(mColorInversionSetting.getValue()); - } - }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + if (DEBUG) { + Log.d(TAG, "UserSwitched newUserId=" + newUser); + } + // update color inversion setting to the new user + mColorInversionSetting.setUserId(newUser); + updateColorInversion(mColorInversionSetting.getValue()); + } + }; private void updateColorInversion(int colorsInvertedValue) { mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index d60cc7579ea8..50449b0936aa 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.internal.util.ScreenshotHelper; 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 @@ public class SystemActions implements CoreStartable { 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 @@ public class SystemActions implements CoreStartable { } 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 5616a00592f2..621b99d6804a 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.os.UserHandle 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 @@ open class BackupHelper : BackupAgentHelper() { 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 @@ open class BackupHelper : BackupAgentHelper() { } 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 @@ open class BackupHelper : BackupAgentHelper() { * @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 @@ open class BackupHelper : BackupAgentHelper() { 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/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 1d4281fbf451..19b054879d89 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -62,6 +62,11 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.biometrics.dagger.BiometricsBackground; +import com.android.systemui.biometrics.udfps.InteractionEvent; +import com.android.systemui.biometrics.udfps.NormalizedTouchData; +import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor; +import com.android.systemui.biometrics.udfps.TouchProcessor; +import com.android.systemui.biometrics.udfps.TouchProcessorResult; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; @@ -143,6 +148,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator; @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; + @Nullable private final TouchProcessor mTouchProcessor; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @@ -166,7 +172,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { // The current request from FingerprintService. Null if no current request. @Nullable UdfpsControllerOverlay mOverlay; - @Nullable private UdfpsEllipseDetection mUdfpsEllipseDetection; // The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when // to turn off high brightness mode. To get around this limitation, the state of the AOD @@ -355,10 +360,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (!mOverlayParams.equals(overlayParams)) { mOverlayParams = overlayParams; - if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { - mUdfpsEllipseDetection.updateOverlayParams(overlayParams); - } - final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateBouncer(); // When the bounds change it's always necessary to re-create the overlay's window with @@ -467,8 +468,99 @@ public class UdfpsController implements DozeReceiver, Dumpable { return portraitTouch; } + private void tryDismissingKeyguard() { + if (!mOnFingerDown) { + playStartHaptic(); + } + mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */); + mAttemptedToDismissKeyguard = true; + } + @VisibleForTesting boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { + if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { + return newOnTouch(requestId, event, fromUdfpsView); + } else { + return oldOnTouch(requestId, event, fromUdfpsView); + } + } + + private boolean newOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { + if (!fromUdfpsView) { + Log.e(TAG, "ignoring the touch injected from outside of UdfpsView"); + return false; + } + if (mOverlay == null) { + Log.w(TAG, "ignoring onTouch with null overlay"); + return false; + } + if (!mOverlay.matchesRequestId(requestId)) { + Log.w(TAG, "ignoring stale touch event: " + requestId + " current: " + + mOverlay.getRequestId()); + return false; + } + + final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId, + mOverlayParams); + if (result instanceof TouchProcessorResult.Failure) { + Log.w(TAG, ((TouchProcessorResult.Failure) result).getReason()); + return false; + } + + final TouchProcessorResult.ProcessedTouch processedTouch = + (TouchProcessorResult.ProcessedTouch) result; + final NormalizedTouchData data = processedTouch.getTouchData(); + + mActivePointerId = processedTouch.getPointerOnSensorId(); + switch (processedTouch.getEvent()) { + case DOWN: + if (shouldTryToDismissKeyguard()) { + tryDismissingKeyguard(); + } + onFingerDown(requestId, + data.getPointerId(), + data.getX(), + data.getY(), + data.getMinor(), + data.getMajor(), + data.getOrientation(), + data.getTime(), + data.getGestureStart(), + mStatusBarStateController.isDozing()); + break; + + case UP: + case CANCEL: + if (InteractionEvent.CANCEL.equals(processedTouch.getEvent())) { + Log.w(TAG, "This is a CANCEL event that's reported as an UP event!"); + } + mAttemptedToDismissKeyguard = false; + onFingerUp(requestId, + mOverlay.getOverlayView(), + data.getPointerId(), + data.getX(), + data.getY(), + data.getMinor(), + data.getMajor(), + data.getOrientation(), + data.getTime(), + data.getGestureStart(), + mStatusBarStateController.isDozing()); + mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); + break; + + + default: + break; + } + + // We should only consume touches that are within the sensor. By returning "false" for + // touches outside of the sensor, we let other UI components consume these events and act on + // them appropriately. + return processedTouch.getTouchData().isWithinSensor(mOverlayParams.getNativeSensorBounds()); + } + + private boolean oldOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { if (mOverlay == null) { Log.w(TAG, "ignoring onTouch with null overlay"); return false; @@ -498,23 +590,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { mVelocityTracker.clear(); } - boolean withinSensorArea; - if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { - // Ellipse detection - withinSensorArea = mUdfpsEllipseDetection.isGoodEllipseOverlap(event); - } else { - // Centroid with expanded overlay - withinSensorArea = - isWithinSensorArea(udfpsView, event.getRawX(), - event.getRawY(), fromUdfpsView); - } - } else { - // Centroid with sensor sized view - withinSensorArea = + final boolean withinSensorArea = isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView); - } - if (withinSensorArea) { Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0); Log.v(TAG, "onTouch | action down"); @@ -528,11 +605,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } if ((withinSensorArea || fromUdfpsView) && shouldTryToDismissKeyguard()) { Log.v(TAG, "onTouch | dismiss keyguard ACTION_DOWN"); - if (!mOnFingerDown) { - playStartHaptic(); - } - mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */); - mAttemptedToDismissKeyguard = true; + tryDismissingKeyguard(); } Trace.endSection(); @@ -545,33 +618,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { ? event.getPointerId(0) : event.findPointerIndex(mActivePointerId); if (idx == event.getActionIndex()) { - boolean actionMoveWithinSensorArea; - if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { - // Ellipse detection - actionMoveWithinSensorArea = - mUdfpsEllipseDetection.isGoodEllipseOverlap(event); - } else { - // Centroid with expanded overlay - actionMoveWithinSensorArea = - isWithinSensorArea(udfpsView, event.getRawX(idx), - event.getRawY(idx), fromUdfpsView); - } - } else { - // Centroid with sensor sized view - actionMoveWithinSensorArea = - isWithinSensorArea(udfpsView, event.getX(idx), - event.getY(idx), fromUdfpsView); - } - + final boolean actionMoveWithinSensorArea = + isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx), + fromUdfpsView); if ((fromUdfpsView || actionMoveWithinSensorArea) && shouldTryToDismissKeyguard()) { Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE"); - if (!mOnFingerDown) { - playStartHaptic(); - } - mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */); - mAttemptedToDismissKeyguard = true; + tryDismissingKeyguard(); break; } // Map the touch to portrait mode if the device is in landscape mode. @@ -696,7 +749,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull ActivityLaunchAnimator activityLaunchAnimator, @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider, @NonNull @BiometricsBackground Executor biometricsExecutor, - @NonNull PrimaryBouncerInteractor primaryBouncerInteractor) { + @NonNull PrimaryBouncerInteractor primaryBouncerInteractor, + @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -737,6 +791,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { mBiometricExecutor = biometricsExecutor; mPrimaryBouncerInteractor = primaryBouncerInteractor; + mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) + ? singlePointerTouchProcessor : null; + mDumpManager.registerDumpable(TAG, this); mOrientationListener = new BiometricDisplayListener( @@ -761,10 +818,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { udfpsHapticsSimulator.setUdfpsController(this); udfpsShell.setUdfpsOverlayController(mUdfpsOverlayController); - - if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { - mUdfpsEllipseDetection = new UdfpsEllipseDetection(mOverlayParams); - } } /** @@ -946,7 +999,36 @@ public class UdfpsController implements DozeReceiver, Dumpable { return mOnFingerDown; } - private void onFingerDown(long requestId, int x, int y, float minor, float major) { + private void onFingerDown( + long requestId, + int x, + int y, + float minor, + float major) { + onFingerDown( + requestId, + MotionEvent.INVALID_POINTER_ID /* pointerId */, + x, + y, + minor, + major, + 0f /* orientation */, + 0L /* time */, + 0L /* gestureStart */, + false /* isAod */); + } + + private void onFingerDown( + long requestId, + int pointerId, + float x, + float y, + float minor, + float major, + float orientation, + long time, + long gestureStart, + boolean isAod) { mExecution.assertIsMainThread(); if (mOverlay == null) { @@ -975,7 +1057,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mOnFingerDown = true; if (mAlternateTouchProvider != null) { mBiometricExecutor.execute(() -> { - mAlternateTouchProvider.onPointerDown(requestId, x, y, minor, major); + mAlternateTouchProvider.onPointerDown(requestId, (int) x, (int) y, minor, major); }); mFgExecutor.execute(() -> { if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { @@ -983,7 +1065,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { } }); } else { - mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major); + if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { + mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y, + minor, major, orientation, time, gestureStart, isAod); + } else { + mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, (int) x, + (int) y, minor, major); + } } Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); final UdfpsView view = mOverlay.getOverlayView(); @@ -1007,6 +1095,32 @@ public class UdfpsController implements DozeReceiver, Dumpable { } private void onFingerUp(long requestId, @NonNull UdfpsView view) { + onFingerUp( + requestId, + view, + MotionEvent.INVALID_POINTER_ID /* pointerId */, + 0f /* x */, + 0f /* y */, + 0f /* minor */, + 0f /* major */, + 0f /* orientation */, + 0L /* time */, + 0L /* gestureStart */, + false /* isAod */); + } + + private void onFingerUp( + long requestId, + @NonNull UdfpsView view, + int pointerId, + float x, + float y, + float minor, + float major, + float orientation, + long time, + long gestureStart, + boolean isAod) { mExecution.assertIsMainThread(); mActivePointerId = -1; mAcquiredReceived = false; @@ -1021,7 +1135,12 @@ public class UdfpsController implements DozeReceiver, Dumpable { } }); } else { - mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId); + if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { + mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x, + y, minor, major, orientation, time, gestureStart, isAod); + } else { + mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId); + } } for (Callback cb : mCallbacks) { cb.onFingerUp(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt deleted file mode 100644 index 8ae4775467df..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -import android.graphics.Point -import android.graphics.Rect -import android.util.RotationUtils -import android.view.MotionEvent -import kotlin.math.cos -import kotlin.math.pow -import kotlin.math.sin - -private const val TAG = "UdfpsEllipseDetection" - -private const val NEEDED_POINTS = 2 - -class UdfpsEllipseDetection(overlayParams: UdfpsOverlayParams) { - var sensorRect = Rect() - var points: Array<Point> = emptyArray() - - init { - sensorRect = Rect(overlayParams.sensorBounds) - - points = calculateSensorPoints(sensorRect) - } - - fun updateOverlayParams(params: UdfpsOverlayParams) { - sensorRect = Rect(params.sensorBounds) - - val rot = params.rotation - RotationUtils.rotateBounds( - sensorRect, - params.naturalDisplayWidth, - params.naturalDisplayHeight, - rot - ) - - points = calculateSensorPoints(sensorRect) - } - - fun isGoodEllipseOverlap(event: MotionEvent): Boolean { - return points.count { checkPoint(event, it) } >= NEEDED_POINTS - } - - private fun checkPoint(event: MotionEvent, point: Point): Boolean { - // Calculate if sensor point is within ellipse - // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE - - // yS))^2 / b^2) <= 1 - val a: Float = cos(event.orientation) * (point.x - event.rawX) - val b: Float = sin(event.orientation) * (point.y - event.rawY) - val c: Float = sin(event.orientation) * (point.x - event.rawX) - val d: Float = cos(event.orientation) * (point.y - event.rawY) - val result = - (a + b).pow(2) / (event.touchMinor / 2).pow(2) + - (c - d).pow(2) / (event.touchMajor / 2).pow(2) - - return result <= 1 - } -} - -fun calculateSensorPoints(sensorRect: Rect): Array<Point> { - val sensorX = sensorRect.centerX() - val sensorY = sensorRect.centerY() - val cornerOffset: Int = sensorRect.width() / 4 - val sideOffset: Int = sensorRect.width() / 3 - - return arrayOf( - Point(sensorX - cornerOffset, sensorY - cornerOffset), - Point(sensorX, sensorY - sideOffset), - Point(sensorX + cornerOffset, sensorY - cornerOffset), - Point(sensorX - sideOffset, sensorY), - Point(sensorX, sensorY), - Point(sensorX + sideOffset, sensorY), - Point(sensorX - cornerOffset, sensorY + cornerOffset), - Point(sensorX, sensorY + sideOffset), - Point(sensorX + cornerOffset, sensorY + cornerOffset) - ) -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt index c23b0f09f099..7f3846ca4e40 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt @@ -7,17 +7,23 @@ import android.view.Surface.Rotation /** * Collection of parameters that define an under-display fingerprint sensor (UDFPS) overlay. * - * @property sensorBounds coordinates of the bounding box around the sensor, in natural orientation, - * in pixels, for the current resolution. - * @property naturalDisplayWidth width of the physical display, in natural orientation, in pixels, - * for the current resolution. - * @property naturalDisplayHeight height of the physical display, in natural orientation, in pixels, - * for the current resolution. - * @property scaleFactor ratio of a dimension in the current resolution to the corresponding - * dimension in the native resolution. - * @property rotation current rotation of the display. + * [sensorBounds] coordinates of the bounding box around the sensor in natural orientation, in + * pixels, for the current resolution. + * + * [overlayBounds] coordinates of the UI overlay in natural orientation, in pixels, for the current + * resolution. + * + * [naturalDisplayWidth] width of the physical display in natural orientation, in pixels, for the + * current resolution. + * + * [naturalDisplayHeight] height of the physical display in natural orientation, in pixels, for the + * current resolution. + * + * [scaleFactor] ratio of a dimension in the current resolution to the corresponding dimension in + * the native resolution. + * + * [rotation] current rotation of the display. */ - data class UdfpsOverlayParams( val sensorBounds: Rect = Rect(), val overlayBounds: Rect = Rect(), @@ -26,17 +32,21 @@ data class UdfpsOverlayParams( val scaleFactor: Float = 1f, @Rotation val rotation: Int = Surface.ROTATION_0 ) { + + /** Same as [sensorBounds], but in native resolution. */ + val nativeSensorBounds = Rect(sensorBounds).apply { scale(1f / scaleFactor) } + /** See [android.view.DisplayInfo.logicalWidth] */ - val logicalDisplayWidth - get() = if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { + val logicalDisplayWidth = + if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { naturalDisplayHeight } else { naturalDisplayWidth } /** See [android.view.DisplayInfo.logicalHeight] */ - val logicalDisplayHeight - get() = if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { + val logicalDisplayHeight = + if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { naturalDisplayWidth } else { naturalDisplayHeight diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt new file mode 100644 index 000000000000..001fed76c124 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt @@ -0,0 +1,43 @@ +/* + * 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.biometrics.dagger + +import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector +import com.android.systemui.biometrics.udfps.EllipseOverlapDetector +import com.android.systemui.biometrics.udfps.OverlapDetector +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import dagger.Module +import dagger.Provides + +/** Dagger module for all things UDFPS. TODO(b/260558624): Move to BiometricsModule. */ +@Module +interface UdfpsModule { + companion object { + + @Provides + @SysUISingleton + fun providesOverlapDetector(featureFlags: FeatureFlags): OverlapDetector { + return if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { + EllipseOverlapDetector() + } else { + BoundingBoxOverlapDetector() + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt new file mode 100644 index 000000000000..79a0acb8bbc1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt @@ -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.biometrics.udfps + +import android.graphics.Rect +import com.android.systemui.dagger.SysUISingleton + +/** Returns whether the touch coordinates are within the sensor's bounding box. */ +@SysUISingleton +class BoundingBoxOverlapDetector : OverlapDetector { + override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean = + touchData.isWithinSensor(nativeSensorBounds) +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt new file mode 100644 index 000000000000..857224290752 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt @@ -0,0 +1,71 @@ +/* + * 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.biometrics.udfps + +import android.graphics.Point +import android.graphics.Rect +import com.android.systemui.dagger.SysUISingleton +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sin + +/** + * Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap + * with the sensor. + */ +@SysUISingleton +class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetector { + + override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean { + val points = calculateSensorPoints(nativeSensorBounds) + return points.count { checkPoint(it, touchData) } >= neededPoints + } + + private fun checkPoint(point: Point, touchData: NormalizedTouchData): Boolean { + // Calculate if sensor point is within ellipse + // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE - + // yS))^2 / b^2) <= 1 + val a: Float = cos(touchData.orientation) * (point.x - touchData.x) + val b: Float = sin(touchData.orientation) * (point.y - touchData.y) + val c: Float = sin(touchData.orientation) * (point.x - touchData.x) + val d: Float = cos(touchData.orientation) * (point.y - touchData.y) + val result = + (a + b).pow(2) / (touchData.minor / 2).pow(2) + + (c - d).pow(2) / (touchData.major / 2).pow(2) + + return result <= 1 + } + + private fun calculateSensorPoints(sensorBounds: Rect): List<Point> { + val sensorX = sensorBounds.centerX() + val sensorY = sensorBounds.centerY() + val cornerOffset: Int = sensorBounds.width() / 4 + val sideOffset: Int = sensorBounds.width() / 3 + + return listOf( + Point(sensorX - cornerOffset, sensorY - cornerOffset), + Point(sensorX, sensorY - sideOffset), + Point(sensorX + cornerOffset, sensorY - cornerOffset), + Point(sensorX - sideOffset, sensorY), + Point(sensorX, sensorY), + Point(sensorX + sideOffset, sensorY), + Point(sensorX - cornerOffset, sensorY + cornerOffset), + Point(sensorX, sensorY + sideOffset), + Point(sensorX + cornerOffset, sensorY + cornerOffset) + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/InteractionEvent.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/InteractionEvent.kt new file mode 100644 index 000000000000..6e47dadc4545 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/InteractionEvent.kt @@ -0,0 +1,50 @@ +/* + * 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.biometrics.udfps + +import android.view.MotionEvent + +/** Interaction event between a finger and the under-display fingerprint sensor (UDFPS). */ +enum class InteractionEvent { + /** + * A finger entered the sensor area. This can originate from either [MotionEvent.ACTION_DOWN] or + * [MotionEvent.ACTION_MOVE]. + */ + DOWN, + + /** + * A finger left the sensor area. This can originate from either [MotionEvent.ACTION_UP] or + * [MotionEvent.ACTION_MOVE]. + */ + UP, + + /** + * The touch reporting has stopped. This corresponds to [MotionEvent.ACTION_CANCEL]. This should + * not be confused with [UP]. If there was a finger on the sensor, it may or may not still be on + * the sensor. + */ + CANCEL, + + /** + * The interaction hasn't changed since the previous event. The can originate from any of + * [MotionEvent.ACTION_DOWN], [MotionEvent.ACTION_MOVE], or [MotionEvent.ACTION_UP] if one of + * these is true: + * - There was previously a finger on the sensor, and that finger is still on the sensor. + * - There was previously no finger on the sensor, and there still isn't. + */ + UNCHANGED, +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt new file mode 100644 index 000000000000..62bedc627b07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt @@ -0,0 +1,65 @@ +/* + * 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.biometrics.udfps + +import android.graphics.Rect +import android.view.MotionEvent + +/** Touch data in natural orientation and native resolution. */ +data class NormalizedTouchData( + + /** + * Value obtained from [MotionEvent.getPointerId], or [MotionEvent.INVALID_POINTER_ID] if the ID + * is not available. + */ + val pointerId: Int, + + /** [MotionEvent.getRawX] mapped to natural orientation and native resolution. */ + val x: Float, + + /** [MotionEvent.getRawY] mapped to natural orientation and native resolution. */ + val y: Float, + + /** [MotionEvent.getTouchMinor] mapped to natural orientation and native resolution. */ + val minor: Float, + + /** [MotionEvent.getTouchMajor] mapped to natural orientation and native resolution. */ + val major: Float, + + /** [MotionEvent.getOrientation] mapped to natural orientation. */ + val orientation: Float, + + /** [MotionEvent.getEventTime]. */ + val time: Long, + + /** [MotionEvent.getDownTime]. */ + val gestureStart: Long, +) { + + /** + * [nativeSensorBounds] contains the location and dimensions of the sensor area in native + * resolution and natural orientation. + * + * Returns whether the coordinates of the given pointer are within the sensor's bounding box. + */ + fun isWithinSensor(nativeSensorBounds: Rect): Boolean { + return nativeSensorBounds.left <= x && + nativeSensorBounds.right >= x && + nativeSensorBounds.top <= y && + nativeSensorBounds.bottom >= y + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt new file mode 100644 index 000000000000..0fec8ffbaa0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt @@ -0,0 +1,24 @@ +/* + * 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.biometrics.udfps + +import android.graphics.Rect + +/** Determines whether the touch has a sufficient overlap with the sensor. */ +interface OverlapDetector { + fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt new file mode 100644 index 000000000000..338bf66d197e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt @@ -0,0 +1,164 @@ +/* + * 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.biometrics.udfps + +import android.graphics.PointF +import android.util.RotationUtils +import android.view.MotionEvent +import android.view.MotionEvent.INVALID_POINTER_ID +import android.view.Surface +import com.android.systemui.biometrics.UdfpsOverlayParams +import com.android.systemui.biometrics.udfps.TouchProcessorResult.Failure +import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * TODO(b/259140693): Consider using an object pool of TouchProcessorResult to avoid allocations. + */ +@SysUISingleton +class SinglePointerTouchProcessor @Inject constructor(val overlapDetector: OverlapDetector) : + TouchProcessor { + + override fun processTouch( + event: MotionEvent, + previousPointerOnSensorId: Int, + overlayParams: UdfpsOverlayParams, + ): TouchProcessorResult { + + fun preprocess(): PreprocessedTouch { + // TODO(b/253085297): Add multitouch support. pointerIndex can be > 0 for ACTION_MOVE. + val pointerIndex = 0 + val touchData = event.normalize(pointerIndex, overlayParams) + val isGoodOverlap = + overlapDetector.isGoodOverlap(touchData, overlayParams.nativeSensorBounds) + return PreprocessedTouch(touchData, previousPointerOnSensorId, isGoodOverlap) + } + + return when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> processActionDown(preprocess()) + MotionEvent.ACTION_MOVE -> processActionMove(preprocess()) + MotionEvent.ACTION_UP -> processActionUp(preprocess()) + MotionEvent.ACTION_CANCEL -> + processActionCancel(event.normalize(pointerIndex = 0, overlayParams)) + else -> + Failure("Unsupported MotionEvent." + MotionEvent.actionToString(event.actionMasked)) + } + } +} + +private data class PreprocessedTouch( + val data: NormalizedTouchData, + val previousPointerOnSensorId: Int, + val isGoodOverlap: Boolean, +) + +private fun processActionDown(touch: PreprocessedTouch): TouchProcessorResult { + return if (touch.isGoodOverlap) { + ProcessedTouch(InteractionEvent.DOWN, pointerOnSensorId = touch.data.pointerId, touch.data) + } else { + val event = + if (touch.data.pointerId == touch.previousPointerOnSensorId) { + InteractionEvent.UP + } else { + InteractionEvent.UNCHANGED + } + ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data) + } +} + +private fun processActionMove(touch: PreprocessedTouch): TouchProcessorResult { + val hadPointerOnSensor = touch.previousPointerOnSensorId != INVALID_POINTER_ID + val interactionEvent = + when { + touch.isGoodOverlap && !hadPointerOnSensor -> InteractionEvent.DOWN + !touch.isGoodOverlap && hadPointerOnSensor -> InteractionEvent.UP + else -> InteractionEvent.UNCHANGED + } + val pointerOnSensorId = + when (interactionEvent) { + InteractionEvent.UNCHANGED -> touch.previousPointerOnSensorId + InteractionEvent.DOWN -> touch.data.pointerId + else -> INVALID_POINTER_ID + } + return ProcessedTouch(interactionEvent, pointerOnSensorId, touch.data) +} + +private fun processActionUp(touch: PreprocessedTouch): TouchProcessorResult { + return if (touch.isGoodOverlap) { + ProcessedTouch(InteractionEvent.UP, pointerOnSensorId = INVALID_POINTER_ID, touch.data) + } else { + val event = + if (touch.previousPointerOnSensorId != INVALID_POINTER_ID) { + InteractionEvent.UP + } else { + InteractionEvent.UNCHANGED + } + ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data) + } +} + +private fun processActionCancel(data: NormalizedTouchData): TouchProcessorResult { + return ProcessedTouch(InteractionEvent.CANCEL, pointerOnSensorId = INVALID_POINTER_ID, data) +} + +/** + * Returns the touch information from the given [MotionEvent] with the relevant fields mapped to + * natural orientation and native resolution. + */ +private fun MotionEvent.normalize( + pointerIndex: Int, + overlayParams: UdfpsOverlayParams +): NormalizedTouchData { + val naturalTouch: PointF = rotateToNaturalOrientation(pointerIndex, overlayParams) + val nativeX = naturalTouch.x / overlayParams.scaleFactor + val nativeY = naturalTouch.y / overlayParams.scaleFactor + val nativeMinor: Float = getTouchMinor(pointerIndex) / overlayParams.scaleFactor + val nativeMajor: Float = getTouchMajor(pointerIndex) / overlayParams.scaleFactor + return NormalizedTouchData( + pointerId = getPointerId(pointerIndex), + x = nativeX, + y = nativeY, + minor = nativeMinor, + major = nativeMajor, + // TODO(b/259311354): touch orientation should be reported relative to Surface.ROTATION_O. + orientation = getOrientation(pointerIndex), + time = eventTime, + gestureStart = downTime, + ) +} + +/** + * Returns the [MotionEvent.getRawX] and [MotionEvent.getRawY] of the given pointer as if the device + * is in the [Surface.ROTATION_0] orientation. + */ +private fun MotionEvent.rotateToNaturalOrientation( + pointerIndex: Int, + overlayParams: UdfpsOverlayParams +): PointF { + val touchPoint = PointF(getRawX(pointerIndex), getRawY(pointerIndex)) + val rot = overlayParams.rotation + if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { + RotationUtils.rotatePointF( + touchPoint, + RotationUtils.deltaRotation(rot, Surface.ROTATION_0), + overlayParams.logicalDisplayWidth.toFloat(), + overlayParams.logicalDisplayHeight.toFloat() + ) + } + return touchPoint +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt new file mode 100644 index 000000000000..ffcebf9cff75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt @@ -0,0 +1,47 @@ +/* + * 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.biometrics.udfps + +import android.view.MotionEvent +import com.android.systemui.biometrics.UdfpsOverlayParams + +/** + * Determines whether a finger entered or left the area of the under-display fingerprint sensor + * (UDFPS). Maps the touch information from a [MotionEvent] to the orientation and scale independent + * [NormalizedTouchData]. + */ +interface TouchProcessor { + + /** + * [event] touch event to be processed. + * + * [previousPointerOnSensorId] pointerId for the finger that was on the sensor prior to this + * event. See [MotionEvent.getPointerId]. If there was no finger on the sensor, this should be + * set to [MotionEvent.INVALID_POINTER_ID]. + * + * [overlayParams] contains the location and dimensions of the sensor area, as well as the scale + * factor and orientation of the overlay. See [UdfpsOverlayParams]. + * + * Returns [TouchProcessorResult.ProcessedTouch] on success, and [TouchProcessorResult.Failure] + * on failure. + */ + fun processTouch( + event: MotionEvent, + previousPointerOnSensorId: Int, + overlayParams: UdfpsOverlayParams, + ): TouchProcessorResult +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessorResult.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessorResult.kt new file mode 100644 index 000000000000..be75bb0d3821 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessorResult.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.systemui.biometrics.udfps + +import android.view.MotionEvent + +/** Contains all the possible returns types for [TouchProcessor.processTouch] */ +sealed class TouchProcessorResult { + + /** + * [event] whether a finger entered or left the sensor area. See [InteractionEvent]. + * + * [pointerOnSensorId] pointerId fof the finger that's currently on the sensor. See + * [MotionEvent.getPointerId]. If there is no finger on the sensor, the value is set to + * [MotionEvent.INVALID_POINTER_ID]. + * + * [touchData] relevant data from the MotionEvent, mapped to natural orientation and native + * resolution. See [NormalizedTouchData]. + */ + data class ProcessedTouch( + val event: InteractionEvent, + val pointerOnSensorId: Int, + val touchData: NormalizedTouchData + ) : TouchProcessorResult() + + /** [reason] the reason for the failure. */ + data class Failure(val reason: String = "") : TouchProcessorResult() +} diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index 537cbc5a267d..a0a892de0085 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 @@ private const val TAG = "BroadcastDispatcher" * 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/controls/ControlsSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt new file mode 100644 index 000000000000..3d10ab906f2f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt @@ -0,0 +1,29 @@ +/* + * 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.controls + +import kotlinx.coroutines.flow.StateFlow + +/** Repository for Device controls related settings. */ +interface ControlsSettingsRepository { + /** Whether device controls activity can be shown above lockscreen for this user. */ + val canShowControlsInLockscreen: StateFlow<Boolean> + + /** Whether trivial controls can be actioned from the lockscreen for this user. */ + val allowActionOnTrivialControlsInLockscreen: StateFlow<Boolean> +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt new file mode 100644 index 000000000000..9dc422a09674 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt @@ -0,0 +1,90 @@ +/* + * 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.controls + +import android.provider.Settings +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.SettingObserver +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.settings.SecureSettings +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.stateIn + +/** + * This implementation uses an `@Application` [CoroutineScope] to provide hot flows for the values + * of the tracked settings. + */ +@SysUISingleton +class ControlsSettingsRepositoryImpl +@Inject +constructor( + @Application private val scope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val userRepository: UserRepository, + private val secureSettings: SecureSettings +) : ControlsSettingsRepository { + + override val canShowControlsInLockscreen = + makeFlowForSetting(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS) + + override val allowActionOnTrivialControlsInLockscreen = + makeFlowForSetting(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS) + + @OptIn(ExperimentalCoroutinesApi::class) + private fun makeFlowForSetting(setting: String): StateFlow<Boolean> { + return userRepository.selectedUserInfo + .distinctUntilChanged() + .flatMapLatest { userInfo -> + conflatedCallbackFlow { + val observer = + object : SettingObserver(secureSettings, null, setting, userInfo.id) { + override fun handleValueChanged( + value: Int, + observedChange: Boolean + ) { + trySend(value == 1) + } + } + observer.isListening = true + trySend(observer.value == 1) + awaitClose { observer.isListening = false } + } + .flowOn(backgroundDispatcher) + .distinctUntilChanged() + } + .stateIn( + scope, + started = SharingStarted.Eagerly, + // When the observer starts listening, the flow will emit the current value + // so the initialValue here is irrelevant. + initialValue = false, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 870649d41b4e..7b1c62326a68 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -34,7 +34,6 @@ import com.android.internal.annotations.VisibleForTesting import com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_USER_ID import com.android.systemui.Dumpable import com.android.systemui.backup.BackupHelper -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.management.ControlsListingController @@ -62,11 +61,10 @@ class ControlsControllerImpl @Inject constructor ( private val uiController: ControlsUiController, private val bindingController: ControlsBindingController, private val listingController: ControlsListingController, - private val broadcastDispatcher: BroadcastDispatcher, private val userFileManager: UserFileManager, + private val userTracker: UserTracker, optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>, dumpManager: DumpManager, - userTracker: UserTracker ) : Dumpable, ControlsController { companion object { @@ -123,18 +121,15 @@ class ControlsControllerImpl @Inject constructor ( userChanging = false } - private val userSwitchReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intent.action == Intent.ACTION_USER_SWITCHED) { - userChanging = true - val newUser = - UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId)) - if (currentUser == newUser) { - userChanging = false - return - } - setValuesForUser(newUser) + private val userTrackerCallback = object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + userChanging = true + val newUserHandle = UserHandle.of(newUser) + if (currentUser == newUserHandle) { + userChanging = false + return } + setValuesForUser(newUserHandle) } } @@ -236,12 +231,7 @@ class ControlsControllerImpl @Inject constructor ( dumpManager.registerDumpable(javaClass.name, this) resetFavorites() userChanging = false - broadcastDispatcher.registerReceiver( - userSwitchReceiver, - IntentFilter(Intent.ACTION_USER_SWITCHED), - executor, - UserHandle.ALL - ) + userTracker.addCallback(userTrackerCallback, executor) context.registerReceiver( restoreFinishedReceiver, IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), @@ -253,7 +243,7 @@ class ControlsControllerImpl @Inject constructor ( } fun destroy() { - broadcastDispatcher.unregisterReceiver(userSwitchReceiver) + userTracker.removeCallback(userTrackerCallback) context.unregisterReceiver(restoreFinishedReceiver) listingController.removeCallback(listingCallback) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt index 9e4a364562e5..77d0496e43db 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt @@ -16,13 +16,10 @@ package com.android.systemui.controls.dagger -import android.content.ContentResolver import android.content.Context -import android.database.ContentObserver -import android.os.UserHandle -import android.provider.Settings import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT +import com.android.systemui.controls.ControlsSettingsRepository import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlsTileResourceConfiguration import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl @@ -31,12 +28,10 @@ import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.settings.SecureSettings import dagger.Lazy +import kotlinx.coroutines.flow.StateFlow import java.util.Optional import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow /** * Pseudo-component to inject into classes outside `com.android.systemui.controls`. @@ -46,47 +41,26 @@ import kotlinx.coroutines.flow.asStateFlow */ @SysUISingleton class ControlsComponent @Inject constructor( - @ControlsFeatureEnabled private val featureEnabled: Boolean, - private val context: Context, - private val lazyControlsController: Lazy<ControlsController>, - private val lazyControlsUiController: Lazy<ControlsUiController>, - private val lazyControlsListingController: Lazy<ControlsListingController>, - private val lockPatternUtils: LockPatternUtils, - private val keyguardStateController: KeyguardStateController, - private val userTracker: UserTracker, - private val secureSettings: SecureSettings, - private val optionalControlsTileResourceConfiguration: - Optional<ControlsTileResourceConfiguration> + @ControlsFeatureEnabled private val featureEnabled: Boolean, + private val context: Context, + private val lazyControlsController: Lazy<ControlsController>, + private val lazyControlsUiController: Lazy<ControlsUiController>, + private val lazyControlsListingController: Lazy<ControlsListingController>, + private val lockPatternUtils: LockPatternUtils, + private val keyguardStateController: KeyguardStateController, + private val userTracker: UserTracker, + controlsSettingsRepository: ControlsSettingsRepository, + optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration> ) { - private val contentResolver: ContentResolver - get() = context.contentResolver - private val _canShowWhileLockedSetting = MutableStateFlow(false) - val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow() + val canShowWhileLockedSetting: StateFlow<Boolean> = + controlsSettingsRepository.canShowControlsInLockscreen private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration = optionalControlsTileResourceConfiguration.orElse( ControlsTileResourceConfigurationImpl() ) - val showWhileLockedObserver = object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - updateShowWhileLocked() - } - } - - init { - if (featureEnabled) { - secureSettings.registerContentObserverForUser( - Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), - false, /* notifyForDescendants */ - showWhileLockedObserver, - UserHandle.USER_ALL - ) - updateShowWhileLocked() - } - } - fun getControlsController(): Optional<ControlsController> { return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty() } @@ -127,11 +101,6 @@ class ControlsComponent @Inject constructor( return Visibility.AVAILABLE } - private fun updateShowWhileLocked() { - _canShowWhileLockedSetting.value = secureSettings.getIntForUser( - Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0 - } - enum class Visibility { AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE } diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt index 6f58abdeed56..9ae605e30a83 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt @@ -20,6 +20,8 @@ import android.app.Activity import android.content.pm.PackageManager import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.ControlsMetricsLoggerImpl +import com.android.systemui.controls.ControlsSettingsRepository +import com.android.systemui.controls.ControlsSettingsRepositoryImpl import com.android.systemui.controls.controller.ControlsBindingController import com.android.systemui.controls.controller.ControlsBindingControllerImpl import com.android.systemui.controls.controller.ControlsController @@ -83,6 +85,11 @@ abstract class ControlsModule { abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController @Binds + abstract fun provideSettingsManager( + manager: ControlsSettingsRepositoryImpl + ): ControlsSettingsRepository + + @Binds abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger @Binds diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index 1f7021e514e5..041ed1d557d7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -25,9 +25,6 @@ import android.app.PendingIntent import android.content.Context import android.content.pm.PackageManager import android.content.pm.ResolveInfo -import android.database.ContentObserver -import android.net.Uri -import android.os.Handler import android.os.UserHandle import android.os.VibrationEffect import android.provider.Settings.Secure @@ -41,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.R import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.controls.ControlsMetricsLogger +import com.android.systemui.controls.ControlsSettingsRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter @@ -69,17 +67,17 @@ class ControlActionCoordinatorImpl @Inject constructor( private val vibrator: VibratorHelper, private val secureSettings: SecureSettings, private val userContextProvider: UserContextProvider, - @Main mainHandler: Handler + private val controlsSettingsRepository: ControlsSettingsRepository, ) : ControlActionCoordinator { private var dialog: Dialog? = null private var pendingAction: Action? = null private var actionsInProgress = mutableSetOf<String>() private val isLocked: Boolean get() = !keyguardStateController.isUnlocked() - private var mAllowTrivialControls: Boolean = secureSettings.getIntForUser( - Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 0, UserHandle.USER_CURRENT) != 0 - private var mShowDeviceControlsInLockscreen: Boolean = secureSettings.getIntForUser( - Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0 + private val allowTrivialControls: Boolean + get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value + private val showDeviceControlsInLockscreen: Boolean + get() = controlsSettingsRepository.canShowControlsInLockscreen.value override lateinit var activityContext: Context companion object { @@ -87,38 +85,6 @@ class ControlActionCoordinatorImpl @Inject constructor( private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2 } - init { - val lockScreenShowControlsUri = - secureSettings.getUriFor(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS) - val showControlsUri = - secureSettings.getUriFor(Secure.LOCKSCREEN_SHOW_CONTROLS) - val controlsContentObserver = object : ContentObserver(mainHandler) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - super.onChange(selfChange, uri) - when (uri) { - lockScreenShowControlsUri -> { - mAllowTrivialControls = secureSettings.getIntForUser( - Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, - 0, UserHandle.USER_CURRENT) != 0 - } - showControlsUri -> { - mShowDeviceControlsInLockscreen = secureSettings - .getIntForUser(Secure.LOCKSCREEN_SHOW_CONTROLS, - 0, UserHandle.USER_CURRENT) != 0 - } - } - } - } - secureSettings.registerContentObserverForUser( - lockScreenShowControlsUri, - false /* notifyForDescendants */, controlsContentObserver, UserHandle.USER_ALL - ) - secureSettings.registerContentObserverForUser( - showControlsUri, - false /* notifyForDescendants */, controlsContentObserver, UserHandle.USER_ALL - ) - } - override fun closeDialogs() { val isActivityFinishing = (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed } @@ -233,7 +199,7 @@ class ControlActionCoordinatorImpl @Inject constructor( @AnyThread @VisibleForTesting fun bouncerOrRun(action: Action) { - val authRequired = action.authIsRequired || !mAllowTrivialControls + val authRequired = action.authIsRequired || !allowTrivialControls if (keyguardStateController.isShowing() && authRequired) { if (isLocked) { @@ -291,7 +257,7 @@ class ControlActionCoordinatorImpl @Inject constructor( PREFS_CONTROLS_FILE, Context.MODE_PRIVATE) val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0) if (attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG || - (mShowDeviceControlsInLockscreen && mAllowTrivialControls)) { + (showDeviceControlsInLockscreen && allowTrivialControls)) { return } val builder = AlertDialog @@ -313,7 +279,7 @@ class ControlActionCoordinatorImpl @Inject constructor( true } - if (mShowDeviceControlsInLockscreen) { + if (showDeviceControlsInLockscreen) { dialog = builder .setTitle(R.string.controls_settings_trivial_controls_dialog_title) .setMessage(R.string.controls_settings_trivial_controls_dialog_message) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java index d60a22204b3d..3d8e4cb71aca 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java @@ -19,7 +19,6 @@ package com.android.systemui.dagger; import android.content.BroadcastReceiver; import com.android.systemui.GuestResetOrExitSessionReceiver; -import com.android.systemui.GuestResumeSessionReceiver; import com.android.systemui.media.dialog.MediaOutputDialogReceiver; import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; @@ -106,15 +105,6 @@ public abstract class DefaultBroadcastReceiverBinder { */ @Binds @IntoMap - @ClassKey(GuestResumeSessionReceiver.class) - public abstract BroadcastReceiver bindGuestResumeSessionReceiver( - GuestResumeSessionReceiver broadcastReceiver); - - /** - * - */ - @Binds - @IntoMap @ClassKey(GuestResetOrExitSessionReceiver.class) public abstract BroadcastReceiver bindGuestResetOrExitSessionReceiver( GuestResetOrExitSessionReceiver broadcastReceiver); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index bcf5e7ae3ed6..3a59f4b5436c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -33,6 +33,7 @@ import com.android.systemui.assist.AssistModule; import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; import com.android.systemui.biometrics.UdfpsDisplayModeProvider; import com.android.systemui.biometrics.dagger.BiometricsModule; +import com.android.systemui.biometrics.dagger.UdfpsModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; import com.android.systemui.controls.dagger.ControlsModule; @@ -154,6 +155,7 @@ import dagger.Provides; TelephonyRepositoryModule.class, TemporaryDisplayModule.class, TunerModule.class, + UdfpsModule.class, UserModule.class, UtilModule.class, NoteTaskModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 0b69b80689e0..5daf1ceaf592 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -29,12 +29,13 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.display.AmbientDisplayConfiguration; import android.os.SystemClock; -import android.os.UserHandle; import android.text.format.Formatter; import android.util.IndentingPrintWriter; import android.util.Log; import android.view.Display; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.logging.UiEvent; @@ -100,6 +101,7 @@ public class DozeTriggers implements DozeMachine.Part { private final BroadcastDispatcher mBroadcastDispatcher; private final AuthController mAuthController; private final KeyguardStateController mKeyguardStateController; + private final UserTracker mUserTracker; private final UiEventLogger mUiEventLogger; private long mNotificationPulseTime; @@ -110,6 +112,14 @@ public class DozeTriggers implements DozeMachine.Part { private boolean mWantTouchScreenSensors; private boolean mWantSensors; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mDozeSensors.onUserSwitched(); + } + }; + @VisibleForTesting public enum DozingUpdateUiEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "Dozing updated due to notification.") @@ -210,6 +220,7 @@ public class DozeTriggers implements DozeMachine.Part { mAuthController = authController; mUiEventLogger = uiEventLogger; mKeyguardStateController = keyguardStateController; + mUserTracker = userTracker; } @Override @@ -234,7 +245,7 @@ public class DozeTriggers implements DozeMachine.Part { return; } mNotificationPulseTime = SystemClock.elapsedRealtime(); - if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) { + if (!mConfig.pulseOnNotificationEnabled(mUserTracker.getUserId())) { runIfNotNull(onPulseSuppressedListener); mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled"); return; @@ -490,12 +501,14 @@ public class DozeTriggers implements DozeMachine.Part { mBroadcastReceiver.register(mBroadcastDispatcher); mDockManager.addListener(mDockEventListener); mDozeHost.addCallback(mHostCallback); + mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); } private void unregisterCallbacks() { mBroadcastReceiver.unregister(mBroadcastDispatcher); mDozeHost.removeCallback(mHostCallback); mDockManager.removeListener(mDockEventListener); + mUserTracker.removeCallback(mUserChangedCallback); } private void stopListeningToAllTriggers() { @@ -620,9 +633,6 @@ public class DozeTriggers implements DozeMachine.Part { requestPulse(DozeLog.PULSE_REASON_INTENT, false, /* performedProxCheck */ null /* onPulseSuppressedListener */); } - if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { - mDozeSensors.onUserSwitched(); - } } public void register(BroadcastDispatcher broadcastDispatcher) { @@ -630,7 +640,6 @@ public class DozeTriggers implements DozeMachine.Part { return; } IntentFilter filter = new IntentFilter(PULSE_ACTION); - filter.addAction(Intent.ACTION_USER_SWITCHED); broadcastDispatcher.registerReceiver(this, filter); mRegistered = true; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java index 48159aed524e..46ce7a90f5a3 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java @@ -192,9 +192,7 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll break; } - // Add margin if specified by the complication. Otherwise add default margin - // between complications. - if (mLayoutParams.isMarginSpecified() || !isRoot) { + if (!isRoot) { final int margin = mLayoutParams.getMargin(mDefaultMargin); switch(direction) { case ComplicationLayoutParams.DIRECTION_DOWN: @@ -213,6 +211,19 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll } }); + if (mLayoutParams.constraintSpecified()) { + switch (direction) { + case ComplicationLayoutParams.DIRECTION_START: + case ComplicationLayoutParams.DIRECTION_END: + params.matchConstraintMaxWidth = mLayoutParams.getConstraint(); + break; + case ComplicationLayoutParams.DIRECTION_UP: + case ComplicationLayoutParams.DIRECTION_DOWN: + params.matchConstraintMaxHeight = mLayoutParams.getConstraint(); + break; + } + } + mView.setLayoutParams(params); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java index 4fae68d57ee8..1755cb92da70 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java @@ -52,6 +52,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { private static final int LAST_POSITION = POSITION_END; private static final int MARGIN_UNSPECIFIED = 0xFFFFFFFF; + private static final int CONSTRAINT_UNSPECIFIED = 0xFFFFFFFF; @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "DIRECTION_" }, value = { @@ -81,6 +82,8 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { private final int mMargin; + private final int mConstraint; + private final boolean mSnapToGuide; // Do not allow specifying opposite positions @@ -110,7 +113,8 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { */ public ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight) { - this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, false); + this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, CONSTRAINT_UNSPECIFIED, + false); } /** @@ -127,7 +131,27 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { */ public ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight, int margin) { - this(width, height, position, direction, weight, margin, false); + this(width, height, position, direction, weight, margin, CONSTRAINT_UNSPECIFIED, false); + } + + /** + * Constructs a {@link ComplicationLayoutParams}. + * @param width The width {@link android.view.View.MeasureSpec} for the view. + * @param height The height {@link android.view.View.MeasureSpec} for the view. + * @param position The place within the parent container where the view should be positioned. + * @param direction The direction the view should be laid out from either the parent container + * or preceding view. + * @param weight The weight that should be considered for this view when compared to other + * views. This has an impact on the placement of the view but not the rendering of + * the view. + * @param margin The margin to apply between complications. + * @param constraint The max width or height the complication is allowed to spread, depending on + * its direction. For horizontal directions, this would be applied on width, + * and for vertical directions, height. + */ + public ComplicationLayoutParams(int width, int height, @Position int position, + @Direction int direction, int weight, int margin, int constraint) { + this(width, height, position, direction, weight, margin, constraint, false); } /** @@ -148,7 +172,8 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { */ public ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight, boolean snapToGuide) { - this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, snapToGuide); + this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, CONSTRAINT_UNSPECIFIED, + snapToGuide); } /** @@ -162,6 +187,9 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { * views. This has an impact on the placement of the view but not the rendering of * the view. * @param margin The margin to apply between complications. + * @param constraint The max width or height the complication is allowed to spread, depending on + * its direction. For horizontal directions, this would be applied on width, + * and for vertical directions, height. * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction * will be automatically set to align with a predetermined guide for that * side. For example, if the complication is aligned to the top end and @@ -169,7 +197,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { * from the end of the parent to the guide. */ public ComplicationLayoutParams(int width, int height, @Position int position, - @Direction int direction, int weight, int margin, boolean snapToGuide) { + @Direction int direction, int weight, int margin, int constraint, boolean snapToGuide) { super(width, height); if (!validatePosition(position)) { @@ -187,6 +215,8 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { mMargin = margin; + mConstraint = constraint; + mSnapToGuide = snapToGuide; } @@ -199,6 +229,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { mDirection = source.mDirection; mWeight = source.mWeight; mMargin = source.mMargin; + mConstraint = source.mConstraint; mSnapToGuide = source.mSnapToGuide; } @@ -261,13 +292,6 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { } /** - * Returns whether margin has been specified by the complication. - */ - public boolean isMarginSpecified() { - return mMargin != MARGIN_UNSPECIFIED; - } - - /** * Returns the margin to apply between complications, or the given default if no margin is * specified. */ @@ -276,6 +300,21 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { } /** + * Returns whether the horizontal or vertical constraint has been specified. + */ + public boolean constraintSpecified() { + return mConstraint != CONSTRAINT_UNSPECIFIED; + } + + /** + * Returns the horizontal or vertical constraint of the complication, depending its direction. + * For horizontal directions, this is the max width, and for vertical directions, max height. + */ + public int getConstraint() { + return mConstraint; + } + + /** * Returns whether the complication's dimension perpendicular to direction should be * automatically set. */ diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java index c01cf43eae74..ee0051220787 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java @@ -32,6 +32,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; +import com.android.systemui.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.controls.ControlsServiceInfo; import com.android.systemui.controls.dagger.ControlsComponent; @@ -151,7 +152,7 @@ public class DreamHomeControlsComplication implements Complication { @Inject DreamHomeControlsChipViewHolder( DreamHomeControlsChipViewController dreamHomeControlsChipViewController, - @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, + @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view, @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams ) { mView = view; @@ -174,7 +175,7 @@ public class DreamHomeControlsComplication implements Complication { /** * Controls behavior of the dream complication. */ - static class DreamHomeControlsChipViewController extends ViewController<ImageView> { + static class DreamHomeControlsChipViewController extends ViewController<View> { private static final String TAG = "DreamHomeControlsCtrl"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -203,7 +204,7 @@ public class DreamHomeControlsComplication implements Complication { @Inject DreamHomeControlsChipViewController( - @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, + @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view, ActivityStarter activityStarter, Context context, ControlsComponent controlsComponent, @@ -218,9 +219,10 @@ public class DreamHomeControlsComplication implements Complication { @Override protected void onViewAttached() { - mView.setImageResource(mControlsComponent.getTileImageId()); - mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId())); - mView.setOnClickListener(this::onClickHomeControls); + final ImageView chip = mView.findViewById(R.id.home_controls_chip); + chip.setImageResource(mControlsComponent.getTileImageId()); + chip.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId())); + chip.setOnClickListener(this::onClickHomeControls); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java index 7d9f1059f3b8..5290e44aa7fb 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java @@ -45,11 +45,12 @@ public interface DreamClockTimeComplicationModule { @Provides @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) static View provideComplicationView(LayoutInflater layoutInflater) { - final TextClock view = Preconditions.checkNotNull((TextClock) + final View view = Preconditions.checkNotNull( layoutInflater.inflate(R.layout.dream_overlay_complication_clock_time, null, false), "R.layout.dream_overlay_complication_clock_time did not properly inflated"); - view.setFontVariationSettings(TAG_WEIGHT + WEIGHT); + ((TextClock) view.findViewById(R.id.time_view)).setFontVariationSettings( + TAG_WEIGHT + WEIGHT); return view; } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java index cf05d2d9cda0..a7aa97f74e31 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java @@ -19,7 +19,7 @@ package com.android.systemui.dreams.complication.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; import android.view.LayoutInflater; -import android.widget.ImageView; +import android.view.View; import com.android.systemui.R; import com.android.systemui.dreams.complication.DreamHomeControlsComplication; @@ -74,8 +74,8 @@ public interface DreamHomeControlsComplicationComponent { @Provides @DreamHomeControlsComplicationScope @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) - static ImageView provideHomeControlsChipView(LayoutInflater layoutInflater) { - return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip, + static View provideHomeControlsChipView(LayoutInflater layoutInflater) { + return layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip, null, false); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java index a514c47f1fc1..9b954f5f0c4a 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java @@ -47,10 +47,10 @@ public interface RegisteredComplicationsModule { String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params"; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1; - int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0; + int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 2; int DREAM_MEDIA_COMPLICATION_WEIGHT = 0; - int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 2; - int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 1; + int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 4; + int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 3; /** * Provides layout parameters for the clock time complication. @@ -72,17 +72,14 @@ public interface RegisteredComplicationsModule { */ @Provides @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) - static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) { + static ComplicationLayoutParams provideHomeControlsChipLayoutParams() { return new ComplicationLayoutParams( - res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width), - res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height), + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, ComplicationLayoutParams.POSITION_BOTTOM | ComplicationLayoutParams.POSITION_START, - ComplicationLayoutParams.DIRECTION_UP, - DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT, - // Add margin to the bottom of home controls to horizontally align with smartspace. - res.getDimensionPixelSize( - R.dimen.dream_overlay_complication_home_controls_padding)); + ComplicationLayoutParams.DIRECTION_END, + DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT); } /** @@ -106,12 +103,14 @@ public interface RegisteredComplicationsModule { @Provides @Named(DREAM_SMARTSPACE_LAYOUT_PARAMS) static ComplicationLayoutParams provideSmartspaceLayoutParams(@Main Resources res) { - return new ComplicationLayoutParams(0, + return new ComplicationLayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, ComplicationLayoutParams.POSITION_BOTTOM | ComplicationLayoutParams.POSITION_START, ComplicationLayoutParams.DIRECTION_END, DREAM_SMARTSPACE_COMPLICATION_WEIGHT, - res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding)); + res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding), + res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_max_width)); } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index aa6c619d9969..76ed3047fb68 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -205,15 +205,6 @@ object Flags { @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info") // 600- status bar - // TODO(b/254512623): Tracking Bug - @Deprecated("Replaced by mobile and wifi specific flags.") - val NEW_STATUS_BAR_PIPELINE_BACKEND = - unreleasedFlag(604, "new_status_bar_pipeline_backend", teamfood = false) - - // TODO(b/254512660): Tracking Bug - @Deprecated("Replaced by mobile and wifi specific flags.") - val NEW_STATUS_BAR_PIPELINE_FRONTEND = - unreleasedFlag(605, "new_status_bar_pipeline_frontend", teamfood = false) // TODO(b/256614753): Tracking Bug val NEW_STATUS_BAR_MOBILE_ICONS = unreleasedFlag(606, "new_status_bar_mobile_icons") @@ -231,6 +222,10 @@ object Flags { // TODO(b/256623670): Tracking Bug @JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon") + // TODO(b/260881289): Tracking Bug + val NEW_STATUS_BAR_ICONS_DEBUG_COLORING = + unreleasedFlag(611, "new_status_bar_icons_debug_coloring") + // 700 - dialer/calls // TODO(b/254512734): Tracking Bug val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip") @@ -346,6 +341,12 @@ object Flags { // 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 @@ -369,7 +370,7 @@ object Flags { // TODO(b/255854141): Tracking Bug @JvmField val WM_ENABLE_PREDICTIVE_BACK_SYSUI = - unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = false) + unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true) // 1300 - screenshots // TODO(b/254512719): Tracking Bug @@ -395,9 +396,7 @@ object Flags { // 1700 - clipboard @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor") - @JvmField - val CLIPBOARD_REMOTE_BEHAVIOR = - unreleasedFlag(1701, "clipboard_remote_behavior", teamfood = true) + @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior") // 1800 - shade container @JvmField @@ -422,6 +421,12 @@ object Flags { // 2300 - stylus @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used") + // 2400 - performance tools and debugging info + // TODO(b/238923086): Tracking Bug + @JvmField + val WARN_ON_BLOCKING_BINDER_TRANSACTIONS = + unreleasedFlag(2400, "warn_on_blocking_binder_transactions") + // TODO(b259590361): Tracking bug val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release") } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt index 29febb6dd0d9..4ae37c51f278 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 android.util.Log 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 021431399ab6..e631816991aa 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 @@ public class KeyguardService extends Service { 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 @@ public class KeyguardService extends Service { 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 @@ public class KeyguardService extends Service { 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 @@ public class KeyguardService extends Service { 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 5ed3ba76cc22..948239a58840 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @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 3c09aab60443..dbc376e62950 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.ContentDescription 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 @@ class CameraQuickAffordanceConfig @Inject constructor( 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/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt index 49527d32d229..62fe80a82908 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt @@ -21,50 +21,52 @@ import android.content.Context import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.statusbar.policy.FlashlightController +import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import javax.inject.Inject @SysUISingleton -class FlashlightQuickAffordanceConfig @Inject constructor( - @Application private val context: Context, - private val flashlightController: FlashlightController, +class FlashlightQuickAffordanceConfig +@Inject +constructor( + @Application private val context: Context, + private val flashlightController: FlashlightController, ) : KeyguardQuickAffordanceConfig { private sealed class FlashlightState { abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState - object On: FlashlightState() { + object On : FlashlightState() { override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( - R.drawable.ic_flashlight_on, + R.drawable.qs_flashlight_icon_on, ContentDescription.Resource(R.string.quick_settings_flashlight_label) ), ActivationState.Active ) } - object OffAvailable: FlashlightState() { + object OffAvailable : FlashlightState() { override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = KeyguardQuickAffordanceConfig.LockScreenState.Visible( Icon.Resource( - R.drawable.ic_flashlight_off, + R.drawable.qs_flashlight_icon_off, ContentDescription.Resource(R.string.quick_settings_flashlight_label) ), ActivationState.Inactive ) } - object Unavailable: FlashlightState() { + object Unavailable : FlashlightState() { override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = KeyguardQuickAffordanceConfig.LockScreenState.Hidden } @@ -77,57 +79,57 @@ class FlashlightQuickAffordanceConfig @Inject constructor( get() = context.getString(R.string.quick_settings_flashlight_label) override val pickerIconResourceId: Int - get() = if (flashlightController.isEnabled) { - R.drawable.ic_flashlight_on - } else { - R.drawable.ic_flashlight_off - } + get() = R.drawable.ic_flashlight_off override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = - conflatedCallbackFlow { - val flashlightCallback = object : FlashlightController.FlashlightListener { - override fun onFlashlightChanged(enabled: Boolean) { - trySendWithFailureLogging( - if (enabled) { - FlashlightState.On.toLockScreenState() - } else { - FlashlightState.OffAvailable.toLockScreenState() - }, - TAG - ) - } - - override fun onFlashlightError() { - trySendWithFailureLogging(FlashlightState.OffAvailable.toLockScreenState(), TAG) - } - - override fun onFlashlightAvailabilityChanged(available: Boolean) { - trySendWithFailureLogging( - if (!available) { - FlashlightState.Unavailable.toLockScreenState() - } else { - if (flashlightController.isEnabled) { - FlashlightState.On.toLockScreenState() - } else { - FlashlightState.OffAvailable.toLockScreenState() - } - }, - TAG - ) - } + conflatedCallbackFlow { + val flashlightCallback = + object : FlashlightController.FlashlightListener { + override fun onFlashlightChanged(enabled: Boolean) { + trySendWithFailureLogging( + if (enabled) { + FlashlightState.On.toLockScreenState() + } else { + FlashlightState.OffAvailable.toLockScreenState() + }, + TAG + ) + } + + override fun onFlashlightError() { + trySendWithFailureLogging( + FlashlightState.OffAvailable.toLockScreenState(), + TAG + ) + } + + override fun onFlashlightAvailabilityChanged(available: Boolean) { + trySendWithFailureLogging( + if (!available) { + FlashlightState.Unavailable.toLockScreenState() + } else { + if (flashlightController.isEnabled) { + FlashlightState.On.toLockScreenState() + } else { + FlashlightState.OffAvailable.toLockScreenState() + } + }, + TAG + ) + } + } + + flashlightController.addCallback(flashlightCallback) + + awaitClose { flashlightController.removeCallback(flashlightCallback) } } - flashlightController.addCallback(flashlightCallback) - - awaitClose { - flashlightController.removeCallback(flashlightCallback) - } - } - - override fun onTriggered(expandable: Expandable?): - KeyguardQuickAffordanceConfig.OnTriggeredResult { - flashlightController - .setFlashlight(flashlightController.isAvailable && !flashlightController.isEnabled) + override fun onTriggered( + expandable: Expandable? + ): KeyguardQuickAffordanceConfig.OnTriggeredResult { + flashlightController.setFlashlight( + flashlightController.isAvailable && !flashlightController.isEnabled + ) return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled } @@ -141,4 +143,4 @@ class FlashlightQuickAffordanceConfig @Inject constructor( companion object { private const val TAG = "FlashlightQuickAffordanceConfig" } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt index 3013227c21c0..072cfb140e62 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -17,27 +17,35 @@ package com.android.systemui.keyguard.data.quickaffordance +import dagger.Binds import dagger.Module import dagger.Provides import dagger.multibindings.ElementsIntoSet @Module -object KeyguardDataQuickAffordanceModule { - @Provides - @ElementsIntoSet - fun quickAffordanceConfigs( - flashlight: FlashlightQuickAffordanceConfig, - home: HomeControlsKeyguardQuickAffordanceConfig, - quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, - qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig, - camera: CameraQuickAffordanceConfig, - ): Set<KeyguardQuickAffordanceConfig> { - return setOf( - camera, - flashlight, - home, - quickAccessWallet, - qrCodeScanner, - ) +interface KeyguardDataQuickAffordanceModule { + @Binds + fun providerClientFactory( + impl: KeyguardQuickAffordanceProviderClientFactoryImpl, + ): KeyguardQuickAffordanceProviderClientFactory + + companion object { + @Provides + @ElementsIntoSet + fun quickAffordanceConfigs( + flashlight: FlashlightQuickAffordanceConfig, + home: HomeControlsKeyguardQuickAffordanceConfig, + quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, + qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig, + camera: CameraQuickAffordanceConfig, + ): Set<KeyguardQuickAffordanceConfig> { + return setOf( + camera, + flashlight, + home, + quickAccessWallet, + qrCodeScanner, + ) + } } } 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 4477310dca41..98b1a731b82c 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 android.content.Intent 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/KeyguardQuickAffordanceLegacySettingSyncer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt index 766096f1fa2b..72747f68bbbd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt @@ -67,7 +67,7 @@ constructor( @Application private val scope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, private val secureSettings: SecureSettings, - private val selectionsManager: KeyguardQuickAffordanceSelectionManager, + private val selectionsManager: KeyguardQuickAffordanceLocalUserSelectionManager, ) { companion object { private val BINDINGS = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt new file mode 100644 index 000000000000..006678546de8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt @@ -0,0 +1,184 @@ +/* + * 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.data.quickaffordance + +import android.content.Context +import android.content.IntentFilter +import android.content.SharedPreferences +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 +import com.android.systemui.dagger.qualifiers.Application +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?" for the user associated with the + * System UI process. + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class KeyguardQuickAffordanceLocalUserSelectionManager +@Inject +constructor( + @Application context: Context, + private val userFileManager: UserFileManager, + private val userTracker: UserTracker, + broadcastDispatcher: BroadcastDispatcher, +) : KeyguardQuickAffordanceSelectionManager { + + private var sharedPrefs: SharedPreferences = instantiateSharedPrefs() + + private val userId: Flow<Int> = conflatedCallbackFlow { + val callback = + object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + trySendWithFailureLogging(newUser, TAG) + } + } + + userTracker.addCallback(callback) { it.run() } + trySendWithFailureLogging(userTracker.userId, TAG) + + awaitClose { userTracker.removeCallback(callback) } + } + + private val defaults: Map<String, List<String>> by lazy { + context.resources + .getStringArray(R.array.config_keyguardQuickAffordanceDefaults) + .associate { item -> + val splitUp = item.split(SLOT_AFFORDANCES_DELIMITER) + check(splitUp.size == 2) + val slotId = splitUp[0] + val affordanceIds = splitUp[1].split(AFFORDANCE_DELIMITER) + slotId to affordanceIds + } + } + + /** + * 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, + ) + + override val selections: Flow<Map<String, List<String>>> = + 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) } + } + } + + override fun getSelections(): Map<String, List<String>> { + val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) } + val result = + slotKeys + .associate { key -> + val slotId = key.substring(KEY_PREFIX_SLOT.length) + val value = sharedPrefs.getString(key, null) + val affordanceIds = + if (!value.isNullOrEmpty()) { + value.split(AFFORDANCE_DELIMITER) + } else { + emptyList() + } + slotId to affordanceIds + } + .toMutableMap() + + // If the result map is missing keys, it means that the system has never set anything for + // those slots. This is where we need examine our defaults and see if there should be a + // default value for the affordances in the slot IDs that are missing from the result. + // + // Once the user makes any selection for a slot, even when they select "None", this class + // will persist a key for that slot ID. In the case of "None", it will have a value of the + // empty string. This is why this system works. + defaults.forEach { (slotId, affordanceIds) -> + if (!result.containsKey(slotId)) { + result[slotId] = affordanceIds + } + } + + return result + } + + override fun setSelections( + slotId: String, + affordanceIds: List<String>, + ) { + val key = "$KEY_PREFIX_SLOT$slotId" + val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER) + 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 = "KeyguardQuickAffordancePrimaryUserSelectionManager" + 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/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt new file mode 100644 index 000000000000..727a81391dc2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt @@ -0,0 +1,43 @@ +/* + * 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.data.quickaffordance + +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserTracker +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClientImpl +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher + +interface KeyguardQuickAffordanceProviderClientFactory { + fun create(): KeyguardQuickAffordanceProviderClient +} + +class KeyguardQuickAffordanceProviderClientFactoryImpl +@Inject +constructor( + private val userTracker: UserTracker, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) : KeyguardQuickAffordanceProviderClientFactory { + override fun create(): KeyguardQuickAffordanceProviderClient { + return KeyguardQuickAffordanceProviderClientImpl( + context = userTracker.userContext, + backgroundDispatcher = backgroundDispatcher, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt new file mode 100644 index 000000000000..8ffef25d3aae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt @@ -0,0 +1,129 @@ +/* + * 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.data.quickaffordance + +import android.content.Context +import android.os.UserHandle +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.settings.UserTracker +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +/** + * Manages and provides access to the current "selections" of keyguard quick affordances, answering + * the question "which affordances should the keyguard show?" for users associated with other System + * UI processes. + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class KeyguardQuickAffordanceRemoteUserSelectionManager +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val userTracker: UserTracker, + private val clientFactory: KeyguardQuickAffordanceProviderClientFactory, + private val userHandle: UserHandle, +) : KeyguardQuickAffordanceSelectionManager { + + private val userId: Flow<Int> = conflatedCallbackFlow { + val callback = + object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + trySendWithFailureLogging(newUser, TAG) + } + } + + userTracker.addCallback(callback) { it.run() } + trySendWithFailureLogging(userTracker.userId, TAG) + + awaitClose { userTracker.removeCallback(callback) } + } + + private val clientOrNull: StateFlow<KeyguardQuickAffordanceProviderClient?> = + userId + .distinctUntilChanged() + .map { selectedUserId -> + if (userHandle.isSystem && userHandle.identifier != selectedUserId) { + clientFactory.create() + } else { + null + } + } + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = null, + ) + + private val _selections: StateFlow<Map<String, List<String>>> = + clientOrNull + .flatMapLatest { client -> + client?.observeSelections()?.map { selections -> + buildMap<String, List<String>> { + selections.forEach { selection -> + val slotId = selection.slotId + val affordanceIds = (get(slotId) ?: emptyList()).toMutableList() + affordanceIds.add(selection.affordanceId) + put(slotId, affordanceIds) + } + } + } + ?: emptyFlow() + } + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = emptyMap(), + ) + + override val selections: Flow<Map<String, List<String>>> = _selections + + override fun getSelections(): Map<String, List<String>> { + return _selections.value + } + + override fun setSelections(slotId: String, affordanceIds: List<String>) { + clientOrNull.value?.let { client -> + scope.launch { + client.deleteAllSelections(slotId = slotId) + affordanceIds.forEach { affordanceId -> + client.insertSelection(slotId = slotId, affordanceId = affordanceId) + } + } + } + } + + companion object { + private const val TAG = "KeyguardQuickAffordanceMultiUserSelectionManager" + } +} 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 b29cf45cc709..21fffede5f97 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 @@ -17,119 +17,22 @@ package com.android.systemui.keyguard.data.quickaffordance -import android.content.Context -import android.content.SharedPreferences -import androidx.annotation.VisibleForTesting -import com.android.systemui.R -import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.settings.UserFileManager -import com.android.systemui.settings.UserTracker -import javax.inject.Inject -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flatMapLatest /** - * Manages and provides access to the current "selections" of keyguard quick affordances, answering - * the question "which affordances should the keyguard show?". + * Defines interface for classes that manage and provide access to the current "selections" of + * keyguard quick affordances, answering the question "which affordances should the keyguard show?". */ -@SysUISingleton -class KeyguardQuickAffordanceSelectionManager -@Inject -constructor( - @Application context: Context, - private val userFileManager: UserFileManager, - private val userTracker: UserTracker, -) { - - private val sharedPrefs: SharedPreferences - get() = - userFileManager.getSharedPreferences( - FILE_NAME, - Context.MODE_PRIVATE, - userTracker.userId, - ) - - private val userId: Flow<Int> = conflatedCallbackFlow { - val callback = - object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { - trySendWithFailureLogging(newUser, TAG) - } - } - - userTracker.addCallback(callback) { it.run() } - trySendWithFailureLogging(userTracker.userId, TAG) - - awaitClose { userTracker.removeCallback(callback) } - } - private val defaults: Map<String, List<String>> by lazy { - context.resources - .getStringArray(R.array.config_keyguardQuickAffordanceDefaults) - .associate { item -> - val splitUp = item.split(SLOT_AFFORDANCES_DELIMITER) - check(splitUp.size == 2) - val slotId = splitUp[0] - val affordanceIds = splitUp[1].split(AFFORDANCE_DELIMITER) - slotId to affordanceIds - } - } +interface KeyguardQuickAffordanceSelectionManager { /** 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) } - } - } + val selections: Flow<Map<String, List<String>>> /** * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in * descending priority order. */ - fun getSelections(): Map<String, List<String>> { - val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) } - val result = - slotKeys - .associate { key -> - val slotId = key.substring(KEY_PREFIX_SLOT.length) - val value = sharedPrefs.getString(key, null) - val affordanceIds = - if (!value.isNullOrEmpty()) { - value.split(AFFORDANCE_DELIMITER) - } else { - emptyList() - } - slotId to affordanceIds - } - .toMutableMap() - - // If the result map is missing keys, it means that the system has never set anything for - // those slots. This is where we need examine our defaults and see if there should be a - // default value for the affordances in the slot IDs that are missing from the result. - // - // Once the user makes any selection for a slot, even when they select "None", this class - // will persist a key for that slot ID. In the case of "None", it will have a value of the - // empty string. This is why this system works. - defaults.forEach { (slotId, affordanceIds) -> - if (!result.containsKey(slotId)) { - result[slotId] = affordanceIds - } - } - - return result - } + fun getSelections(): Map<String, List<String>> /** * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance @@ -138,17 +41,9 @@ constructor( fun setSelections( slotId: String, affordanceIds: List<String>, - ) { - val key = "$KEY_PREFIX_SLOT$slotId" - val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER) - sharedPrefs.edit().putString(key, value).apply() - } + ) companion object { - private const val TAG = "KeyguardQuickAffordanceSelectionManager" - @VisibleForTesting 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 = "," + const val FILE_NAME = "quick_affordance_selections" } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt index d95a1a726bf5..e3f5e90b2300 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt @@ -18,45 +18,93 @@ package com.android.systemui.keyguard.data.repository import android.content.Context +import android.os.UserHandle import com.android.systemui.Dumpable import com.android.systemui.R +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation +import com.android.systemui.settings.UserTracker import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** Abstracts access to application state related to keyguard quick affordances. */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class KeyguardQuickAffordanceRepository @Inject constructor( @Application private val appContext: Context, @Application private val scope: CoroutineScope, - private val selectionManager: KeyguardQuickAffordanceSelectionManager, + private val localUserSelectionManager: KeyguardQuickAffordanceLocalUserSelectionManager, + private val remoteUserSelectionManager: KeyguardQuickAffordanceRemoteUserSelectionManager, + private val userTracker: UserTracker, legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer, private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>, dumpManager: DumpManager, + userHandle: UserHandle, ) { + private val userId: Flow<Int> = + ConflatedCallbackFlow.conflatedCallbackFlow { + val callback = + object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + trySendWithFailureLogging(newUser, TAG) + } + } + + userTracker.addCallback(callback) { it.run() } + trySendWithFailureLogging(userTracker.userId, TAG) + + awaitClose { userTracker.removeCallback(callback) } + } + + private val selectionManager: StateFlow<KeyguardQuickAffordanceSelectionManager> = + userId + .distinctUntilChanged() + .map { selectedUserId -> + if (userHandle.identifier == selectedUserId) { + localUserSelectionManager + } else { + remoteUserSelectionManager + } + } + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = localUserSelectionManager, + ) + /** * List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the * given ID. The configs are sorted in descending priority order. */ val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> = - selectionManager.selections - .map { selectionsBySlotId -> - selectionsBySlotId.mapValues { (_, selections) -> - configs.filter { selections.contains(it.key) } + selectionManager + .flatMapLatest { selectionManager -> + selectionManager.selections.map { selectionsBySlotId -> + selectionsBySlotId.mapValues { (_, selections) -> + configs.filter { selections.contains(it.key) } + } } } .stateIn( @@ -99,7 +147,7 @@ constructor( * slot with the given ID. The configs are sorted in descending priority order. */ fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> { - val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList()) + val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList()) return configs.filter { selections.contains(it.key) } } @@ -108,7 +156,7 @@ constructor( * are sorted in descending priority order. */ fun getSelections(): Map<String, List<String>> { - return selectionManager.getSelections() + return selectionManager.value.getSelections() } /** @@ -119,7 +167,7 @@ constructor( slotId: String, affordanceIds: List<String>, ) { - selectionManager.setSelections( + selectionManager.value.setSelections( slotId = slotId, affordanceIds = affordanceIds, ) @@ -188,6 +236,7 @@ constructor( } companion object { + private const val TAG = "KeyguardQuickAffordanceRepository" private const val SLOT_CONFIG_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 000000000000..0e865cee0b76 --- /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 2d94d760cb54..748c6e8b75b9 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.model.KeyguardSlotPickerRepresentati 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 @@ -190,8 +190,6 @@ constructor( /** Returns affordance IDs indexed by slot ID, for all known slots. */ suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> { - check(isUsingRepository) - val slots = repository.get().getSlotPickerRepresentations() val selections = repository.get().getSelections() val affordanceById = @@ -312,8 +310,6 @@ constructor( suspend fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> { - check(isUsingRepository) - return repository.get().getAffordancePickerRepresentations() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 3276b6dd9748..cbe512ff83ba 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.binder +import android.graphics.drawable.Animatable2 import android.util.Size import android.util.TypedValue import android.view.View @@ -27,12 +28,11 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.keyguard.LockIconViewController import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.animation.Interpolators +import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel @@ -73,7 +73,8 @@ object KeyguardBottomAreaViewBinder { fun onConfigurationChanged() /** - * Returns whether the keyguard bottom area should be constrained to the top of the lock icon + * Returns whether the keyguard bottom area should be constrained to the top of the lock + * icon */ fun shouldConstrainToTopOfLockIcon(): Boolean } @@ -217,7 +218,7 @@ object KeyguardBottomAreaViewBinder { } override fun shouldConstrainToTopOfLockIcon(): Boolean = - viewModel.shouldConstrainToTopOfLockIcon() + viewModel.shouldConstrainToTopOfLockIcon() } } @@ -248,6 +249,27 @@ object KeyguardBottomAreaViewBinder { IconViewBinder.bind(viewModel.icon, view) + (view.drawable as? Animatable2)?.let { animatable -> + (viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId -> + // Always start the animation (we do call stop() below, if we need to skip it). + animatable.start() + + if (view.tag != iconResourceId) { + // Here when we haven't run the animation on a previous update. + // + // Save the resource ID for next time, so we know not to re-animate the same + // animation again. + view.tag = iconResourceId + } else { + // Here when we've already done this animation on a previous update and want to + // skip directly to the final frame of the animation to avoid running it. + // + // By calling stop after start, we go to the final frame of the animation. + animatable.stop() + } + } + } + view.isActivated = viewModel.isActivated view.drawable.setTint( Utils.getColorAttrDefaultColor( diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt index c27bfa338df3..bb04b6b41a33 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt @@ -30,10 +30,11 @@ import kotlinx.coroutines.flow.Flow */ interface Diffable<T> { /** - * Finds the differences between [prevVal] and [this] and logs those diffs to [row]. + * Finds the differences between [prevVal] and this object and logs those diffs to [row]. * * Each implementer should determine which individual fields have changed between [prevVal] and - * [this], and only log the fields that have actually changed. This helps save buffer space. + * this object, and only log the fields that have actually changed. This helps save buffer + * space. * * For example, if: * - prevVal = Object(val1=100, val2=200, val3=300) @@ -42,6 +43,16 @@ interface Diffable<T> { * Then only the val3 change should be logged. */ fun logDiffs(prevVal: T, row: TableRowLogger) + + /** + * Logs all the relevant fields of this object to [row]. + * + * As opposed to [logDiffs], this method should log *all* fields. + * + * Implementation is optional. This method will only be used with [logDiffsForTable] in order to + * fully log the initial value of the flow. + */ + fun logFull(row: TableRowLogger) {} } /** @@ -57,8 +68,35 @@ fun <T : Diffable<T>> Flow<T>.logDiffsForTable( columnPrefix: String, initialValue: T, ): Flow<T> { - return this.pairwiseBy(initialValue) { prevVal, newVal -> + // Fully log the initial value to the table. + val getInitialValue = { + tableLogBuffer.logChange(columnPrefix) { row -> initialValue.logFull(row) } + initialValue + } + return this.pairwiseBy(getInitialValue) { prevVal: T, newVal: T -> tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal) newVal } } + +/** + * Each time the boolean flow is updated with a new value that's different from the previous value, + * logs the new value to the given [tableLogBuffer]. + */ +fun Flow<Boolean>.logDiffsForTable( + tableLogBuffer: TableLogBuffer, + columnPrefix: String, + columnName: String, + initialValue: Boolean, +): Flow<Boolean> { + val initialValueFun = { + tableLogBuffer.logChange(columnPrefix, columnName, initialValue) + initialValue + } + return this.pairwiseBy(initialValueFun) { prevVal, newVal: Boolean -> + if (prevVal != newVal) { + tableLogBuffer.logChange(columnPrefix, columnName, newVal) + } + newVal + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt index 429637a0ee4d..9d0b833d26cc 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt @@ -16,10 +16,7 @@ package com.android.systemui.log.table -import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable -import com.android.systemui.dump.DumpManager -import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.plugins.util.RingBuffer import com.android.systemui.util.time.SystemClock import java.io.PrintWriter @@ -83,7 +80,7 @@ class TableLogBuffer( maxSize: Int, private val name: String, private val systemClock: SystemClock, -) { +) : Dumpable { init { if (maxSize <= 0) { throw IllegalArgumentException("maxSize must be > 0") @@ -104,6 +101,9 @@ class TableLogBuffer( * @param columnPrefix a prefix that will be applied to every column name that gets logged. This * ensures that all the columns related to the same state object will be grouped together in the * table. + * + * @throws IllegalArgumentException if [columnPrefix] or column name contain "|". "|" is used as + * the separator token for parsing, so it can't be present in any part of the column name. */ @Synchronized fun <T : Diffable<T>> logDiffs(columnPrefix: String, prevVal: T, newVal: T) { @@ -113,6 +113,25 @@ class TableLogBuffer( newVal.logDiffs(prevVal, row) } + /** + * Logs change(s) to the buffer using [rowInitializer]. + * + * @param rowInitializer a function that will be called immediately to store relevant data on + * the row. + */ + @Synchronized + fun logChange(columnPrefix: String, rowInitializer: (TableRowLogger) -> Unit) { + val row = tempRow + row.timestamp = systemClock.currentTimeMillis() + row.columnPrefix = columnPrefix + rowInitializer(row) + } + + /** Logs a boolean change. */ + fun logChange(prefix: String, columnName: String, value: Boolean) { + logChange(systemClock.currentTimeMillis(), prefix, columnName, value) + } + // Keep these individual [logChange] methods private (don't let clients give us their own // timestamps.) @@ -135,32 +154,31 @@ class TableLogBuffer( @Synchronized private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange { + verifyValidName(prefix, columnName) val tableChange = buffer.advance() tableChange.reset(timestamp, prefix, columnName) return tableChange } - /** - * Registers this buffer as dumpables in [dumpManager]. Must be called for the table to be - * dumped. - * - * This will be automatically called in [TableLogBufferFactory.create]. - */ - fun registerDumpables(dumpManager: DumpManager) { - dumpManager.registerNormalDumpable("$name-changes", changeDumpable) - dumpManager.registerNormalDumpable("$name-table", tableDumpable) + private fun verifyValidName(prefix: String, columnName: String) { + if (prefix.contains(SEPARATOR)) { + throw IllegalArgumentException("columnPrefix cannot contain $SEPARATOR but was $prefix") + } + if (columnName.contains(SEPARATOR)) { + throw IllegalArgumentException( + "columnName cannot contain $SEPARATOR but was $columnName" + ) + } } - private val changeDumpable = Dumpable { pw, _ -> dumpChanges(pw) } - private val tableDumpable = Dumpable { pw, _ -> dumpTable(pw) } - - /** Dumps the list of [TableChange] objects. */ @Synchronized - @VisibleForTesting - fun dumpChanges(pw: PrintWriter) { + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println(HEADER_PREFIX + name) + pw.println("version $VERSION") for (i in 0 until buffer.size) { buffer[i].dump(pw) } + pw.println(FOOTER_PREFIX + name) } /** Dumps an individual [TableChange]. */ @@ -170,70 +188,14 @@ class TableLogBuffer( } val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp) pw.print(formattedTimestamp) - pw.print(" ") + pw.print(SEPARATOR) pw.print(this.getName()) - pw.print("=") + pw.print(SEPARATOR) pw.print(this.getVal()) pw.println() } /** - * Coalesces all the [TableChange] objects into a table of values of time and dumps the table. - */ - // TODO(b/259454430): Since this is an expensive process, it could cause the bug report dump to - // fail and/or not dump anything else. We should move this processing to ABT (Android Bug - // Tool), where we have unlimited time to process. - @Synchronized - @VisibleForTesting - fun dumpTable(pw: PrintWriter) { - val messages = buffer.iterator().asSequence().toList() - - if (messages.isEmpty()) { - return - } - - // Step 1: Create list of column headers - val headerSet = mutableSetOf<String>() - messages.forEach { headerSet.add(it.getName()) } - val headers: MutableList<String> = headerSet.toList().sorted().toMutableList() - headers.add(0, "timestamp") - - // Step 2: Create a list with the current values for each column. Will be updated with each - // change. - val currentRow: MutableList<String> = MutableList(headers.size) { DEFAULT_COLUMN_VALUE } - - // Step 3: For each message, make the correct update to [currentRow] and save it to [rows]. - val columnIndices: Map<String, Int> = - headers.mapIndexed { index, headerName -> headerName to index }.toMap() - val allRows = mutableListOf<List<String>>() - - messages.forEach { - if (!it.hasData()) { - return@forEach - } - - val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(it.timestamp) - if (formattedTimestamp != currentRow[0]) { - // The timestamp has updated, so save the previous row and continue to the next row - allRows.add(currentRow.toList()) - currentRow[0] = formattedTimestamp - } - val columnIndex = columnIndices[it.getName()]!! - currentRow[columnIndex] = it.getVal() - } - // Add the last row - allRows.add(currentRow.toList()) - - // Step 4: Dump the rows - DumpsysTableLogger( - name, - headers, - allRows, - ) - .printTableData(pw) - } - - /** * A private implementation of [TableRowLogger]. * * Used so that external clients can't modify [timestamp]. @@ -261,4 +223,8 @@ class TableLogBuffer( } val TABLE_LOG_DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) -private const val DEFAULT_COLUMN_VALUE = "UNKNOWN" + +private const val HEADER_PREFIX = "SystemUI StateChangeTableSection START: " +private const val FOOTER_PREFIX = "SystemUI StateChangeTableSection END: " +private const val SEPARATOR = "|" +private const val VERSION = "1" diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt index f1f906f46d2d..7a90a7470cd2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt @@ -34,7 +34,7 @@ constructor( maxSize: Int, ): TableLogBuffer { val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock) - tableBuffer.registerDumpables(dumpManager) + dumpManager.registerNormalDumpable(name, tableBuffer) return tableBuffer } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt index 4891297dbcf9..2d10b823f784 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt @@ -32,10 +32,12 @@ import com.android.systemui.Dumpable import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT +import com.android.systemui.settings.UserTracker import com.android.systemui.tuner.TunerService import com.android.systemui.util.Utils import com.android.systemui.util.time.SystemClock @@ -55,6 +57,8 @@ class MediaResumeListener constructor( private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, + private val userTracker: UserTracker, + @Main private val mainExecutor: Executor, @Background private val backgroundExecutor: Executor, private val tunerService: TunerService, private val mediaBrowserFactory: ResumeMediaBrowserFactory, @@ -77,18 +81,26 @@ constructor( private var currentUserId: Int = context.userId @VisibleForTesting - val userChangeReceiver = + val userUnlockReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_USER_UNLOCKED == intent.action) { - loadMediaResumptionControls() - } else if (Intent.ACTION_USER_SWITCHED == intent.action) { - currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) - loadSavedComponents() + val userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) + if (userId == currentUserId) { + loadMediaResumptionControls() + } } } } + private val userTrackerCallback = + object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + currentUserId = newUser + loadSavedComponents() + } + } + private val mediaBrowserCallback = object : ResumeMediaBrowser.Callback() { override fun addTrack( @@ -126,13 +138,13 @@ constructor( dumpManager.registerDumpable(TAG, this) val unlockFilter = IntentFilter() unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED) - unlockFilter.addAction(Intent.ACTION_USER_SWITCHED) broadcastDispatcher.registerReceiver( - userChangeReceiver, + userUnlockReceiver, unlockFilter, null, UserHandle.ALL ) + userTracker.addCallback(userTrackerCallback, mainExecutor) loadSavedComponents() } } 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 b252be1b4cdc..f7a9bc760caf 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 @@ constructor( 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 4bf3031c02b4..4feb9844cb8b 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 @@ constructor( */ 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/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index d132a95dedcb..5202562861ea 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -57,13 +57,12 @@ import static com.android.systemui.statusbar.phone.CentralSurfaces.dumpBarTransi import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay; import android.annotation.IdRes; +import android.annotation.NonNull; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.StatusBarManager; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.PixelFormat; @@ -120,7 +119,6 @@ import com.android.internal.view.AppearanceRegion; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; @@ -138,6 +136,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.recents.utilities.Utilities; @@ -208,7 +207,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final NotificationRemoteInputManager mNotificationRemoteInputManager; private final OverviewProxyService mOverviewProxyService; private final NavigationModeController mNavigationModeController; - private final BroadcastDispatcher mBroadcastDispatcher; + private final UserTracker mUserTracker; private final CommandQueue mCommandQueue; private final Optional<Pip> mPipOptional; private final Optional<Recents> mRecentsOptional; @@ -516,7 +515,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, SysUiState sysUiFlagsContainer, - BroadcastDispatcher broadcastDispatcher, + UserTracker userTracker, CommandQueue commandQueue, Optional<Pip> pipOptional, Optional<Recents> recentsOptional, @@ -559,7 +558,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mNotificationRemoteInputManager = notificationRemoteInputManager; mOverviewProxyService = overviewProxyService; mNavigationModeController = navigationModeController; - mBroadcastDispatcher = broadcastDispatcher; + mUserTracker = userTracker; mCommandQueue = commandQueue; mPipOptional = pipOptional; mRecentsOptional = recentsOptional; @@ -745,9 +744,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements prepareNavigationBarView(); checkNavBarModes(); - IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, - Handler.getMain(), UserHandle.ALL); + mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); notifyNavigationBarScreenOn(); @@ -798,7 +795,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mView.setUpdateActiveTouchRegionsCallback(null); getBarTransitions().destroy(); mOverviewProxyService.removeCallback(mOverviewProxyListener); - mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); + mUserTracker.removeCallback(mUserChangedCallback); mWakefulnessLifecycle.removeObserver(mWakefulnessObserver); if (mOrientationHandle != null) { resetSecondaryHandle(); @@ -1743,21 +1740,14 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } }; - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // TODO(193941146): Currently unregistering a receiver through BroadcastDispatcher is - // async, but we've already cleared the fields. Just return early in this case. - if (mView == null) { - return; - } - String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - // The accessibility settings may be different for the new user - updateAccessibilityStateFlags(); - } - } - }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + // The accessibility settings may be different for the new user + updateAccessibilityStateFlags(); + } + }; @VisibleForTesting int getNavigationIconHints() { diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 1da866efc08d..5a1ad96da7a9 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -39,6 +39,8 @@ import android.text.format.DateUtils; import android.util.Log; import android.util.Slog; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.fuelgauge.Estimate; import com.android.settingslib.utils.ThreadUtils; @@ -47,6 +49,7 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -80,6 +83,7 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { private final PowerManager mPowerManager; private final WarningsUI mWarnings; private final WakefulnessLifecycle mWakefulnessLifecycle; + private final UserTracker mUserTracker; private InattentiveSleepWarningView mOverlayView; private final Configuration mLastConfiguration = new Configuration(); private int mPlugType = 0; @@ -122,12 +126,21 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { } }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mWarnings.userSwitched(); + } + }; + @Inject public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher, CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, WarningsUI warningsUI, EnhancedEstimates enhancedEstimates, WakefulnessLifecycle wakefulnessLifecycle, - PowerManager powerManager) { + PowerManager powerManager, + UserTracker userTracker) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mCommandQueue = commandQueue; @@ -136,6 +149,7 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { mEnhancedEstimates = enhancedEstimates; mPowerManager = powerManager; mWakefulnessLifecycle = wakefulnessLifecycle; + mUserTracker = userTracker; } public void start() { @@ -154,6 +168,7 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { false, obs, UserHandle.USER_ALL); updateBatteryWarningLevels(); mReceiver.init(); + mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); mWakefulnessLifecycle.addObserver(mWakefulnessObserver); // Check to see if we need to let the user know that the phone previously shut down due @@ -250,7 +265,6 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_USER_SWITCHED); mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler); // Force get initial values. Relying on Sticky behavior until API for getting info. if (!mHasReceivedBattery) { @@ -332,8 +346,6 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks { plugged, bucket); }); - } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { - mWarnings.userSwitched(); } else { Slog.w(TAG, "unknown intent: " + intent); } 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 314252bf310b..4c9c99cc16f0 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.plugins.FalsingManager 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 @@ class UserSwitchDialogController @VisibleForTesting constructor( } } - 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/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 2ee5f05549cf..645b1256e5f1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -51,10 +51,13 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; + import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.util.leak.RotationUtils; @@ -76,6 +79,7 @@ public class ScreenPinningRequest implements View.OnClickListener, private final AccessibilityManager mAccessibilityService; private final WindowManager mWindowManager; private final BroadcastDispatcher mBroadcastDispatcher; + private final UserTracker mUserTracker; private RequestWindowView mRequestWindow; private int mNavBarMode; @@ -83,12 +87,21 @@ public class ScreenPinningRequest implements View.OnClickListener, /** ID of task to be pinned or locked. */ private int taskId; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + clearPrompt(); + } + }; + @Inject public ScreenPinningRequest( Context context, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, NavigationModeController navigationModeController, - BroadcastDispatcher broadcastDispatcher) { + BroadcastDispatcher broadcastDispatcher, + UserTracker userTracker) { mContext = context; mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; mAccessibilityService = (AccessibilityManager) @@ -97,6 +110,7 @@ public class ScreenPinningRequest implements View.OnClickListener, mContext.getSystemService(Context.WINDOW_SERVICE); mNavBarMode = navigationModeController.addListener(this); mBroadcastDispatcher = broadcastDispatcher; + mUserTracker = userTracker; } public void clearPrompt() { @@ -228,9 +242,9 @@ public class ScreenPinningRequest implements View.OnClickListener, } IntentFilter filter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED); - filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_SCREEN_OFF); mBroadcastDispatcher.registerReceiver(mReceiver, filter); + mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor()); } private void inflateView(int rotation) { @@ -358,6 +372,7 @@ public class ScreenPinningRequest implements View.OnClickListener, @Override public void onDetachedFromWindow() { mBroadcastDispatcher.unregisterReceiver(mReceiver); + mUserTracker.removeCallback(mUserChangedCallback); } protected void onConfigurationChanged() { @@ -388,8 +403,7 @@ public class ScreenPinningRequest implements View.OnClickListener, public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) { post(mUpdateLayoutRunnable); - } else if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED) - || intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { + } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { clearPrompt(); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index ce4e0ecee914..b8684ee30b9a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -33,13 +33,16 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.CallbackController; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -55,8 +58,10 @@ public class RecordingController private boolean mIsRecording; private PendingIntent mStopIntent; private CountDownTimer mCountDownTimer = null; - private BroadcastDispatcher mBroadcastDispatcher; - private UserContextProvider mUserContextProvider; + private final Executor mMainExecutor; + private final BroadcastDispatcher mBroadcastDispatcher; + private final UserContextProvider mUserContextProvider; + private final UserTracker mUserTracker; protected static final String INTENT_UPDATE_STATE = "com.android.systemui.screenrecord.UPDATE_STATE"; @@ -66,12 +71,13 @@ public class RecordingController new CopyOnWriteArrayList<>(); @VisibleForTesting - protected final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - stopRecording(); - } - }; + final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + stopRecording(); + } + }; @VisibleForTesting protected final BroadcastReceiver mStateChangeReceiver = new BroadcastReceiver() { @@ -92,10 +98,14 @@ public class RecordingController * Create a new RecordingController */ @Inject - public RecordingController(BroadcastDispatcher broadcastDispatcher, - UserContextProvider userContextProvider) { + public RecordingController(@Main Executor mainExecutor, + BroadcastDispatcher broadcastDispatcher, + UserContextProvider userContextProvider, + UserTracker userTracker) { + mMainExecutor = mainExecutor; mBroadcastDispatcher = broadcastDispatcher; mUserContextProvider = userContextProvider; + mUserTracker = userTracker; } /** Create a dialog to show screen recording options to the user. */ @@ -139,9 +149,7 @@ public class RecordingController } try { startIntent.send(); - IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, userFilter, null, - UserHandle.ALL); + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); IntentFilter stateFilter = new IntentFilter(INTENT_UPDATE_STATE); mBroadcastDispatcher.registerReceiver(mStateChangeReceiver, stateFilter, null, @@ -211,7 +219,7 @@ public class RecordingController public synchronized void updateState(boolean isRecording) { if (!isRecording && mIsRecording) { // Unregister receivers if we have stopped recording - mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver); + mUserTracker.removeCallback(mUserChangedCallback); mBroadcastDispatcher.unregisterReceiver(mStateChangeReceiver); } mIsRecording = isRecording; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 10d31ea2d277..a6447a5bf500 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.WindowManager; 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 @@ public class ScreenshotController { 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 @@ public class ScreenshotController { } } + private void respondToBack() { + dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); + } + /** * Update resources on configuration change. Reinflate for theme/color changes. */ @@ -476,6 +489,26 @@ public class ScreenshotController { // 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 @@ public class ScreenshotController { if (DEBUG_INPUT) { Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK"); } - dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); + respondToBack(); return true; } return false; @@ -546,9 +579,16 @@ public class ScreenshotController { private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) { - withWindowAttached(() -> + withWindowAttached(() -> { + if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY) + && mUserManager.isManagedProfile(owner.getIdentifier())) { + mScreenshotView.announceForAccessibility(mContext.getResources().getString( + R.string.screenshot_saving_work_profile_title)); + } else { mScreenshotView.announceForAccessibility( - mContext.getResources().getString(R.string.screenshot_saving_title))); + mContext.getResources().getString(R.string.screenshot_saving_title)); + } + }); mScreenshotView.reset(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 7641554ede3d..fae938d542f1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -825,12 +825,23 @@ public class ScreenshotView extends FrameLayout implements } }); if (mQuickShareChip != null) { - mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent, - () -> { - mUiEventLogger.log( - ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0, mPackageName); - animateDismissal(); - }); + if (imageData.quickShareAction != null) { + mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent, + () -> { + mUiEventLogger.log( + ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0, + mPackageName); + animateDismissal(); + }); + } else { + // hide chip and unset pending interaction if necessary, since we don't actually + // have a useable quick share intent + Log.wtf(TAG, "Showed quick share chip, but quick share intent was null"); + if (mPendingInteraction == PendingInteraction.QUICK_SHARE) { + mPendingInteraction = null; + } + mQuickShareChip.setVisibility(GONE); + } } if (mPendingInteraction != null) { diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt index d450afa59c7d..bfba6dfddfac 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 java.io.File 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 @@ class UserFileManagerImpl @Inject constructor( ) : 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 @@ class UserFileManagerImpl @Inject constructor( 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 @@ class UserFileManagerImpl @Inject constructor( } } } - - /** - * 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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 7fbdeca3963a..60376f4deec8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -393,6 +393,9 @@ public final class NotificationPanelViewController implements Dumpable { private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private int mQsTrackingPointer; private VelocityTracker mQsVelocityTracker; + private TrackingStartedListener mTrackingStartedListener; + private OpenCloseListener mOpenCloseListener; + private GestureRecorder mGestureRecorder; private boolean mQsTracking; /** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */ private boolean mConflictingQsExpansionGesture; @@ -1362,6 +1365,14 @@ public final class NotificationPanelViewController implements Dumpable { mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea); } + void setOpenCloseListener(OpenCloseListener openCloseListener) { + mOpenCloseListener = openCloseListener; + } + + void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) { + mTrackingStartedListener = trackingStartedListener; + } + private void updateGestureExclusionRect() { Rect exclusionRect = calculateGestureExclusionRect(); mView.setSystemGestureExclusionRects(exclusionRect.isEmpty() ? Collections.emptyList() @@ -1936,9 +1947,9 @@ public final class NotificationPanelViewController implements Dumpable { } private void fling(float vel) { - GestureRecorder gr = mCentralSurfaces.getGestureRecorder(); - if (gr != null) { - gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); + if (mGestureRecorder != null) { + mGestureRecorder.tag("fling " + ((vel > 0) ? "open" : "closed"), + "notifications,v=" + vel); } fling(vel, true, 1.0f /* collapseSpeedUpFactor */, false); } @@ -2072,6 +2083,14 @@ public final class NotificationPanelViewController implements Dumpable { mInitialTouchX = x; initVelocityTracker(); trackMovement(event); + float qsExpansionFraction = computeQsExpansionFraction(); + // Intercept the touch if QS is between fully collapsed and fully expanded state + if (!mSplitShadeEnabled + && qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) { + mShadeLog.logMotionEvent(event, + "onQsIntercept: down action, QS partially expanded/collapsed"); + return true; + } if (mKeyguardShowing && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { // Dragging down on the lockscreen statusbar should prohibit other interactions @@ -2324,6 +2343,14 @@ public final class NotificationPanelViewController implements Dumpable { if (!isFullyCollapsed()) { handleQsDown(event); } + // defer touches on QQS to shade while shade is collapsing. Added margin for error + // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS. + if (!mSplitShadeEnabled + && computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) { + mShadeLog.logMotionEvent(event, + "handleQsTouch: QQS touched while shade collapsing"); + mQsTracking = false; + } if (!mQsExpandImmediate && mQsTracking) { onQsTouch(event); if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) { @@ -2564,7 +2591,6 @@ public final class NotificationPanelViewController implements Dumpable { // Reset scroll position and apply that position to the expanded height. float height = mQsExpansionHeight; setQsExpansionHeight(height); - updateExpandedHeightToMaxHeight(); mNotificationStackScrollLayoutController.checkSnoozeLeavebehind(); // When expanding QS, let's authenticate the user if possible, @@ -3711,7 +3737,7 @@ public final class NotificationPanelViewController implements Dumpable { mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen()); endClosing(); mTracking = true; - mCentralSurfaces.onTrackingStarted(); + mTrackingStartedListener.onTrackingStarted(); notifyExpandingStarted(); updatePanelExpansionAndVisibility(); mScrimController.onTrackingStarted(); @@ -3945,7 +3971,7 @@ public final class NotificationPanelViewController implements Dumpable { } private void onClosingFinished() { - mCentralSurfaces.onClosingFinished(); + mOpenCloseListener.onClosingFinished(); setClosingWithAlphaFadeout(false); mMediaHierarchyManager.closeGuts(); } @@ -4504,11 +4530,13 @@ public final class NotificationPanelViewController implements Dumpable { */ public void initDependencies( CentralSurfaces centralSurfaces, + GestureRecorder recorder, Runnable hideExpandedRunnable, NotificationShelfController notificationShelfController) { // TODO(b/254859580): this can be injected. mCentralSurfaces = centralSurfaces; + mGestureRecorder = recorder; mHideExpandedRunnable = hideExpandedRunnable; mNotificationStackScrollLayoutController.setShelfController(notificationShelfController); mNotificationShelfController = notificationShelfController; @@ -4590,20 +4618,20 @@ public final class NotificationPanelViewController implements Dumpable { return false; } - // If the view that would receive the touch is disabled, just have status bar - // eat the gesture. - if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) { - Log.v(TAG, - String.format( - "onTouchForwardedFromStatusBar: " - + "panel view disabled, eating touch at (%d,%d)", - (int) event.getX(), - (int) event.getY() - ) - ); - return true; + if (event.getAction() == MotionEvent.ACTION_DOWN) { + // If the view that would receive the touch is disabled, just have status + // bar eat the gesture. + if (!mView.isEnabled()) { + mShadeLog.logMotionEvent(event, + "onTouchForwardedFromStatusBar: panel view disabled"); + return true; + } + if (isFullyCollapsed() && event.getY() < 1f) { + // b/235889526 Eat events on the top edge of the phone when collapsed + mShadeLog.logMotionEvent(event, "top edge touch ignored"); + return true; + } } - return mView.dispatchTouchEvent(event); } }; @@ -5757,7 +5785,7 @@ public final class NotificationPanelViewController implements Dumpable { if (mSplitShadeEnabled && !isOnKeyguard()) { setQsExpandImmediate(true); } - mCentralSurfaces.makeExpandedVisible(false); + mOpenCloseListener.onOpenStarted(); } if (state == STATE_CLOSED) { setQsExpandImmediate(false); @@ -6240,5 +6268,18 @@ public final class NotificationPanelViewController implements Dumpable { return super.performAccessibilityAction(host, action, args); } } + + /** Listens for when touch tracking begins. */ + interface TrackingStartedListener { + void onTrackingStarted(); + } + + /** Listens for when shade begins opening of finishes closing. */ + interface OpenCloseListener { + /** Called when the shade finishes closing. */ + void onClosingFinished(); + /** Called when the shade starts opening. */ + void onOpenStarted(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java index aa610bdcc90e..a41a15d4e2a2 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 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; */ public interface ShadeController { - /** - * Make our window larger and the panel expanded - */ - void instantExpandNotificationsPanel(); + /** Make our window larger and the shade expanded */ + void instantExpandShade(); + + /** Collapse the shade instantly with no animation. */ + void instantCollapseShade(); + + /** 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)}. */ - void animateCollapsePanels(); + /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */ + void animateCollapseShadeForced(); - /** See {@link #animateCollapsePanels(int, boolean)}. */ - void animateCollapsePanels(int flags); + /** 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 @@ public interface ShadeController { */ 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,51 @@ public interface ShadeController { * * @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); + + /** Called when the shade finishes collapsing. */ + void onClosingFinished(); + + /** 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 d783293b95d4..638e74883f23 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.CommandQueue; 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 @@ import dagger.Lazy; /** 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 int mDisplayId; + private final CommandQueue mCommandQueue; + private final KeyguardStateController mKeyguardStateController; + private final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarStateController mStatusBarStateController; - protected final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private final int mDisplayId; - protected final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; + 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 @@ public class ShadeControllerImpl implements ShadeController { 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 @@ public class ShadeControllerImpl implements ShadeController { @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 @@ public class ShadeControllerImpl implements ShadeController { } @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,154 @@ public class ShadeControllerImpl implements ShadeController { } @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 onClosingFinished() { + runPostCollapseRunnables(); + if (!mPresenter.isPresenterFullyCollapsed()) { + // if we set it not to be focusable when collapsing, we have to undo it when we aborted + // the closing + mNotificationShadeWindowController.setNotificationShadeFocusable(true); + } } - protected NotificationShadeWindowView getNotificationShadeWindowView() { - return getCentralSurfaces().getNotificationShadeWindowView(); + @Override + public void instantCollapseShade() { + mNotificationPanelViewController.instantCollapse(); + runPostCollapseRunnables(); + } + + @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; + mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables); + mNotificationPanelViewController.setOpenCloseListener( + new NotificationPanelViewController.OpenCloseListener() { + @Override + public void onClosingFinished() { + ShadeControllerImpl.this.onClosingFinished(); + } + + @Override + public void onOpenStarted() { + makeExpandedVisible(false); + } + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index bc456d5d4613..2334a4c27af8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -15,6 +15,7 @@ import android.os.Trace import android.util.AttributeSet import android.util.MathUtils.lerp import android.view.View +import android.view.animation.PathInterpolator import com.android.systemui.animation.Interpolators import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold import com.android.systemui.util.getColorWithAlpha @@ -88,10 +89,12 @@ object LiftReveal : LightRevealEffect { class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect { - private val INTERPOLATOR = Interpolators.FAST_OUT_SLOW_IN_REVERSE + // Interpolator that reveals >80% of the content at 0.5 progress, makes revealing faster + private val interpolator = PathInterpolator(/* controlX1= */ 0.4f, /* controlY1= */ 0f, + /* controlX2= */ 0.2f, /* controlY2= */ 1f) override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) { - val interpolatedAmount = INTERPOLATOR.getInterpolation(amount) + val interpolatedAmount = interpolator.getInterpolation(amount) scrim.interpolatedRevealAmount = interpolatedAmount diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index cdefae6b87f9..f4cd985adbdb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -30,6 +30,7 @@ import android.content.IntentSender; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -37,6 +38,7 @@ import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.NotificationVisibility; @@ -127,21 +129,6 @@ public class NotificationLockscreenUserManagerImpl implements public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { - case Intent.ACTION_USER_SWITCHED: - mCurrentUserId = intent.getIntExtra( - Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL); - updateCurrentProfilesCache(); - - Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); - - updateLockscreenNotificationSetting(); - updatePublicMode(); - mPresenter.onUserSwitched(mCurrentUserId); - - for (UserChangedListener listener : mListeners) { - listener.onUserChanged(mCurrentUserId); - } - break; case Intent.ACTION_USER_REMOVED: int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (removedUserId != -1) { @@ -181,6 +168,25 @@ public class NotificationLockscreenUserManagerImpl implements } }; + protected final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + mCurrentUserId = newUser; + updateCurrentProfilesCache(); + + Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); + + updateLockscreenNotificationSetting(); + updatePublicMode(); + mPresenter.onUserSwitched(mCurrentUserId); + + for (UserChangedListener listener : mListeners) { + listener.onUserChanged(mCurrentUserId); + } + } + }; + protected final Context mContext; private final Handler mMainHandler; protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>(); @@ -284,7 +290,6 @@ public class NotificationLockscreenUserManagerImpl implements null /* handler */, UserHandle.ALL); IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_UNLOCKED); @@ -298,6 +303,8 @@ public class NotificationLockscreenUserManagerImpl implements mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null, Context.RECEIVER_EXPORTED_UNAUDITED); + mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler)); + mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late updateCurrentProfilesCache(); 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 143c6979c590..bd5b8f0b17a5 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 @@ open class PrivacyDotViewController @Inject constructor( 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 @@ open class PrivacyDotViewController @Inject constructor( return uiExecutor } - fun setShowingListener(l: ShowingListener?) { - showingListener = l - } - @UiThread fun setNewRotation(rot: Int) { dlog("updateRotation: $rot") @@ -219,7 +219,7 @@ open class PrivacyDotViewController @Inject constructor( // 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 @@ open class PrivacyDotViewController @Inject constructor( } @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/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt index 7eb890677206..39daa13ae168 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt @@ -44,4 +44,8 @@ class NotifPipelineFlags @Inject constructor( val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy { featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD) } + + val isNoHunForOldWhenEnabled: Boolean by lazy { + featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index d97b712df030..3e2dd053d938 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -38,6 +38,7 @@ import java.io.PrintWriter import javax.inject.Inject import kotlin.math.min + @SysUISingleton class NotificationWakeUpCoordinator @Inject constructor( dumpManager: DumpManager, @@ -45,7 +46,8 @@ class NotificationWakeUpCoordinator @Inject constructor( private val statusBarStateController: StatusBarStateController, private val bypassController: KeyguardBypassController, private val dozeParameters: DozeParameters, - private val screenOffAnimationController: ScreenOffAnimationController + private val screenOffAnimationController: ScreenOffAnimationController, + private val logger: NotificationWakeUpCoordinatorLogger, ) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener, Dumpable { @@ -242,6 +244,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } override fun onDozeAmountChanged(linear: Float, eased: Float) { + logger.logOnDozeAmountChanged(linear, eased) if (overrideDozeAmountIfAnimatingScreenOff(linear)) { return } @@ -273,6 +276,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } override fun onStateChanged(newState: Int) { + logger.logOnStateChanged(newState) if (state == StatusBarState.SHADE && newState == StatusBarState.SHADE) { // The SHADE -> SHADE transition is only possible as part of cancelling the screen-off // animation (e.g. by fingerprint unlock). This is done because the system is in an @@ -320,8 +324,12 @@ class NotificationWakeUpCoordinator @Inject constructor( private fun overrideDozeAmountIfBypass(): Boolean { if (bypassController.bypassEnabled) { if (statusBarStateController.state == StatusBarState.KEYGUARD) { + logger.logSetDozeAmount("1.0", "1.0", + "Override: bypass (keyguard)", StatusBarState.KEYGUARD) setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)") } else { + logger.logSetDozeAmount("0.0", "0.0", + "Override: bypass (shade)", statusBarStateController.state) setDozeAmount(0f, 0f, source = "Override: bypass (shade)") } return true diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt new file mode 100644 index 000000000000..b40ce25c58d2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt @@ -0,0 +1,55 @@ +/* + * 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.statusbar.notification + +import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG +import javax.inject.Inject + +class NotificationWakeUpCoordinatorLogger +@Inject +constructor(@NotificationLog private val buffer: LogBuffer) { + fun logSetDozeAmount(linear: String, eased: String, source: String, state: Int) { + buffer.log( + TAG, + DEBUG, + { + str1 = linear + str2 = eased + str3 = source + int1 = state + }, + { "setDozeAmount: linear: $str1, eased: $str2, source: $str3, state: $int1" } + ) + } + + fun logOnDozeAmountChanged(linear: Float, eased: Float) { + buffer.log( + TAG, + DEBUG, + { + double1 = linear.toDouble() + str2 = eased.toString() + }, + { "onDozeAmountChanged($double1, $str2)" } + ) + } + + fun logOnStateChanged(newState: Int) { + buffer.log(TAG, DEBUG, { int1 = newState }, { "onStateChanged($int1)" }) + } +} + +private const val TAG = "NotificationWakeUpCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index e6dbcee10f60..7513aa7fa2a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -2,22 +2,20 @@ package com.android.systemui.statusbar.notification.interruption import android.app.Notification import android.app.Notification.VISIBILITY_SECRET -import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent -import android.content.IntentFilter import android.database.ContentObserver import android.net.Uri import android.os.Handler +import android.os.HandlerExecutor import android.os.UserHandle import android.provider.Settings import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.CoreStartable -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -78,7 +76,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val highPriorityProvider: HighPriorityProvider, private val statusBarStateController: SysuiStatusBarStateController, - private val broadcastDispatcher: BroadcastDispatcher, + private val userTracker: UserTracker, private val secureSettings: SecureSettings, private val globalSettings: GlobalSettings ) : CoreStartable, KeyguardNotificationVisibilityProvider { @@ -87,6 +85,15 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val onStateChangedListeners = ListenerSet<Consumer<String>>() private var hideSilentNotificationsOnLockscreen: Boolean = false + private val userTrackerCallback = object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + if (isLockedOrLocking) { + // maybe public mode changed + notifyStateChanged("onUserSwitched") + } + } + } + override fun start() { readShowSilentNotificationSetting() keyguardStateController.addCallback(object : KeyguardStateController.Callback { @@ -143,14 +150,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( notifyStateChanged("onStatusBarUpcomingStateChanged") } }) - broadcastDispatcher.registerReceiver(object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (isLockedOrLocking) { - // maybe public mode changed - notifyStateChanged(intent.action!!) - } - } - }, IntentFilter(Intent.ACTION_USER_SWITCHED)) + userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler)) } override fun addOnStateChangedListener(listener: Consumer<String>) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 073b6b041b81..13b3aca2a88f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -106,6 +106,36 @@ class NotificationInterruptLogger @Inject constructor( }) } + fun logNoHeadsUpOldWhen( + entry: NotificationEntry, + notifWhen: Long, + notifAge: Long + ) { + buffer.log(TAG, DEBUG, { + str1 = entry.logKey + long1 = notifWhen + long2 = notifAge + }, { + "No heads up: old when $long1 (age=$long2 ms): $str1" + }) + } + + fun logMaybeHeadsUpDespiteOldWhen( + entry: NotificationEntry, + notifWhen: Long, + notifAge: Long, + reason: String + ) { + buffer.log(TAG, DEBUG, { + str1 = entry.logKey + str2 = reason + long1 = notifWhen + long2 = notifAge + }, { + "Maybe heads up: old when $long1 (age=$long2 ms) but $str2: $str1" + }) + } + fun logNoHeadsUpSuppressedBy( entry: NotificationEntry, suppressor: NotificationInterruptSuppressor diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index c4f5a3a30608..ec5bd6894283 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -20,6 +20,7 @@ import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD; import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR; +import android.app.Notification; import android.app.NotificationManager; import android.content.ContentResolver; import android.database.ContentObserver; @@ -82,7 +83,10 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235), @UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard") - FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236); + FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236), + + @UiEvent(doc = "HUN suppressed for old when") + HUN_SUPPRESSED_OLD_WHEN(1237); private final int mId; @@ -346,6 +350,10 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } + if (shouldSuppressHeadsUpWhenAwakeForOldWhen(entry, log)) { + return false; + } + for (int i = 0; i < mSuppressors.size(); i++) { if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) { if (log) mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i)); @@ -470,4 +478,51 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter private boolean isSnoozedPackage(StatusBarNotification sbn) { return mHeadsUpManager.isSnoozed(sbn.getPackageName()); } + + private boolean shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log) { + if (!mFlags.isNoHunForOldWhenEnabled()) { + return false; + } + + final Notification notification = entry.getSbn().getNotification(); + if (notification == null) { + return false; + } + + final long when = notification.when; + final long now = System.currentTimeMillis(); + final long age = now - when; + + if (age < MAX_HUN_WHEN_AGE_MS) { + return false; + } + + if (when <= 0) { + // Some notifications (including many system notifications) are posted with the "when" + // field set to 0. Nothing in the Javadocs for Notification mentions a special meaning + // for a "when" of 0, but Android didn't even exist at the dawn of the Unix epoch. + // Therefore, assume that these notifications effectively don't have a "when" value, + // and don't suppress HUNs. + if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "when <= 0"); + return false; + } + + if (notification.fullScreenIntent != null) { + if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "full-screen intent"); + return false; + } + + if (notification.isForegroundService()) { + if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "foreground service"); + return false; + } + + if (log) mLogger.logNoHeadsUpOldWhen(entry, when, age); + final int uid = entry.getSbn().getUid(); + final String packageName = entry.getSbn().getPackageName(); + mUiEventLogger.log(NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN, uid, packageName); + return true; + } + + public static final long MAX_HUN_WHEN_AGE_MS = 24 * 60 * 60 * 1000; } 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 64f87cabaf74..b56bae12be6c 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.CommandQueue; 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 0ce9656a21b5..f21db0bde59a 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 @@ public class NotificationConversationInfo extends LinearLayout implements // 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 073bd4bf302b..b519aefcd4c9 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mShadeNeedsToClose = false; postDelayed( () -> { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE); }, DELAY_BEFORE_SHADE_CLOSE /* delayMillis */); } @@ -5139,6 +5128,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 ae854e2df91a..25f99c69d454 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.ExpandableNotificationRow 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 @@ constructor( @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 @@ constructor( 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 @@ constructor( 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 @@ constructor( private fun computeHeightPerNotificationLimit( stack: NotificationStackScrollLayout, - shelfHeight: Float + shelfHeight: Float, + computeHeight: Boolean ): Sequence<StackHeight> = sequence { log { "computeHeightPerNotificationLimit" } @@ -213,9 +222,14 @@ constructor( 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 @@ constructor( 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 01ca66742287..e068f87f03b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -51,7 +51,6 @@ import com.android.systemui.qs.QSPanelController; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; -import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.NotificationPresenter; @@ -193,8 +192,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void animateExpandSettingsPanel(@Nullable String subpanel); - void animateCollapsePanels(int flags, boolean force); - void collapsePanelOnMainThread(); void togglePanel(); @@ -282,8 +279,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void postAnimateOpenPanels(); - boolean isExpandedVisible(); - boolean isPanelExpanded(); void onInputFocusTransfer(boolean start, boolean cancel, float velocity); @@ -292,8 +287,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void onTouchEvent(MotionEvent event); - GestureRecorder getGestureRecorder(); - BiometricUnlockController getBiometricUnlockController(); void showWirelessChargingAnimation(int batteryLevel); @@ -408,10 +401,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn LightRevealScrim getLightRevealScrim(); - void onTrackingStarted(); - - void onClosingFinished(); - // TODO: Figure out way to remove these. NavigationBarView getNavigationBarView(); @@ -495,12 +484,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void updateNotificationPanelTouchState(); - 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 f3482f490d92..6b72e9696f83 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 class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba 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 class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba 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 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } } @@ -293,7 +293,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { mCentralSurfaces.updateQsExpansionEnabled(); if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } } @@ -550,7 +550,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba @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 32ea8d6fa695..16112578de66 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.WallpaperInfo; 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 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { /** */ @Override - public void animateCollapsePanels(int flags, boolean force) { - mCommandQueueCallbacks.animateCollapsePanels(flags, force); - } - - /** */ - @Override public void togglePanel() { mCommandQueueCallbacks.togglePanel(); } @@ -493,8 +486,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private View mReportRejectedTouch; - private boolean mExpandedVisible; - private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; private final ShadeExpansionStateManager mShadeExpansionStateManager; @@ -893,6 +884,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { updateDisplaySize(); mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId); + initShadeVisibilityListener(); + // start old BaseStatusBar.start(). mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( @@ -1083,6 +1076,25 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationPanelViewController.initDependencies( this, - this::makeExpandedInvisible, + mGestureRec, + mShadeController::makeExpandedInvisible, mNotificationShelfController); BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop); @@ -1431,6 +1444,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController); mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); + mShadeController.setNotificationPresenter(mPresenter); mNotificationsController.initialize( this, mPresenter, @@ -1480,11 +1494,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 +1516,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationShadeWindowViewController.setupExpandedStatusBar(); mNotificationPanelViewController = mCentralSurfacesComponent.getNotificationPanelViewController(); + mShadeController.setNotificationPanelViewController(mNotificationPanelViewController); + mShadeController.setNotificationShadeWindowViewController( + mNotificationShadeWindowViewController); mCentralSurfacesComponent.getLockIconViewController().init(); mStackScrollerController = mCentralSurfacesComponent.getNotificationStackScrollLayoutController(); @@ -1825,9 +1838,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { public void onLaunchAnimationCancelled(boolean isLaunchForActivity) { if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing() && isLaunchForActivity) { - onClosingFinished(); + mShadeController.onClosingFinished(); } else { - mShadeController.collapsePanel(true /* animate */); + mShadeController.collapseShade(true /* animate */); } } @@ -1835,10 +1848,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onLaunchAnimationEnd(boolean launchIsFullScreen) { if (!mPresenter.isCollapsing()) { - onClosingFinished(); + mShadeController.onClosingFinished(); } if (launchIsFullScreen) { - instantCollapseNotificationPanel(); + mShadeController.instantCollapseShade(); } } @@ -1928,33 +1941,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @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 +1956,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public boolean isExpandedVisible() { - return mExpandedVisible; - } - - @Override public boolean isPanelExpanded() { return mPanelExpanded; } @@ -1996,46 +1984,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - 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,16 +2027,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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()); } } @Override - public GestureRecorder getGestureRecorder() { - return mGestureRec; - } - - @Override public BiometricUnlockController getBiometricUnlockController() { return mBiometricUnlockController; } @@ -2221,7 +2172,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 +2487,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } 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 +2530,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 +2645,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 +2664,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } else { mNotificationLogger.stopNotificationLogging(); - handleVisibleToUserChangedImpl(visibleToUser); + onInvisibleToUser(); if (mIsBackCallbackRegistered) { ViewRootImpl viewRootImpl = getViewRootImpl(); @@ -2736,41 +2684,38 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - // 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 +2893,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void updatePanelExpansionForKeyguard() { if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode() != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) { - mShadeController.instantExpandNotificationsPanel(); + mShadeController.instantExpandShade(); } } @@ -3067,7 +3012,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // 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 +3170,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public boolean onMenuPressed() { if (shouldUnlockOnMenuPressed()) { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); + mShadeController.animateCollapseShadeForced(); return true; } return false; @@ -3271,7 +3215,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED && !isBouncerShowingOverDream()) { if (mNotificationPanelViewController.canPanelBeCollapsed()) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } return true; } @@ -3281,8 +3225,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @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 +3265,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - @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 +3272,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void collapsePanelOnMainThread() { if (Looper.getMainLooper().isCurrentThread()) { - mShadeController.collapsePanel(); + mShadeController.collapseShade(); } else { - mContext.getMainExecutor().execute(mShadeController::collapsePanel); + mContext.getMainExecutor().execute(mShadeController::collapseShade); } } @@ -3378,21 +3315,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return mLightRevealScrim; } - @Override - public void onTrackingStarted() { - mShadeController.runPostCollapseRunnables(); - } - - @Override - public void onClosingFinished() { - mShadeController.runPostCollapseRunnables(); - if (!mPresenter.isPresenterFullyCollapsed()) { - // if we set it not to be focusable when collapsing, we have to undo it when we aborted - // the closing - mNotificationShadeWindowController.setNotificationShadeFocusable(true); - } - } - // TODO: Figure out way to remove these. @Override public NavigationBarView getNavigationBarView() { @@ -3477,7 +3399,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationShadeWindowViewController.cancelCurrentTouch(); } if (mPanelExpanded && mState == StatusBarState.SHADE) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } } @@ -3540,7 +3462,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // 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 +3491,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // 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(); } }); @@ -3904,8 +3826,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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 +3888,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { action.run(); }).start(); - return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard; + return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard; } @Override @@ -4062,8 +3983,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { 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/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java index 26e6db664e07..4beb87ddae2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java @@ -15,23 +15,21 @@ package com.android.systemui.statusbar.phone; import android.app.StatusBarManager; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.NonNull; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -43,9 +41,9 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { private final List<Callback> mCallbacks = new ArrayList<>(); private final Context mContext; + private final Executor mMainExecutor; private final UserManager mUserManager; private final UserTracker mUserTracker; - private final BroadcastDispatcher mBroadcastDispatcher; private final LinkedList<UserInfo> mProfiles; private boolean mListening; private int mCurrentUser; @@ -53,12 +51,12 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { /** */ @Inject - public ManagedProfileControllerImpl(Context context, UserTracker userTracker, - BroadcastDispatcher broadcastDispatcher) { + public ManagedProfileControllerImpl(Context context, @Main Executor mainExecutor, + UserTracker userTracker) { mContext = context; + mMainExecutor = mainExecutor; mUserManager = UserManager.get(mContext); mUserTracker = userTracker; - mBroadcastDispatcher = broadcastDispatcher; mProfiles = new LinkedList<UserInfo>(); } @@ -130,30 +128,34 @@ public class ManagedProfileControllerImpl implements ManagedProfileController { } private void setListening(boolean listening) { + if (mListening == listening) { + return; + } mListening = listening; if (listening) { reloadManagedProfiles(); - - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - mBroadcastDispatcher.registerReceiver( - mReceiver, filter, null /* handler */, UserHandle.ALL); + mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); } else { - mBroadcastDispatcher.unregisterReceiver(mReceiver); + mUserTracker.removeCallback(mUserChangedCallback); } } - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - reloadManagedProfiles(); - for (Callback callback : mCallbacks) { - callback.onManagedProfileChanged(); - } - } - }; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + reloadManagedProfiles(); + for (Callback callback : mCallbacks) { + callback.onManagedProfileChanged(); + } + } + + @Override + public void onProfilesChanged(@NonNull List<UserInfo> profiles) { + reloadManagedProfiles(); + for (Callback callback : mCallbacks) { + callback.onManagedProfileChanged(); + } + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index fb0d3e4406bf..d500f999b5e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -352,6 +352,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump .getBoolean(R.bool.notification_scrim_transparent); updateScrims(); mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback); + + // prepare() sets proper initial values for most states + for (ScrimState state : ScrimState.values()) { + state.prepare(state); + } } /** @@ -641,10 +646,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private void setTransitionToFullShade(boolean transitioning) { if (transitioning != mTransitioningToFullShade) { mTransitioningToFullShade = transitioning; - if (transitioning) { - // Let's make sure the shade locked is ready - ScrimState.SHADE_LOCKED.prepare(mState); - } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 52430d33cbf0..0e9d3ce33d5b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -146,18 +146,12 @@ public enum ScrimState { mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha; mNotifAlpha = 1f; mFrontAlpha = 0f; - mBehindTint = Color.BLACK; + mBehindTint = mClipQsScrim ? Color.TRANSPARENT : Color.BLACK; if (mClipQsScrim) { updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); } } - - // to make sure correct color is returned before "prepare" is called - @Override - public int getBehindTint() { - return Color.BLACK; - } }, /** 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 44ad60473925..f9d316b2ff20 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // 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 b6ae4a088880..05bf8604c2c0 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 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte 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 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte private void expandBubbleStack(NotificationEntry entry) { mBubblesManagerOptional.get().expandStackAndSelectBubble(entry); - mShadeController.collapsePanel(); + mShadeController.collapseShade(); } private void startNotificationIntent( @@ -593,9 +593,9 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte 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 8a49850b1822..7fe01825890f 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 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, } }; mShadeController.postOnShadeExpanded(clickPendingViewRunnable); - mShadeController.instantExpandNotificationsPanel(); + mShadeController.instantExpandShade(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt index 946d7e4a3e75..4d914fe0adef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt @@ -52,6 +52,6 @@ class StatusBarPipelineFlags @Inject constructor(private val featureFlags: Featu * Returns true if we should apply some coloring to the wifi icon that was rendered with the new * pipeline to help with debugging. */ - // For now, just always apply the debug coloring if we've enabled the new icon. - fun useWifiDebugColoring(): Boolean = useNewWifiIcon() + fun useWifiDebugColoring(): Boolean = + featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt index 7aa5ee1389f3..8ff9198da119 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt @@ -23,9 +23,10 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.qs.SettingObserver -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange +import com.android.systemui.statusbar.pipeline.dagger.AirplaneTableLog import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -58,7 +59,7 @@ class AirplaneModeRepositoryImpl constructor( @Background private val bgHandler: Handler, private val globalSettings: GlobalSettings, - logger: ConnectivityPipelineLogger, + @AirplaneTableLog logger: TableLogBuffer, @Application scope: CoroutineScope, ) : AirplaneModeRepository { // TODO(b/254848912): Replace this with a generic SettingObserver coroutine once we have it. @@ -82,7 +83,12 @@ constructor( awaitClose { observer.isListening = false } } .distinctUntilChanged() - .logInputChange(logger, "isAirplaneMode") + .logDiffsForTable( + logger, + columnPrefix = "", + columnName = "isAirplaneMode", + initialValue = false + ) .stateIn( scope, started = SharingStarted.WhileSubscribed(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt new file mode 100644 index 000000000000..4f70f660187f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt @@ -0,0 +1,25 @@ +/* + * 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.statusbar.pipeline.dagger + +import javax.inject.Qualifier + +/** Airplane mode logs in table format. */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class AirplaneTableLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 0662fb3d52b9..c961422086f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -71,5 +71,13 @@ abstract class StatusBarPipelineModule { fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { return factory.create("WifiTableLog", 100) } + + @JvmStatic + @Provides + @SysUISingleton + @AirplaneTableLog + fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { + return factory.create("AirplaneTableLog", 30) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt index 8436b13d7038..a682a5711a6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt @@ -31,15 +31,19 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { if (prevVal is Inactive) { return } - row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE) if (prevVal is CarrierMerged) { // The only difference between CarrierMerged and Inactive is the type + row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE) return } // When changing from Active to Inactive, we need to log diffs to all the fields. - logDiffsFromActiveToNotActive(prevVal as Active, row) + logFullNonActiveNetwork(TYPE_INACTIVE, row) + } + + override fun logFull(row: TableRowLogger) { + logFullNonActiveNetwork(TYPE_INACTIVE, row) } } @@ -56,15 +60,15 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { if (prevVal is CarrierMerged) { return } - row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED) if (prevVal is Inactive) { // The only difference between CarrierMerged and Inactive is the type. + row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED) return } // When changing from Active to CarrierMerged, we need to log diffs to all the fields. - logDiffsFromActiveToNotActive(prevVal as Active, row) + logFullNonActiveNetwork(TYPE_CARRIER_MERGED, row) } } @@ -121,7 +125,11 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_VALIDATED, isValidated) } if (prevVal !is Active || prevVal.level != level) { - row.logChange(COL_LEVEL, level ?: LEVEL_DEFAULT) + if (level != null) { + row.logChange(COL_LEVEL, level) + } else { + row.logChange(COL_LEVEL, LEVEL_DEFAULT) + } } if (prevVal !is Active || prevVal.ssid != ssid) { row.logChange(COL_SSID, ssid) @@ -143,7 +151,6 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } } - override fun toString(): String { // Only include the passpoint-related values in the string if we have them. (Most // networks won't have them so they'll be mostly clutter.) @@ -170,21 +177,15 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } } - internal fun logDiffsFromActiveToNotActive(prevActive: Active, row: TableRowLogger) { + internal fun logFullNonActiveNetwork(type: String, row: TableRowLogger) { + row.logChange(COL_NETWORK_TYPE, type) row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT) row.logChange(COL_VALIDATED, false) row.logChange(COL_LEVEL, LEVEL_DEFAULT) row.logChange(COL_SSID, null) - - if (prevActive.isPasspointAccessPoint) { - row.logChange(COL_PASSPOINT_ACCESS_POINT, false) - } - if (prevActive.isOnlineSignUpForPasspointAccessPoint) { - row.logChange(COL_ONLINE_SIGN_UP, false) - } - if (prevActive.passpointProviderFriendlyName != null) { - row.logChange(COL_PASSPOINT_NAME, null) - } + row.logChange(COL_PASSPOINT_ACCESS_POINT, false) + row.logChange(COL_ONLINE_SIGN_UP, false) + row.logChange(COL_PASSPOINT_NAME, null) } } @@ -201,5 +202,5 @@ const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint" const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint" const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName" -const val LEVEL_DEFAULT = -1 -const val NETWORK_ID_DEFAULT = -1 +val LEVEL_DEFAULT: String? = null +val NETWORK_ID_DEFAULT: String? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index d84cbcc60853..6875b523a962 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -120,6 +120,7 @@ public class Clock extends TextView implements @Override public void onUserChanged(int newUser, @NonNull Context userContext) { mCurrentUserId = newUser; + updateClock(); } }; @@ -190,7 +191,6 @@ public class Clock extends TextView implements filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - filter.addAction(Intent.ACTION_USER_SWITCHED); // NOTE: This receiver could run before this method returns, as it's not dispatching // on the main thread and BroadcastDispatcher may not need to register with Context. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java index b234e9c4e746..63b9ff9717d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java @@ -28,11 +28,14 @@ import androidx.annotation.NonNull; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.settings.UserTracker; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Date; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -45,22 +48,34 @@ public class NextAlarmControllerImpl extends BroadcastReceiver private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>(); + private final UserTracker mUserTracker; private AlarmManager mAlarmManager; private AlarmManager.AlarmClockInfo mNextAlarm; + private final UserTracker.Callback mUserChangedCallback = + new UserTracker.Callback() { + @Override + public void onUserChanged(int newUser, @NonNull Context userContext) { + updateNextAlarm(); + } + }; + /** */ @Inject public NextAlarmControllerImpl( + @Main Executor mainExecutor, AlarmManager alarmManager, BroadcastDispatcher broadcastDispatcher, - DumpManager dumpManager) { + DumpManager dumpManager, + UserTracker userTracker) { dumpManager.registerDumpable("NextAlarmController", this); mAlarmManager = alarmManager; + mUserTracker = userTracker; IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL); + mUserTracker.addCallback(mUserChangedCallback, mainExecutor); updateNextAlarm(); } @@ -98,14 +113,13 @@ public class NextAlarmControllerImpl extends BroadcastReceiver public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (action.equals(Intent.ACTION_USER_SWITCHED) - || action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { + if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { updateNextAlarm(); } } private void updateNextAlarm() { - mNextAlarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT); + mNextAlarm = mAlarmManager.getNextAlarmClock(mUserTracker.getUserId()); fireNextAlarmChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java index 0c72b78a3c46..2b29885db682 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java +++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java @@ -17,6 +17,7 @@ package com.android.systemui.user; import android.app.Activity; +import android.os.UserHandle; import com.android.settingslib.users.EditUserInfoController; import com.android.systemui.user.data.repository.UserRepositoryModule; @@ -51,4 +52,22 @@ public abstract class UserModule { @IntoMap @ClassKey(UserSwitcherActivity.class) public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity); + + /** + * Provides the {@link UserHandle} for the user associated with this System UI process. + * + * <p>Note that this is static and unchanging for the life-time of the process we are running + * in. It can be <i>different</i> from the user that is the currently-selected user, which may + * be associated with a different System UI process. + * + * <p>For example, the System UI process which creates all the windows and renders UI is always + * the one associated with the primary user on the device. However, if the user is switched to + * another, non-primary user (for example user "X"), then a secondary System UI process will be + * spawned. While the original primary user process continues to be the only one rendering UI, + * the new system UI process may be used for things like file or content access. + */ + @Provides + public static UserHandle provideUserHandle() { + return new UserHandle(UserHandle.myUserId()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 4c9b8e4639ca..c0f03902202a 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -242,7 +242,15 @@ constructor( val isUserSwitcherEnabled = globalSettings.getIntForUser( Settings.Global.USER_SWITCHER_ENABLED, - 0, + if ( + appContext.resources.getBoolean( + com.android.internal.R.bool.config_showUserSwitcherByDefault + ) + ) { + 1 + } else { + 0 + }, UserHandle.USER_SYSTEM, ) != 0 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 c5b697c90e0c..512fadf1384f 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 @@ -114,9 +114,9 @@ constructor( private val callbackMutex = Mutex() private val callbacks = mutableSetOf<UserCallback>() - private val userInfos = - combine(repository.userSwitcherSettings, repository.userInfos) { settings, userInfos -> - userInfos.filter { !it.isGuest || canCreateGuestUser(settings) }.filter { it.isFull } + private val userInfos: Flow<List<UserInfo>> = + repository.userInfos.map { userInfos -> + userInfos.filter { it.isFull } } /** List of current on-device users to select from. */ @@ -493,7 +493,7 @@ constructor( 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 85c29647719b..14cc3e783fed 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 @@ sealed class ShowDialogRequestModel( ) : 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 000000000000..3fe2a7b19851 --- /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 ed2589889435..b8ae257aaac5 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 @@ class UserSwitchDialog( 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 41410542204c..d4512309f6c6 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 @@ constructor( } 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/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt index f71d596ff835..b61b2e66fb22 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt @@ -20,7 +20,6 @@ import java.util.concurrent.atomic.AtomicReference import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onStart @@ -58,6 +57,22 @@ fun <T, R> Flow<T>.pairwiseBy( onStart { emit(initialValue) }.pairwiseBy(transform) /** + * Returns a new [Flow] that combines the two most recent emissions from [this] using [transform]. + * + * + * The output of [getInitialValue] will be used as the "old" value for the first emission. As + * opposed to the initial value in the above [pairwiseBy], [getInitialValue] can do some work before + * returning the initial value. + * + * Useful for code that needs to compare the current value to the previous value. + */ +fun <T, R> Flow<T>.pairwiseBy( + getInitialValue: suspend () -> T, + transform: suspend (previousValue: T, newValue: T) -> R, +): Flow<R> = + onStart { emit(getInitialValue()) }.pairwiseBy(transform) + +/** * Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new * Flow will not start emitting until it has received two emissions from the upstream Flow. * diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java index ad97ef4a79bc..5df4a5b54ccc 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; 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 @@ public class ImageWallpaper extends WallpaperService { } @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 a4384d5810ce..7033ccde8c7d 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 @@ public class BubblesManager { } 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 @@ public class BubblesManager { } 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 000000000000..2a75bd98bfe8 --- /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 000000000000..188dff21efa4 --- /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 000000000000..d9686bbeb0cd --- /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/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index e39b9b58158f..84f6d913b310 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -558,11 +558,40 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged(); verify(mView).onDensityOrFontScaleChanged(); - verify(mKeyguardSecurityViewFlipperController).onDensityOrFontScaleChanged(); + verify(mKeyguardSecurityViewFlipperController).clearViews(); verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class), any(KeyguardSecurityCallback.class)); } + @Test + public void onThemeChanged() { + ArgumentCaptor<ConfigurationController.ConfigurationListener> + configurationListenerArgumentCaptor = ArgumentCaptor.forClass( + ConfigurationController.ConfigurationListener.class); + mKeyguardSecurityContainerController.onViewAttached(); + verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture()); + configurationListenerArgumentCaptor.getValue().onThemeChanged(); + + verify(mView).reloadColors(); + verify(mKeyguardSecurityViewFlipperController).clearViews(); + verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class), + any(KeyguardSecurityCallback.class)); + } + + @Test + public void onUiModeChanged() { + ArgumentCaptor<ConfigurationController.ConfigurationListener> + configurationListenerArgumentCaptor = ArgumentCaptor.forClass( + ConfigurationController.ConfigurationListener.class); + mKeyguardSecurityContainerController.onViewAttached(); + verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture()); + configurationListenerArgumentCaptor.getValue().onUiModeChanged(); + + verify(mView).reloadColors(); + verify(mKeyguardSecurityViewFlipperController).clearViews(); + verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class), + any(KeyguardSecurityCallback.class)); + } private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() { mKeyguardSecurityContainerController.onViewAttached(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java index fd02ac97cec2..1614b577a6cc 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java @@ -109,7 +109,7 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { @Test public void onDensityOrFontScaleChanged() { - mKeyguardSecurityViewFlipperController.onDensityOrFontScaleChanged(); + mKeyguardSecurityViewFlipperController.clearViews(); verify(mView).removeAllViews(); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 63e160331e6c..4e358ddd49ea 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -609,6 +609,64 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() { + // GIVEN unlocking with biometric is allowed + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + + // THEN unlocking with face and fp is allowed + Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FACE)); + Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT)); + } + + @Test + public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() { + // GIVEN unlocking with biometric is not allowed + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); + + // THEN unlocking with face is not allowed + Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FACE)); + } + + @Test + public void testUnlockingWithFaceAllowed_fingerprintLockout() { + // GIVEN unlocking with biometric is allowed + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + + // WHEN fingerprint is locked out + fingerprintErrorLockedOut(); + + // THEN unlocking with face is not allowed + Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FACE)); + } + + @Test + public void testUnlockingWithFpAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() { + // GIVEN unlocking with biometric is not allowed + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); + + // THEN unlocking with fingerprint is not allowed + Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT)); + } + + @Test + public void testUnlockingWithFpAllowed_fingerprintLockout() { + // GIVEN unlocking with biometric is allowed + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + + // WHEN fingerprint is locked out + fingerprintErrorLockedOut(); + + // THEN unlocking with fingeprint is not allowed + Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT)); + } + + @Test public void testTriesToAuthenticate_whenBouncer() { setKeyguardBouncerVisibility(true); @@ -1284,6 +1342,24 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void testRequestFaceAuthFromOccludingApp_whenInvoked_startsFaceAuth() { + mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true); + + assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue(); + } + + @Test + public void testRequestFaceAuthFromOccludingApp_whenInvoked_stopsFaceAuth() { + mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true); + + assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue(); + + mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); + + assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse(); + } + + @Test public void testRequireUnlockForNfc_Broadcast() { KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); mKeyguardUpdateMonitor.registerCallback(callback); diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 181839ab512f..0627fc6c542f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -77,7 +77,6 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.decor.CornerDecorProvider; import com.android.systemui.decor.CutoutDecorProviderFactory; import com.android.systemui.decor.CutoutDecorProviderImpl; @@ -132,8 +131,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { @Mock private TunerService mTunerService; @Mock - private BroadcastDispatcher mBroadcastDispatcher; - @Mock private UserTracker mUserTracker; @Mock private PrivacyDotViewController mDotViewController; @@ -223,8 +220,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { mExecutor)); mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings, - mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController, - mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) { + mTunerService, mUserTracker, mDotViewController, mThreadFactory, + mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) { @Override public void start() { super.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index acdafe3e1c7d..b267a5c23a49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -70,8 +70,13 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.biometrics.udfps.InteractionEvent; +import com.android.systemui.biometrics.udfps.NormalizedTouchData; +import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor; +import com.android.systemui.biometrics.udfps.TouchProcessorResult; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.plugins.FalsingManager; @@ -190,6 +195,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private AlternateUdfpsTouchProvider mAlternateTouchProvider; @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; + @Mock + private SinglePointerTouchProcessor mSinglePointerTouchProcessor; // Capture listeners so that they can be used to send events @Captor @@ -275,7 +282,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mDisplayManager, mHandler, mConfigurationController, mSystemClock, mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker, mActivityLaunchAnimator, alternateTouchProvider, mBiometricsExecutor, - mPrimaryBouncerInteractor); + mPrimaryBouncerInteractor, mSinglePointerTouchProcessor); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -1086,4 +1093,100 @@ public class UdfpsControllerTest extends SysuiTestCase { anyString(), any()); } + + @Test + public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath() + throws RemoteException { + // Disable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(false); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN ACTION_DOWN is received + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricsExecutor.runAllReady(); + downEvent.recycle(); + + // AND ACTION_MOVE is received + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); + mBiometricsExecutor.runAllReady(); + moveEvent.recycle(); + + // AND ACTION_UP is received + MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); + mBiometricsExecutor.runAllReady(); + upEvent.recycle(); + + // THEN the old FingerprintManager path is invoked. + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), + anyFloat(), anyFloat()); + verify(mFingerprintManager).onPointerUp(anyLong(), anyInt()); + } + + @Test + public void onTouch_withNewTouchDetection_shouldCallOldFingerprintManagerPath() + throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( + InteractionEvent.DOWN, 1 /* pointerId */, touchData); + final TouchProcessorResult processorResultUp = new TouchProcessorResult.ProcessedTouch( + InteractionEvent.UP, 1 /* pointerId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricsExecutor.runAllReady(); + downEvent.recycle(); + + // AND ACTION_UP is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultUp); + MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); + mBiometricsExecutor.runAllReady(); + upEvent.recycle(); + + // THEN the new FingerprintManager path is invoked. + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt new file mode 100644 index 000000000000..4f89b69108f4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt @@ -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.systemui.biometrics.udfps + +import android.graphics.Rect +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters + +@SmallTest +@RunWith(Parameterized::class) +class BoundingBoxOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() { + val underTest = BoundingBoxOverlapDetector() + + @Test + fun isGoodOverlap() { + val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat()) + val actual = underTest.isGoodOverlap(touchData, SENSOR) + + assertThat(actual).isEqualTo(testCase.expected) + } + + data class TestCase(val x: Int, val y: Int, val expected: Boolean) + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun data(): List<TestCase> = + listOf( + genPositiveTestCases( + validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()), + validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY()) + ), + genNegativeTestCases( + invalidXs = listOf(SENSOR.left - 1, SENSOR.right + 1), + invalidYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1), + validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()), + validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY()) + ) + ) + .flatten() + } +} + +/* Placeholder touch parameters. */ +private const val POINTER_ID = 42 +private const val NATIVE_MINOR = 2.71828f +private const val NATIVE_MAJOR = 3.14f +private const val ORIENTATION = 1.23f +private const val TIME = 12345699L +private const val GESTURE_START = 12345600L + +/* Template [NormalizedTouchData]. */ +private val TOUCH_DATA = + NormalizedTouchData( + POINTER_ID, + x = 0f, + y = 0f, + NATIVE_MINOR, + NATIVE_MAJOR, + ORIENTATION, + TIME, + GESTURE_START + ) + +private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */) + +private fun genTestCases( + xs: List<Int>, + ys: List<Int>, + expected: Boolean +): List<BoundingBoxOverlapDetectorTest.TestCase> { + return xs.flatMap { x -> + ys.map { y -> BoundingBoxOverlapDetectorTest.TestCase(x, y, expected) } + } +} + +private fun genPositiveTestCases( + validXs: List<Int>, + validYs: List<Int>, +) = genTestCases(validXs, validYs, expected = true) + +private fun genNegativeTestCases( + invalidXs: List<Int>, + invalidYs: List<Int>, + validXs: List<Int>, + validYs: List<Int>, +): List<BoundingBoxOverlapDetectorTest.TestCase> { + return genTestCases(invalidXs, validYs, expected = false) + + genTestCases(validXs, invalidYs, expected = false) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt new file mode 100644 index 000000000000..834d0a69e427 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt @@ -0,0 +1,90 @@ +package com.android.systemui.biometrics.udfps + +import android.graphics.Rect +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters + +@SmallTest +@RunWith(Parameterized::class) +class NormalizedTouchDataTest(val testCase: TestCase) : SysuiTestCase() { + + @Test + fun isWithinSensor() { + val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat()) + val actual = touchData.isWithinSensor(SENSOR) + + assertThat(actual).isEqualTo(testCase.expected) + } + + data class TestCase(val x: Int, val y: Int, val expected: Boolean) + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun data(): List<TestCase> = + listOf( + genPositiveTestCases( + validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()), + validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY()) + ), + genNegativeTestCases( + invalidXs = listOf(SENSOR.left - 1, SENSOR.right + 1), + invalidYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1), + validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()), + validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY()) + ) + ) + .flatten() + } +} + +/* Placeholder touch parameters. */ +private const val POINTER_ID = 42 +private const val NATIVE_MINOR = 2.71828f +private const val NATIVE_MAJOR = 3.14f +private const val ORIENTATION = 1.23f +private const val TIME = 12345699L +private const val GESTURE_START = 12345600L + +/* Template [NormalizedTouchData]. */ +private val TOUCH_DATA = + NormalizedTouchData( + POINTER_ID, + x = 0f, + y = 0f, + NATIVE_MINOR, + NATIVE_MAJOR, + ORIENTATION, + TIME, + GESTURE_START + ) + +private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */) + +private fun genTestCases( + xs: List<Int>, + ys: List<Int>, + expected: Boolean +): List<NormalizedTouchDataTest.TestCase> { + return xs.flatMap { x -> ys.map { y -> NormalizedTouchDataTest.TestCase(x, y, expected) } } +} + +private fun genPositiveTestCases( + validXs: List<Int>, + validYs: List<Int>, +) = genTestCases(validXs, validYs, expected = true) + +private fun genNegativeTestCases( + invalidXs: List<Int>, + invalidYs: List<Int>, + validXs: List<Int>, + validYs: List<Int>, +): List<NormalizedTouchDataTest.TestCase> { + return genTestCases(invalidXs, validYs, expected = false) + + genTestCases(validXs, invalidYs, expected = false) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt new file mode 100644 index 000000000000..95c53b408056 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt @@ -0,0 +1,506 @@ +/* + * 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.biometrics.udfps + +import android.graphics.Rect +import android.view.MotionEvent +import android.view.MotionEvent.INVALID_POINTER_ID +import android.view.MotionEvent.PointerProperties +import android.view.Surface +import android.view.Surface.Rotation +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.UdfpsOverlayParams +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters + +@SmallTest +@RunWith(Parameterized::class) +class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() { + private val overlapDetector = FakeOverlapDetector() + private val underTest = SinglePointerTouchProcessor(overlapDetector) + + @Test + fun processTouch() { + overlapDetector.shouldReturn = testCase.isGoodOverlap + + val actual = + underTest.processTouch( + testCase.event, + testCase.previousPointerOnSensorId, + testCase.overlayParams, + ) + + assertThat(actual).isInstanceOf(testCase.expected.javaClass) + if (actual is TouchProcessorResult.ProcessedTouch) { + assertThat(actual).isEqualTo(testCase.expected) + } + } + + data class TestCase( + val event: MotionEvent, + val isGoodOverlap: Boolean, + val previousPointerOnSensorId: Int, + val overlayParams: UdfpsOverlayParams, + val expected: TouchProcessorResult, + ) { + override fun toString(): String { + val expectedOutput = + if (expected is TouchProcessorResult.ProcessedTouch) { + expected.event.toString() + + ", (x: ${expected.touchData.x}, y: ${expected.touchData.y})" + + ", pointerOnSensorId: ${expected.pointerOnSensorId}" + + ", ..." + } else { + TouchProcessorResult.Failure().toString() + } + return "{" + + MotionEvent.actionToString(event.action) + + ", (x: ${event.x}, y: ${event.y})" + + ", scale: ${overlayParams.scaleFactor}" + + ", rotation: " + + Surface.rotationToString(overlayParams.rotation) + + ", previousPointerOnSensorId: $previousPointerOnSensorId" + + ", ...} expected: {$expectedOutput}" + } + } + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun data(): List<TestCase> = + listOf( + // MotionEvent.ACTION_DOWN + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_DOWN, + previousPointerOnSensorId = INVALID_POINTER_ID, + isGoodOverlap = true, + expectedInteractionEvent = InteractionEvent.DOWN, + expectedPointerOnSensorId = POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_DOWN, + previousPointerOnSensorId = POINTER_ID, + isGoodOverlap = true, + expectedInteractionEvent = InteractionEvent.DOWN, + expectedPointerOnSensorId = POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_DOWN, + previousPointerOnSensorId = INVALID_POINTER_ID, + isGoodOverlap = false, + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_DOWN, + previousPointerOnSensorId = POINTER_ID, + isGoodOverlap = false, + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + // MotionEvent.ACTION_MOVE + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_MOVE, + previousPointerOnSensorId = INVALID_POINTER_ID, + isGoodOverlap = true, + expectedInteractionEvent = InteractionEvent.DOWN, + expectedPointerOnSensorId = POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_MOVE, + previousPointerOnSensorId = POINTER_ID, + isGoodOverlap = true, + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_MOVE, + previousPointerOnSensorId = INVALID_POINTER_ID, + isGoodOverlap = false, + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_MOVE, + previousPointerOnSensorId = POINTER_ID, + isGoodOverlap = false, + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + // MotionEvent.ACTION_UP + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_UP, + previousPointerOnSensorId = INVALID_POINTER_ID, + isGoodOverlap = true, + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_UP, + previousPointerOnSensorId = POINTER_ID, + isGoodOverlap = true, + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_UP, + previousPointerOnSensorId = INVALID_POINTER_ID, + isGoodOverlap = false, + expectedInteractionEvent = InteractionEvent.UNCHANGED, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_UP, + previousPointerOnSensorId = POINTER_ID, + isGoodOverlap = false, + expectedInteractionEvent = InteractionEvent.UP, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + // MotionEvent.ACTION_CANCEL + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_CANCEL, + previousPointerOnSensorId = INVALID_POINTER_ID, + isGoodOverlap = true, + expectedInteractionEvent = InteractionEvent.CANCEL, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_CANCEL, + previousPointerOnSensorId = POINTER_ID, + isGoodOverlap = true, + expectedInteractionEvent = InteractionEvent.CANCEL, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_CANCEL, + previousPointerOnSensorId = INVALID_POINTER_ID, + isGoodOverlap = false, + expectedInteractionEvent = InteractionEvent.CANCEL, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + genPositiveTestCases( + motionEventAction = MotionEvent.ACTION_CANCEL, + previousPointerOnSensorId = POINTER_ID, + isGoodOverlap = false, + expectedInteractionEvent = InteractionEvent.CANCEL, + expectedPointerOnSensorId = INVALID_POINTER_ID, + ), + ) + .flatten() + + listOf( + // Unsupported MotionEvent actions. + genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_DOWN), + genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_UP), + genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_ENTER), + genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_MOVE), + genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT), + ) + .flatten() + } +} + +/* Display dimensions in native resolution and natural orientation. */ +private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400 +private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600 + +/* + * ROTATION_0 map: + * _ _ _ _ + * _ _ O _ + * _ _ _ _ + * _ S _ _ + * _ S _ _ + * _ _ _ _ + * + * (_) empty space + * (S) sensor + * (O) touch outside of the sensor + */ +private val ROTATION_0_NATIVE_SENSOR_BOUNDS = + Rect( + 100, /* left */ + 300, /* top */ + 200, /* right */ + 500, /* bottom */ + ) +private val ROTATION_0_INPUTS = + OrientationBasedInputs( + rotation = Surface.ROTATION_0, + nativeXWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterX(), + nativeYWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterY(), + nativeXOutsideSensor = 250f, + nativeYOutsideSensor = 150f, + ) + +/* + * ROTATION_90 map: + * _ _ _ _ _ _ + * _ O _ _ _ _ + * _ _ _ S S _ + * _ _ _ _ _ _ + * + * (_) empty space + * (S) sensor + * (O) touch outside of the sensor + */ +private val ROTATION_90_NATIVE_SENSOR_BOUNDS = + Rect( + 300, /* left */ + 200, /* top */ + 500, /* right */ + 300, /* bottom */ + ) +private val ROTATION_90_INPUTS = + OrientationBasedInputs( + rotation = Surface.ROTATION_90, + nativeXWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterX(), + nativeYWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterY(), + nativeXOutsideSensor = 150f, + nativeYOutsideSensor = 150f, + ) + +/* ROTATION_180 is not supported. It's treated the same as ROTATION_0. */ +private val ROTATION_180_INPUTS = + ROTATION_0_INPUTS.copy( + rotation = Surface.ROTATION_180, + ) + +/* + * ROTATION_270 map: + * _ _ _ _ _ _ + * _ S S _ _ _ + * _ _ _ _ O _ + * _ _ _ _ _ _ + * + * (_) empty space + * (S) sensor + * (O) touch outside of the sensor + */ +private val ROTATION_270_NATIVE_SENSOR_BOUNDS = + Rect( + 100, /* left */ + 100, /* top */ + 300, /* right */ + 200, /* bottom */ + ) +private val ROTATION_270_INPUTS = + OrientationBasedInputs( + rotation = Surface.ROTATION_270, + nativeXWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterX(), + nativeYWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterY(), + nativeXOutsideSensor = 450f, + nativeYOutsideSensor = 250f, + ) + +/* Placeholder touch parameters. */ +private const val POINTER_ID = 42 +private const val NATIVE_MINOR = 2.71828f +private const val NATIVE_MAJOR = 3.14f +private const val ORIENTATION = 1.23f +private const val TIME = 12345699L +private const val GESTURE_START = 12345600L + +/* Template [MotionEvent]. */ +private val MOTION_EVENT = + obtainMotionEvent( + action = 0, + pointerId = POINTER_ID, + x = 0f, + y = 0f, + minor = 0f, + major = 0f, + orientation = ORIENTATION, + time = TIME, + gestureStart = GESTURE_START, + ) + +/* Template [NormalizedTouchData]. */ +private val NORMALIZED_TOUCH_DATA = + NormalizedTouchData( + POINTER_ID, + x = 0f, + y = 0f, + NATIVE_MINOR, + NATIVE_MAJOR, + ORIENTATION, + TIME, + GESTURE_START + ) + +/* + * Contains test inputs that are tied to a particular device orientation. + * + * "native" means in native resolution (not scaled). + */ +private data class OrientationBasedInputs( + @Rotation val rotation: Int, + val nativeXWithinSensor: Float, + val nativeYWithinSensor: Float, + val nativeXOutsideSensor: Float, + val nativeYOutsideSensor: Float, +) { + + fun toOverlayParams(scaleFactor: Float): UdfpsOverlayParams = + UdfpsOverlayParams( + sensorBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor), + overlayBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor), + naturalDisplayHeight = (ROTATION_0_NATIVE_DISPLAY_HEIGHT * scaleFactor).toInt(), + naturalDisplayWidth = (ROTATION_0_NATIVE_DISPLAY_WIDTH * scaleFactor).toInt(), + scaleFactor = scaleFactor, + rotation = rotation + ) + + fun getNativeX(isWithinSensor: Boolean): Float { + return if (isWithinSensor) nativeXWithinSensor else nativeXOutsideSensor + } + + fun getNativeY(isWithinSensor: Boolean): Float { + return if (isWithinSensor) nativeYWithinSensor else nativeYOutsideSensor + } +} + +private fun genPositiveTestCases( + motionEventAction: Int, + previousPointerOnSensorId: Int, + isGoodOverlap: Boolean, + expectedInteractionEvent: InteractionEvent, + expectedPointerOnSensorId: Int +): List<SinglePointerTouchProcessorTest.TestCase> { + val scaleFactors = listOf(0.75f, 1f, 1.5f) + val orientations = + listOf( + ROTATION_0_INPUTS, + ROTATION_90_INPUTS, + ROTATION_180_INPUTS, + ROTATION_270_INPUTS, + ) + return scaleFactors.flatMap { scaleFactor -> + orientations.map { orientation -> + val overlayParams = orientation.toOverlayParams(scaleFactor) + val nativeX = orientation.getNativeX(isGoodOverlap) + val nativeY = orientation.getNativeY(isGoodOverlap) + val event = + MOTION_EVENT.copy( + action = motionEventAction, + x = nativeX * scaleFactor, + y = nativeY * scaleFactor, + minor = NATIVE_MINOR * scaleFactor, + major = NATIVE_MAJOR * scaleFactor, + ) + val expectedTouchData = + NORMALIZED_TOUCH_DATA.copy( + x = ROTATION_0_INPUTS.getNativeX(isGoodOverlap), + y = ROTATION_0_INPUTS.getNativeY(isGoodOverlap), + ) + val expected = + TouchProcessorResult.ProcessedTouch( + event = expectedInteractionEvent, + pointerOnSensorId = expectedPointerOnSensorId, + touchData = expectedTouchData, + ) + SinglePointerTouchProcessorTest.TestCase( + event = event, + isGoodOverlap = isGoodOverlap, + previousPointerOnSensorId = previousPointerOnSensorId, + overlayParams = overlayParams, + expected = expected, + ) + } + } +} + +private fun genTestCasesForUnsupportedAction( + motionEventAction: Int +): List<SinglePointerTouchProcessorTest.TestCase> { + val isGoodOverlap = true + val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID) + return previousPointerOnSensorIds.map { previousPointerOnSensorId -> + val overlayParams = ROTATION_0_INPUTS.toOverlayParams(scaleFactor = 1f) + val nativeX = ROTATION_0_INPUTS.getNativeX(isGoodOverlap) + val nativeY = ROTATION_0_INPUTS.getNativeY(isGoodOverlap) + val event = + MOTION_EVENT.copy( + action = motionEventAction, + x = nativeX, + y = nativeY, + minor = NATIVE_MINOR, + major = NATIVE_MAJOR, + ) + SinglePointerTouchProcessorTest.TestCase( + event = event, + isGoodOverlap = isGoodOverlap, + previousPointerOnSensorId = previousPointerOnSensorId, + overlayParams = overlayParams, + expected = TouchProcessorResult.Failure(), + ) + } +} + +private fun obtainMotionEvent( + action: Int, + pointerId: Int, + x: Float, + y: Float, + minor: Float, + major: Float, + orientation: Float, + time: Long, + gestureStart: Long, +): MotionEvent { + val pp = PointerProperties() + pp.id = pointerId + val pc = MotionEvent.PointerCoords() + pc.x = x + pc.y = y + pc.touchMinor = minor + pc.touchMajor = major + pc.orientation = orientation + return MotionEvent.obtain( + gestureStart /* downTime */, + time /* eventTime */, + action /* action */, + 1 /* pointerCount */, + arrayOf(pp) /* pointerProperties */, + arrayOf(pc) /* pointerCoords */, + 0 /* metaState */, + 0 /* buttonState */, + 1f /* xPrecision */, + 1f /* yPrecision */, + 0 /* deviceId */, + 0 /* edgeFlags */, + 0 /* source */, + 0 /* flags */ + ) +} + +private fun MotionEvent.copy( + action: Int = this.action, + pointerId: Int = this.getPointerId(0), + x: Float = this.rawX, + y: Float = this.rawY, + minor: Float = this.touchMinor, + major: Float = this.touchMajor, + orientation: Float = this.orientation, + time: Long = this.eventTime, + gestureStart: Long = this.downTime, +) = obtainMotionEvent(action, pointerId, x, y, minor, major, orientation, time, gestureStart) + +private fun Rect.scaled(scaleFactor: Float) = Rect(this).apply { scale(scaleFactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt new file mode 100644 index 000000000000..4b88b44c3f03 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt @@ -0,0 +1,187 @@ +/* + * 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.controls + +import android.content.pm.UserInfo +import android.provider.Settings +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class ControlsSettingsRepositoryImplTest : SysuiTestCase() { + + companion object { + private const val LOCKSCREEN_SHOW = Settings.Secure.LOCKSCREEN_SHOW_CONTROLS + private const val LOCKSCREEN_ACTION = Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS + + private fun createUser(id: Int): UserInfo { + return UserInfo(id, "user_$id", 0) + } + + private val ALL_USERS = (0..1).map { it to createUser(it) }.toMap() + } + + private lateinit var underTest: ControlsSettingsRepository + + private lateinit var testScope: TestScope + private lateinit var secureSettings: FakeSettings + private lateinit var userRepository: FakeUserRepository + + @Before + fun setUp() { + secureSettings = FakeSettings() + userRepository = FakeUserRepository() + userRepository.setUserInfos(ALL_USERS.values.toList()) + + val coroutineDispatcher = UnconfinedTestDispatcher() + testScope = TestScope(coroutineDispatcher) + + underTest = + ControlsSettingsRepositoryImpl( + scope = testScope.backgroundScope, + backgroundDispatcher = coroutineDispatcher, + userRepository = userRepository, + secureSettings = secureSettings, + ) + } + + @Test + fun showInLockScreen() = + testScope.runTest { + setUser(0) + val values = mutableListOf<Boolean>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.canShowControlsInLockscreen.toList(values) + } + assertThat(values.last()).isFalse() + + secureSettings.putBool(LOCKSCREEN_SHOW, true) + assertThat(values.last()).isTrue() + + secureSettings.putBool(LOCKSCREEN_SHOW, false) + assertThat(values.last()).isFalse() + + secureSettings.putBoolForUser(LOCKSCREEN_SHOW, true, 1) + assertThat(values.last()).isFalse() + + setUser(1) + assertThat(values.last()).isTrue() + + job.cancel() + } + + @Test + fun showInLockScreen_changesInOtherUsersAreNotQueued() = + testScope.runTest { + setUser(0) + + val values = mutableListOf<Boolean>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.canShowControlsInLockscreen.toList(values) + } + + secureSettings.putBoolForUser(LOCKSCREEN_SHOW, true, 1) + secureSettings.putBoolForUser(LOCKSCREEN_SHOW, false, 1) + + setUser(1) + assertThat(values.last()).isFalse() + assertThat(values).containsNoneIn(listOf(true)) + + job.cancel() + } + + @Test + fun actionInLockScreen() = + testScope.runTest { + setUser(0) + val values = mutableListOf<Boolean>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.allowActionOnTrivialControlsInLockscreen.toList(values) + } + assertThat(values.last()).isFalse() + + secureSettings.putBool(LOCKSCREEN_ACTION, true) + assertThat(values.last()).isTrue() + + secureSettings.putBool(LOCKSCREEN_ACTION, false) + assertThat(values.last()).isFalse() + + secureSettings.putBoolForUser(LOCKSCREEN_ACTION, true, 1) + assertThat(values.last()).isFalse() + + setUser(1) + assertThat(values.last()).isTrue() + + job.cancel() + } + + @Test + fun actionInLockScreen_changesInOtherUsersAreNotQueued() = + testScope.runTest { + setUser(0) + + val values = mutableListOf<Boolean>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.allowActionOnTrivialControlsInLockscreen.toList(values) + } + + secureSettings.putBoolForUser(LOCKSCREEN_ACTION, true, 1) + secureSettings.putBoolForUser(LOCKSCREEN_ACTION, false, 1) + + setUser(1) + assertThat(values.last()).isFalse() + assertThat(values).containsNoneIn(listOf(true)) + + job.cancel() + } + + @Test + fun valueIsUpdatedWhenNotSubscribed() = + testScope.runTest { + setUser(0) + assertThat(underTest.canShowControlsInLockscreen.value).isFalse() + + secureSettings.putBool(LOCKSCREEN_SHOW, true) + + assertThat(underTest.canShowControlsInLockscreen.value).isTrue() + } + + private suspend fun setUser(id: Int) { + secureSettings.userId = id + userRepository.setSelectedUserInfo(ALL_USERS[id]!!) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt new file mode 100644 index 000000000000..8a1bed20e700 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt @@ -0,0 +1,37 @@ +/* + * 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.controls + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeControlsSettingsRepository : ControlsSettingsRepository { + private val _canShowControlsInLockscreen = MutableStateFlow(false) + override val canShowControlsInLockscreen = _canShowControlsInLockscreen.asStateFlow() + private val _allowActionOnTrivialControlsInLockscreen = MutableStateFlow(false) + override val allowActionOnTrivialControlsInLockscreen = + _allowActionOnTrivialControlsInLockscreen.asStateFlow() + + fun setCanShowControlsInLockscreen(value: Boolean) { + _canShowControlsInLockscreen.value = value + } + + fun setAllowActionOnTrivialControlsInLockscreen(value: Boolean) { + _allowActionOnTrivialControlsInLockscreen.value = value + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt index 4ed5649c9c50..1d00d6b05568 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt @@ -18,30 +18,24 @@ package com.android.systemui.controls.ui import android.content.Context import android.content.SharedPreferences -import android.database.ContentObserver -import android.net.Uri -import android.os.Handler -import android.os.UserHandle -import android.provider.Settings import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.controls.ControlsMetricsLogger +import com.android.systemui.controls.FakeControlsSettingsRepository import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.DelayableExecutor -import com.android.systemui.util.mockito.any import com.android.systemui.util.settings.SecureSettings import com.android.wm.shell.TaskViewFactory import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Answers -import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.`when` @@ -79,8 +73,6 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { @Mock private lateinit var secureSettings: SecureSettings @Mock - private lateinit var mainHandler: Handler - @Mock private lateinit var userContextProvider: UserContextProvider companion object { @@ -91,17 +83,15 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { private lateinit var coordinator: ControlActionCoordinatorImpl private lateinit var action: ControlActionCoordinatorImpl.Action + private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository @Before fun setUp() { MockitoAnnotations.initMocks(this) - `when`(secureSettings.getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)) - .thenReturn(Settings.Secure - .getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)) - `when`(secureSettings.getIntForUser(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, - 0, UserHandle.USER_CURRENT)) - .thenReturn(1) + controlsSettingsRepository = FakeControlsSettingsRepository() + controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true) + controlsSettingsRepository.setCanShowControlsInLockscreen(true) coordinator = spy(ControlActionCoordinatorImpl( mContext, @@ -115,7 +105,7 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { vibratorHelper, secureSettings, userContextProvider, - mainHandler + controlsSettingsRepository )) val userContext = mock(Context::class.java) @@ -128,9 +118,6 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { `when`(pref.getInt(DeviceControlsControllerImpl.PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)) .thenReturn(2) - verify(secureSettings).registerContentObserverForUser(any(Uri::class.java), - anyBoolean(), any(ContentObserver::class.java), anyInt()) - `when`(cvh.cws.ci.controlId).thenReturn(ID) `when`(cvh.cws.control?.isAuthRequired()).thenReturn(true) action = spy(coordinator.Action(ID, {}, false, true)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index c31fd828c730..1b34706bd220 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -17,7 +17,6 @@ package com.android.systemui.controls.controller import android.app.PendingIntent -import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.ContextWrapper @@ -31,7 +30,6 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.backup.BackupHelper -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.management.ControlsListingController @@ -85,10 +83,8 @@ class ControlsControllerImplTest : SysuiTestCase() { @Mock private lateinit var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var listingController: ControlsListingController - @Mock(stubOnly = true) + @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var userFileManager: UserFileManager @@ -104,7 +100,7 @@ class ControlsControllerImplTest : SysuiTestCase() { ArgumentCaptor<ControlsBindingController.LoadCallback> @Captor - private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> + private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback> @Captor private lateinit var listingCallbackCaptor: ArgumentCaptor<ControlsListingController.ControlsListingCallback> @@ -170,16 +166,15 @@ class ControlsControllerImplTest : SysuiTestCase() { uiController, bindingController, listingController, - broadcastDispatcher, userFileManager, + userTracker, Optional.of(persistenceWrapper), - mock(DumpManager::class.java), - userTracker + mock(DumpManager::class.java) ) controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper - verify(broadcastDispatcher).registerReceiver( - capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL), anyInt(), any() + verify(userTracker).addCallback( + capture(userTrackerCallbackCaptor), any() ) verify(listingController).addCallback(capture(listingCallbackCaptor)) @@ -227,11 +222,10 @@ class ControlsControllerImplTest : SysuiTestCase() { uiController, bindingController, listingController, - broadcastDispatcher, userFileManager, + userTracker, Optional.of(persistenceWrapper), - mock(DumpManager::class.java), - userTracker + mock(DumpManager::class.java) ) assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites()) } @@ -518,14 +512,8 @@ class ControlsControllerImplTest : SysuiTestCase() { delayableExecutor.runAllReady() reset(persistenceWrapper) - val intent = Intent(Intent.ACTION_USER_SWITCHED).apply { - putExtra(Intent.EXTRA_USER_HANDLE, otherUser) - } - val pendingResult = mock(BroadcastReceiver.PendingResult::class.java) - `when`(pendingResult.sendingUserId).thenReturn(otherUser) - broadcastReceiverCaptor.value.pendingResult = pendingResult - broadcastReceiverCaptor.value.onReceive(mContext, intent) + userTrackerCallbackCaptor.value.onUserChanged(otherUser, mContext) verify(persistenceWrapper).changeFileAndBackupManager(any(), any()) verify(persistenceWrapper).readFavorites() diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt index 77f451f61c3c..48fc46b7e730 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt @@ -17,19 +17,18 @@ package com.android.systemui.controls.dagger import android.testing.AndroidTestingRunner -import android.provider.Settings import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.FakeControlsSettingsRepository import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlsTileResourceConfiguration import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.settings.SecureSettings import dagger.Lazy import java.util.Optional import org.junit.Assert.assertEquals @@ -63,13 +62,13 @@ class ControlsComponentTest : SysuiTestCase() { @Mock private lateinit var lockPatternUtils: LockPatternUtils @Mock - private lateinit var secureSettings: SecureSettings - @Mock private lateinit var optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration> @Mock private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration + private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository + companion object { fun <T> eq(value: T): T = Mockito.eq(value) ?: value } @@ -78,6 +77,8 @@ class ControlsComponentTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + controlsSettingsRepository = FakeControlsSettingsRepository() + `when`(userTracker.userHandle.identifier).thenReturn(0) `when`(optionalControlsTileResourceConfiguration.orElse(any())) .thenReturn(controlsTileResourceConfiguration) @@ -125,8 +126,7 @@ class ControlsComponentTest : SysuiTestCase() { `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(STRONG_AUTH_NOT_REQUIRED) `when`(keyguardStateController.isUnlocked()).thenReturn(false) - `when`(secureSettings.getInt(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), anyInt())) - .thenReturn(0) + controlsSettingsRepository.setCanShowControlsInLockscreen(false) val component = setupComponent(true) assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility()) @@ -137,9 +137,7 @@ class ControlsComponentTest : SysuiTestCase() { `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(STRONG_AUTH_NOT_REQUIRED) `when`(keyguardStateController.isUnlocked()).thenReturn(false) - `when`(secureSettings.getIntForUser(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), - anyInt(), anyInt())) - .thenReturn(1) + controlsSettingsRepository.setCanShowControlsInLockscreen(true) val component = setupComponent(true) assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility()) @@ -147,8 +145,7 @@ class ControlsComponentTest : SysuiTestCase() { @Test fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() { - `when`(secureSettings.getInt(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), anyInt())) - .thenReturn(0) + controlsSettingsRepository.setCanShowControlsInLockscreen(false) `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(STRONG_AUTH_NOT_REQUIRED) `when`(keyguardStateController.isUnlocked()).thenReturn(true) @@ -187,7 +184,7 @@ class ControlsComponentTest : SysuiTestCase() { lockPatternUtils, keyguardStateController, userTracker, - secureSettings, + controlsSettingsRepository, optionalControlsTileResourceConfiguration ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java index 7a2ba95f74a0..06a944e7a6d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java @@ -361,8 +361,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { assertThat(lp.getMarginEnd()).isEqualTo(margin); }); - // The third view should be at the top end corner. No margin should be applied if not - // specified. + // The third view should be at the top end corner. No margin should be applied. verifyChange(thirdViewInfo, true, lp -> { assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); @@ -442,65 +441,129 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { } /** - * Ensures the root complication applies margin if specified. + * Ensures layout sets correct max width constraint. */ @Test - public void testRootComplicationSpecifiedMargin() { - final int defaultMargin = 5; - final int complicationMargin = 10; + public void testWidthConstraint() { + final int maxWidth = 20; final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, defaultMargin, mTouchSession, 0, 0); + new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); - final ViewInfo firstViewInfo = new ViewInfo( + final ViewInfo viewStartDirection = new ViewInfo( new ComplicationLayoutParams( 100, 100, ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_END, - ComplicationLayoutParams.DIRECTION_DOWN, - 0), + ComplicationLayoutParams.DIRECTION_START, + 0, + 5, + maxWidth), + Complication.CATEGORY_STANDARD, + mLayout); + final ViewInfo viewEndDirection = new ViewInfo( + new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP + | ComplicationLayoutParams.POSITION_START, + ComplicationLayoutParams.DIRECTION_END, + 0, + 5, + maxWidth), Complication.CATEGORY_STANDARD, mLayout); - addComplication(engine, firstViewInfo); + addComplication(engine, viewStartDirection); + addComplication(engine, viewEndDirection); - final ViewInfo secondViewInfo = new ViewInfo( + // Verify both horizontal direction views have max width set correctly, and max height is + // not set. + verifyChange(viewStartDirection, false, lp -> { + assertThat(lp.matchConstraintMaxWidth).isEqualTo(maxWidth); + assertThat(lp.matchConstraintMaxHeight).isEqualTo(0); + }); + verifyChange(viewEndDirection, false, lp -> { + assertThat(lp.matchConstraintMaxWidth).isEqualTo(maxWidth); + assertThat(lp.matchConstraintMaxHeight).isEqualTo(0); + }); + } + + /** + * Ensures layout sets correct max height constraint. + */ + @Test + public void testHeightConstraint() { + final int maxHeight = 20; + final ComplicationLayoutEngine engine = + new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + + final ViewInfo viewUpDirection = new ViewInfo( + new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_BOTTOM + | ComplicationLayoutParams.POSITION_END, + ComplicationLayoutParams.DIRECTION_UP, + 0, + 5, + maxHeight), + Complication.CATEGORY_STANDARD, + mLayout); + final ViewInfo viewDownDirection = new ViewInfo( new ComplicationLayoutParams( 100, 100, ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_END, - ComplicationLayoutParams.DIRECTION_START, - 0), - Complication.CATEGORY_SYSTEM, + ComplicationLayoutParams.DIRECTION_DOWN, + 0, + 5, + maxHeight), + Complication.CATEGORY_STANDARD, mLayout); - addComplication(engine, secondViewInfo); + addComplication(engine, viewUpDirection); + addComplication(engine, viewDownDirection); - firstViewInfo.clearInvocations(); - secondViewInfo.clearInvocations(); + // Verify both vertical direction views have max height set correctly, and max width is + // not set. + verifyChange(viewUpDirection, false, lp -> { + assertThat(lp.matchConstraintMaxHeight).isEqualTo(maxHeight); + assertThat(lp.matchConstraintMaxWidth).isEqualTo(0); + }); + verifyChange(viewDownDirection, false, lp -> { + assertThat(lp.matchConstraintMaxHeight).isEqualTo(maxHeight); + assertThat(lp.matchConstraintMaxWidth).isEqualTo(0); + }); + } - final ViewInfo thirdViewInfo = new ViewInfo( + /** + * Ensures layout does not set any constraint if not specified. + */ + @Test + public void testConstraintNotSetWhenNotSpecified() { + final ComplicationLayoutEngine engine = + new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + + final ViewInfo view = new ViewInfo( new ComplicationLayoutParams( 100, 100, ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_END, - ComplicationLayoutParams.DIRECTION_START, - 1, - complicationMargin), - Complication.CATEGORY_SYSTEM, + ComplicationLayoutParams.DIRECTION_DOWN, + 0, + 5), + Complication.CATEGORY_STANDARD, mLayout); - addComplication(engine, thirdViewInfo); + addComplication(engine, view); - // The third view is the root view and has specified margin, which should be applied based - // on its direction. - verifyChange(thirdViewInfo, true, lp -> { - assertThat(lp.getMarginStart()).isEqualTo(0); - assertThat(lp.getMarginEnd()).isEqualTo(complicationMargin); - assertThat(lp.topMargin).isEqualTo(0); - assertThat(lp.bottomMargin).isEqualTo(0); + // Verify neither max height nor max width set. + verifyChange(view, false, lp -> { + assertThat(lp.matchConstraintMaxHeight).isEqualTo(0); + assertThat(lp.matchConstraintMaxWidth).isEqualTo(0); }); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java index ce7561e95f1e..fdb4cc4480da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java @@ -97,35 +97,10 @@ public class ComplicationLayoutParamsTest extends SysuiTestCase { } /** - * Ensures ComplicationLayoutParams correctly returns whether the complication specified margin. - */ - @Test - public void testIsMarginSpecified() { - final ComplicationLayoutParams paramsNoMargin = new ComplicationLayoutParams( - 100, - 100, - ComplicationLayoutParams.POSITION_TOP - | ComplicationLayoutParams.POSITION_START, - ComplicationLayoutParams.DIRECTION_DOWN, - 0); - assertThat(paramsNoMargin.isMarginSpecified()).isFalse(); - - final ComplicationLayoutParams paramsWithMargin = new ComplicationLayoutParams( - 100, - 100, - ComplicationLayoutParams.POSITION_TOP - | ComplicationLayoutParams.POSITION_START, - ComplicationLayoutParams.DIRECTION_DOWN, - 0, - 20 /*margin*/); - assertThat(paramsWithMargin.isMarginSpecified()).isTrue(); - } - - /** * Ensures unspecified margin uses default. */ @Test - public void testUnspecifiedMarginUsesDefault() { + public void testDefaultMargin() { final ComplicationLayoutParams params = new ComplicationLayoutParams( 100, 100, @@ -161,13 +136,15 @@ public class ComplicationLayoutParamsTest extends SysuiTestCase { ComplicationLayoutParams.POSITION_TOP, ComplicationLayoutParams.DIRECTION_DOWN, 3, - 10); + 10, + 20); final ComplicationLayoutParams copy = new ComplicationLayoutParams(params); assertThat(copy.getDirection() == params.getDirection()).isTrue(); assertThat(copy.getPosition() == params.getPosition()).isTrue(); assertThat(copy.getWeight() == params.getWeight()).isTrue(); assertThat(copy.getMargin(0) == params.getMargin(1)).isTrue(); + assertThat(copy.getConstraint() == params.getConstraint()).isTrue(); assertThat(copy.height == params.height).isTrue(); assertThat(copy.width == params.width).isTrue(); } @@ -193,4 +170,31 @@ public class ComplicationLayoutParamsTest extends SysuiTestCase { assertThat(copy.height == params.height).isTrue(); assertThat(copy.width == params.width).isTrue(); } + + /** + * Ensures that constraint is set correctly. + */ + @Test + public void testConstraint() { + final ComplicationLayoutParams paramsWithoutConstraint = new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP, + ComplicationLayoutParams.DIRECTION_DOWN, + 3, + 10); + assertThat(paramsWithoutConstraint.constraintSpecified()).isFalse(); + + final int constraint = 10; + final ComplicationLayoutParams paramsWithConstraint = new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP, + ComplicationLayoutParams.DIRECTION_DOWN, + 3, + 10, + constraint); + assertThat(paramsWithConstraint.constraintSpecified()).isTrue(); + assertThat(paramsWithConstraint.getConstraint()).isEqualTo(constraint); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java index 30ad485d7ac3..e6d3a69593cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java @@ -35,6 +35,7 @@ import android.widget.ImageView; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.controls.ControlsServiceInfo; import com.android.systemui.controls.controller.ControlsController; @@ -84,7 +85,10 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor; @Mock - private ImageView mView; + private View mView; + + @Mock + private ImageView mHomeControlsView; @Mock private ActivityStarter mActivityStarter; @@ -105,6 +109,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { when(mControlsComponent.getControlsListingController()).thenReturn( Optional.of(mControlsListingController)); when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE); + when(mView.findViewById(R.id.home_controls_chip)).thenReturn(mHomeControlsView); } @Test @@ -206,9 +211,9 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { final ArgumentCaptor<View.OnClickListener> clickListenerCaptor = ArgumentCaptor.forClass(View.OnClickListener.class); - verify(mView).setOnClickListener(clickListenerCaptor.capture()); + verify(mHomeControlsView).setOnClickListener(clickListenerCaptor.capture()); - clickListenerCaptor.getValue().onClick(mView); + clickListenerCaptor.getValue().onClick(mHomeControlsView); verify(mUiEventLogger).log( DreamHomeControlsComplication.DreamHomeControlsChipViewController .DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED); 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 cedde58746d2..cef452b8ec22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard import android.content.ContentValues import android.content.pm.PackageManager import android.content.pm.ProviderInfo +import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SystemUIAppComponentFactoryBase @@ -27,8 +28,10 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -36,8 +39,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceIn 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 @@ -74,8 +77,8 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() { underTest = KeyguardQuickAffordanceProvider() val scope = CoroutineScope(IMMEDIATE) - val selectionManager = - KeyguardQuickAffordanceSelectionManager( + val localUserSelectionManager = + KeyguardQuickAffordanceLocalUserSelectionManager( context = context, userFileManager = mock<UserFileManager>().apply { @@ -89,12 +92,22 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, + ) + val remoteUserSelectionManager = + KeyguardQuickAffordanceRemoteUserSelectionManager( + scope = scope, + userTracker = userTracker, + clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker), + userHandle = UserHandle.SYSTEM, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( appContext = context, scope = scope, - selectionManager = selectionManager, + localUserSelectionManager = localUserSelectionManager, + remoteUserSelectionManager = remoteUserSelectionManager, + userTracker = userTracker, configs = setOf( FakeKeyguardQuickAffordanceConfig( @@ -113,9 +126,10 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() { scope = scope, backgroundDispatcher = IMMEDIATE, secureSettings = FakeSettings(), - selectionsManager = selectionManager, + selectionsManager = localUserSelectionManager, ), dumpManager = mock(), + userHandle = UserHandle.SYSTEM, ) underTest.interactor = KeyguardQuickAffordanceInteractor( 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 623becf166d3..7205f3068abb 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 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() { @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/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt index cda701819d60..9fa7db127e1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.statusbar.policy.FlashlightController import com.android.systemui.utils.leaks.FakeFlashlightController import com.android.systemui.utils.leaks.LeakCheckedTest +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -38,156 +39,177 @@ import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() { @Mock private lateinit var context: Context private lateinit var flashlightController: FakeFlashlightController - private lateinit var underTest : FlashlightQuickAffordanceConfig + private lateinit var underTest: FlashlightQuickAffordanceConfig @Before fun setUp() { injectLeakCheckedDependency(FlashlightController::class.java) MockitoAnnotations.initMocks(this) - flashlightController = SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) as FakeFlashlightController + flashlightController = + SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) + as FakeFlashlightController underTest = FlashlightQuickAffordanceConfig(context, flashlightController) } @Test fun `flashlight is off -- triggered -- icon is on and active`() = runTest { - //given + // given flashlightController.isEnabled = false flashlightController.isAvailable = true val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when underTest.onTriggered(null) val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertEquals(R.drawable.ic_flashlight_on, - ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + assertEquals( + R.drawable.qs_flashlight_icon_on, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon + as? Icon.Resource) + ?.res + ) job.cancel() } @Test fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest { - //given + // given flashlightController.isEnabled = true flashlightController.isAvailable = true val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when underTest.onTriggered(null) val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertEquals(R.drawable.ic_flashlight_off, - ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + assertEquals( + R.drawable.qs_flashlight_icon_off, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon + as? Icon.Resource) + ?.res + ) job.cancel() } @Test fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest { - //given + // given flashlightController.isEnabled = true flashlightController.isAvailable = false val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when flashlightController.onFlashlightError() val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertEquals(R.drawable.ic_flashlight_off, - ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + assertEquals( + R.drawable.qs_flashlight_icon_off, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon + as? Icon.Resource) + ?.res + ) job.cancel() } @Test fun `flashlight availability now off -- hidden`() = runTest { - //given + // given flashlightController.isEnabled = true flashlightController.isAvailable = false val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when flashlightController.onFlashlightAvailabilityChanged(false) val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden) job.cancel() } @Test fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest { - //given + // given flashlightController.isEnabled = true flashlightController.isAvailable = false val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when flashlightController.onFlashlightAvailabilityChanged(true) val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Active) - assertEquals(R.drawable.ic_flashlight_on, (lastValue.icon as? Icon.Resource)?.res) + assertTrue( + (lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState + is ActivationState.Active + ) + assertEquals(R.drawable.qs_flashlight_icon_on, (lastValue.icon as? Icon.Resource)?.res) job.cancel() } @Test fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest { - //given + // given flashlightController.isEnabled = false flashlightController.isAvailable = false val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() - val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) } - //when + // when flashlightController.onFlashlightAvailabilityChanged(true) val lastValue = values.last() - //then + // then assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) - assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Inactive) - assertEquals(R.drawable.ic_flashlight_off, (lastValue.icon as? Icon.Resource)?.res) + assertTrue( + (lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState + is ActivationState.Inactive + ) + assertEquals(R.drawable.qs_flashlight_icon_off, (lastValue.icon as? Icon.Resource)?.res) job.cancel() } @Test fun `flashlight available -- picker state default`() = runTest { - //given + // given flashlightController.isAvailable = true - //when + // when val result = underTest.getPickerScreenState() - //then + // then assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default) } @Test fun `flashlight not available -- picker state unavailable`() = runTest { - //given + // given flashlightController.isAvailable = false - //when + // when val result = underTest.getPickerScreenState() - //then + // then assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) } -}
\ 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 8ef921eaa50a..3b0169d77063 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 @@ -57,7 +57,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { private lateinit var testScope: TestScope private lateinit var testDispatcher: TestDispatcher - private lateinit var selectionManager: KeyguardQuickAffordanceSelectionManager + private lateinit var selectionManager: KeyguardQuickAffordanceLocalUserSelectionManager private lateinit var settings: FakeSettings @Before @@ -75,7 +75,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { testDispatcher = UnconfinedTestDispatcher() testScope = TestScope(testDispatcher) selectionManager = - KeyguardQuickAffordanceSelectionManager( + KeyguardQuickAffordanceLocalUserSelectionManager( context = context, userFileManager = mock { @@ -89,6 +89,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { .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/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt index d8ee9f113d33..67091a9f40c7 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/KeyguardQuickAffordanceLocalUserSelectionManagerTest.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.settings.UserFileManager 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,15 +44,19 @@ import org.junit.runners.JUnit4 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() { +class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() { @Mock private lateinit var userFileManager: UserFileManager - private lateinit var underTest: KeyguardQuickAffordanceSelectionManager + private lateinit var underTest: KeyguardQuickAffordanceLocalUserSelectionManager private lateinit var userTracker: FakeUserTracker private lateinit var sharedPrefs: MutableMap<Int, SharedPreferences> @@ -60,15 +70,23 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { sharedPrefs.getOrPut(userId) { FakeSharedPreferences() } } userTracker = FakeUserTracker() + val dispatcher = UnconfinedTestDispatcher() + Dispatchers.setMain(dispatcher) underTest = - KeyguardQuickAffordanceSelectionManager( + KeyguardQuickAffordanceLocalUserSelectionManager( 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 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { 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/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt new file mode 100644 index 000000000000..d7e9cf144f88 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt @@ -0,0 +1,219 @@ +/* + * 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.data.quickaffordance + +import android.content.pm.UserInfo +import android.os.UserHandle +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() { + + @Mock private lateinit var userHandle: UserHandle + + private lateinit var underTest: KeyguardQuickAffordanceRemoteUserSelectionManager + + private lateinit var clientFactory: FakeKeyguardQuickAffordanceProviderClientFactory + private lateinit var testScope: TestScope + private lateinit var testDispatcher: TestDispatcher + private lateinit var userTracker: FakeUserTracker + private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient + private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(userHandle.identifier).thenReturn(UserHandle.USER_SYSTEM) + whenever(userHandle.isSystem).thenReturn(true) + client1 = FakeKeyguardQuickAffordanceProviderClient() + client2 = FakeKeyguardQuickAffordanceProviderClient() + + userTracker = FakeUserTracker() + userTracker.set( + userInfos = + listOf( + UserInfo( + UserHandle.USER_SYSTEM, + "Primary", + /* flags= */ 0, + ), + UserInfo( + OTHER_USER_ID_1, + "Secondary 1", + /* flags= */ 0, + ), + UserInfo( + OTHER_USER_ID_2, + "Secondary 2", + /* flags= */ 0, + ), + ), + selectedUserIndex = 0, + ) + + clientFactory = + FakeKeyguardQuickAffordanceProviderClientFactory( + userTracker, + ) { selectedUserId -> + when (selectedUserId) { + OTHER_USER_ID_1 -> client1 + OTHER_USER_ID_2 -> client2 + else -> error("No client set-up for user $selectedUserId!") + } + } + + testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) + + underTest = + KeyguardQuickAffordanceRemoteUserSelectionManager( + scope = testScope.backgroundScope, + userTracker = userTracker, + clientFactory = clientFactory, + userHandle = userHandle, + ) + } + + @Test + fun `selections - primary user process`() = + testScope.runTest { + val values = mutableListOf<Map<String, List<String>>>() + val job = launch { underTest.selections.toList(values) } + + runCurrent() + assertThat(values.last()).isEmpty() + + client1.insertSelection( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1, + ) + client2.insertSelection( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2, + ) + + userTracker.set( + userInfos = userTracker.userProfiles, + selectedUserIndex = 1, + ) + runCurrent() + assertThat(values.last()) + .isEqualTo( + mapOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to + listOf( + FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1, + ), + ) + ) + + userTracker.set( + userInfos = userTracker.userProfiles, + selectedUserIndex = 2, + ) + runCurrent() + assertThat(values.last()) + .isEqualTo( + mapOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to + listOf( + FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2, + ), + ) + ) + + job.cancel() + } + + @Test + fun `selections - secondary user process - always empty`() = + testScope.runTest { + whenever(userHandle.isSystem).thenReturn(false) + val values = mutableListOf<Map<String, List<String>>>() + val job = launch { underTest.selections.toList(values) } + + runCurrent() + assertThat(values.last()).isEmpty() + + client1.insertSelection( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1, + ) + userTracker.set( + userInfos = userTracker.userProfiles, + selectedUserIndex = 1, + ) + runCurrent() + assertThat(values.last()).isEmpty() + + job.cancel() + } + + @Test + fun setSelections() = + testScope.runTest { + userTracker.set( + userInfos = userTracker.userProfiles, + selectedUserIndex = 1, + ) + runCurrent() + + underTest.setSelections( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + affordanceIds = listOf(FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1), + ) + runCurrent() + + assertThat(underTest.getSelections()) + .isEqualTo( + mapOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to + listOf( + FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1, + ), + ) + ) + } + + companion object { + private const val OTHER_USER_ID_1 = UserHandle.MIN_SECONDARY_USER_ID + 1 + private const val OTHER_USER_ID_2 = UserHandle.MIN_SECONDARY_USER_ID + 2 + } +} 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 5c75417c3473..c40488adf029 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 @@ -17,17 +17,23 @@ package com.android.systemui.keyguard.data.repository +import android.content.pm.UserInfo +import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -39,6 +45,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -55,14 +62,24 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { private lateinit var config1: FakeKeyguardQuickAffordanceConfig private lateinit var config2: FakeKeyguardQuickAffordanceConfig + private lateinit var userTracker: FakeUserTracker + private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient + private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient @Before fun setUp() { - config1 = FakeKeyguardQuickAffordanceConfig("built_in:1") - config2 = FakeKeyguardQuickAffordanceConfig("built_in:2") + config1 = + FakeKeyguardQuickAffordanceConfig( + FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1 + ) + config2 = + FakeKeyguardQuickAffordanceConfig( + FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2 + ) val scope = CoroutineScope(IMMEDIATE) - val selectionManager = - KeyguardQuickAffordanceSelectionManager( + userTracker = FakeUserTracker() + val localUserSelectionManager = + KeyguardQuickAffordanceLocalUserSelectionManager( context = context, userFileManager = mock<UserFileManager>().apply { @@ -75,23 +92,45 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { ) .thenReturn(FakeSharedPreferences()) }, - userTracker = FakeUserTracker(), + userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, + ) + client1 = FakeKeyguardQuickAffordanceProviderClient() + client2 = FakeKeyguardQuickAffordanceProviderClient() + val remoteUserSelectionManager = + KeyguardQuickAffordanceRemoteUserSelectionManager( + scope = scope, + userTracker = userTracker, + clientFactory = + FakeKeyguardQuickAffordanceProviderClientFactory( + userTracker, + ) { selectedUserId -> + when (selectedUserId) { + SECONDARY_USER_1 -> client1 + SECONDARY_USER_2 -> client2 + else -> error("No set-up client for user $selectedUserId!") + } + }, + userHandle = UserHandle.SYSTEM, ) underTest = KeyguardQuickAffordanceRepository( appContext = context, scope = scope, - selectionManager = selectionManager, + localUserSelectionManager = localUserSelectionManager, + remoteUserSelectionManager = remoteUserSelectionManager, + userTracker = userTracker, legacySettingSyncer = KeyguardQuickAffordanceLegacySettingSyncer( scope = scope, backgroundDispatcher = IMMEDIATE, secureSettings = FakeSettings(), - selectionsManager = selectionManager, + selectionsManager = localUserSelectionManager, ), configs = setOf(config1, config2), dumpManager = mock(), + userHandle = UserHandle.SYSTEM, ) } @@ -186,7 +225,53 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { ) } - private suspend fun assertSelections( + @Test + fun `selections for secondary user`() = + runBlocking(IMMEDIATE) { + userTracker.set( + userInfos = + listOf( + UserInfo( + UserHandle.USER_SYSTEM, + "Primary", + /* flags= */ 0, + ), + UserInfo( + SECONDARY_USER_1, + "Secondary 1", + /* flags= */ 0, + ), + UserInfo( + SECONDARY_USER_2, + "Secondary 2", + /* flags= */ 0, + ), + ), + selectedUserIndex = 2, + ) + client2.insertSelection( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2, + ) + val observed = mutableListOf<Map<String, List<KeyguardQuickAffordanceConfig>>>() + val job = underTest.selections.onEach { observed.add(it) }.launchIn(this) + yield() + + assertSelections( + observed = observed.last(), + expected = + mapOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to + listOf( + config2, + ), + ) + ) + + job.cancel() + } + + private fun assertSelections( observed: Map<String, List<KeyguardQuickAffordanceConfig>>?, expected: Map<String, List<KeyguardQuickAffordanceConfig>>, ) { @@ -200,5 +285,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { companion object { private val IMMEDIATE = Dispatchers.Main.immediate + private const val SECONDARY_USER_1 = UserHandle.MIN_SECONDARY_USER_ID + 1 + private const val SECONDARY_USER_2 = UserHandle.MIN_SECONDARY_USER_ID + 2 } } 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 c2650ec455d8..1c1f0399bb06 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 @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Intent +import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase @@ -29,9 +30,11 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry @@ -237,8 +240,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { val qrCodeScanner = FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER) val scope = CoroutineScope(IMMEDIATE) - val selectionManager = - KeyguardQuickAffordanceSelectionManager( + val localUserSelectionManager = + KeyguardQuickAffordanceLocalUserSelectionManager( context = context, userFileManager = mock<UserFileManager>().apply { @@ -252,21 +255,32 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, + ) + val remoteUserSelectionManager = + KeyguardQuickAffordanceRemoteUserSelectionManager( + scope = scope, + userTracker = userTracker, + clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker), + userHandle = UserHandle.SYSTEM, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( appContext = context, scope = scope, - selectionManager = selectionManager, + localUserSelectionManager = localUserSelectionManager, + remoteUserSelectionManager = remoteUserSelectionManager, + userTracker = userTracker, legacySettingSyncer = KeyguardQuickAffordanceLegacySettingSyncer( scope = scope, backgroundDispatcher = IMMEDIATE, secureSettings = FakeSettings(), - selectionsManager = selectionManager, + selectionsManager = localUserSelectionManager, ), configs = setOf(homeControls, quickAccessWallet, qrCodeScanner), dumpManager = mock(), + userHandle = UserHandle.SYSTEM, ) underTest = KeyguardQuickAffordanceInteractor( 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 b79030602368..11fe905b1d1f 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 @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor +import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase @@ -26,9 +27,11 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel @@ -98,8 +101,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER) val scope = CoroutineScope(IMMEDIATE) - val selectionManager = - KeyguardQuickAffordanceSelectionManager( + val localUserSelectionManager = + KeyguardQuickAffordanceLocalUserSelectionManager( context = context, userFileManager = mock<UserFileManager>().apply { @@ -113,21 +116,32 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, + ) + val remoteUserSelectionManager = + KeyguardQuickAffordanceRemoteUserSelectionManager( + scope = scope, + userTracker = userTracker, + clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker), + userHandle = UserHandle.SYSTEM, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( appContext = context, scope = scope, - selectionManager = selectionManager, + localUserSelectionManager = localUserSelectionManager, + remoteUserSelectionManager = remoteUserSelectionManager, + userTracker = userTracker, legacySettingSyncer = KeyguardQuickAffordanceLegacySettingSyncer( scope = scope, backgroundDispatcher = IMMEDIATE, secureSettings = FakeSettings(), - selectionsManager = selectionManager, + selectionsManager = localUserSelectionManager, ), configs = setOf(homeControls, quickAccessWallet, qrCodeScanner), dumpManager = mock(), + userHandle = UserHandle.SYSTEM, ) featureFlags = FakeFeatureFlags().apply { 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 8b166bd89426..83a5d0e90c84 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 @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Intent +import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase @@ -27,9 +28,11 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig +import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor @@ -121,8 +124,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { whenever(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED) val scope = CoroutineScope(IMMEDIATE) - val selectionManager = - KeyguardQuickAffordanceSelectionManager( + val localUserSelectionManager = + KeyguardQuickAffordanceLocalUserSelectionManager( context = context, userFileManager = mock<UserFileManager>().apply { @@ -136,18 +139,28 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, + ) + val remoteUserSelectionManager = + KeyguardQuickAffordanceRemoteUserSelectionManager( + scope = scope, + userTracker = userTracker, + clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker), + userHandle = UserHandle.SYSTEM, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( appContext = context, scope = scope, - selectionManager = selectionManager, + localUserSelectionManager = localUserSelectionManager, + remoteUserSelectionManager = remoteUserSelectionManager, + userTracker = userTracker, legacySettingSyncer = KeyguardQuickAffordanceLegacySettingSyncer( scope = scope, backgroundDispatcher = IMMEDIATE, secureSettings = FakeSettings(), - selectionsManager = selectionManager, + selectionsManager = localUserSelectionManager, ), configs = setOf( @@ -156,6 +169,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { qrCodeScannerAffordanceConfig, ), dumpManager = mock(), + userHandle = UserHandle.SYSTEM, ) underTest = KeyguardBottomAreaViewModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt index 688c66ac80c9..2c8d7abd4f4a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt @@ -46,6 +46,109 @@ class TableLogBufferTest : SysuiTestCase() { } @Test + fun dumpChanges_hasHeader() { + val dumpedString = dumpChanges() + + assertThat(logLines(dumpedString)[0]).isEqualTo(HEADER_PREFIX + NAME) + } + + @Test + fun dumpChanges_hasVersion() { + val dumpedString = dumpChanges() + + assertThat(logLines(dumpedString)[1]).isEqualTo("version $VERSION") + } + + @Test + fun dumpChanges_hasFooter() { + val dumpedString = dumpChanges() + + assertThat(logLines(dumpedString).last()).isEqualTo(FOOTER_PREFIX + NAME) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_str_separatorNotAllowedInPrefix() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("columnName", "stringValue") + } + } + underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_bool_separatorNotAllowedInPrefix() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("columnName", true) + } + } + underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_int_separatorNotAllowedInPrefix() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("columnName", 567) + } + } + underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_str_separatorNotAllowedInColumnName() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("column${SEPARATOR}Name", "stringValue") + } + } + underTest.logDiffs("prefix", TestDiffable(), next) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_bool_separatorNotAllowedInColumnName() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("column${SEPARATOR}Name", true) + } + } + underTest.logDiffs("prefix", TestDiffable(), next) + } + + @Test(expected = IllegalArgumentException::class) + fun dumpChanges_int_separatorNotAllowedInColumnName() { + val next = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("column${SEPARATOR}Name", 456) + } + } + underTest.logDiffs("prefix", TestDiffable(), next) + } + + @Test + fun logChange_bool_dumpsCorrectly() { + systemClock.setCurrentTimeMillis(4000L) + + underTest.logChange("prefix", "columnName", true) + + val dumpedString = dumpChanges() + val expected = + TABLE_LOG_DATE_FORMAT.format(4000L) + + SEPARATOR + + "prefix.columnName" + + SEPARATOR + + "true" + assertThat(dumpedString).contains(expected) + } + + @Test fun dumpChanges_strChange_logsFromNext() { systemClock.setCurrentTimeMillis(100L) @@ -66,11 +169,14 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() - assertThat(dumpedString).contains("prefix") - assertThat(dumpedString).contains("stringValChange") - assertThat(dumpedString).contains("newStringVal") + val expected = + TABLE_LOG_DATE_FORMAT.format(100L) + + SEPARATOR + + "prefix.stringValChange" + + SEPARATOR + + "newStringVal" + assertThat(dumpedString).contains(expected) assertThat(dumpedString).doesNotContain("prevStringVal") - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) } @Test @@ -94,11 +200,14 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() - assertThat(dumpedString).contains("prefix") - assertThat(dumpedString).contains("booleanValChange") - assertThat(dumpedString).contains("true") + val expected = + TABLE_LOG_DATE_FORMAT.format(100L) + + SEPARATOR + + "prefix.booleanValChange" + + SEPARATOR + + "true" + assertThat(dumpedString).contains(expected) assertThat(dumpedString).doesNotContain("false") - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) } @Test @@ -122,11 +231,14 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() - assertThat(dumpedString).contains("prefix") - assertThat(dumpedString).contains("intValChange") - assertThat(dumpedString).contains("67890") + val expected = + TABLE_LOG_DATE_FORMAT.format(100L) + + SEPARATOR + + "prefix.intValChange" + + SEPARATOR + + "67890" + assertThat(dumpedString).contains(expected) assertThat(dumpedString).doesNotContain("12345") - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) } @Test @@ -152,9 +264,9 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() // THEN the dump still works - assertThat(dumpedString).contains("booleanValChange") - assertThat(dumpedString).contains("true") - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(100L)) + val expected = + TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + "booleanValChange" + SEPARATOR + "true" + assertThat(dumpedString).contains(expected) } @Test @@ -186,15 +298,34 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() - assertThat(dumpedString).contains("valChange") - assertThat(dumpedString).contains("stateValue12") - assertThat(dumpedString).contains("stateValue20") - assertThat(dumpedString).contains("stateValue40") - assertThat(dumpedString).contains("stateValue45") - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(12000L)) - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(20000L)) - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(40000L)) - assertThat(dumpedString).contains(TABLE_LOG_DATE_FORMAT.format(45000L)) + val expected1 = + TABLE_LOG_DATE_FORMAT.format(12000L) + + SEPARATOR + + "valChange" + + SEPARATOR + + "stateValue12" + val expected2 = + TABLE_LOG_DATE_FORMAT.format(20000L) + + SEPARATOR + + "valChange" + + SEPARATOR + + "stateValue20" + val expected3 = + TABLE_LOG_DATE_FORMAT.format(40000L) + + SEPARATOR + + "valChange" + + SEPARATOR + + "stateValue40" + val expected4 = + TABLE_LOG_DATE_FORMAT.format(45000L) + + SEPARATOR + + "valChange" + + SEPARATOR + + "stateValue45" + assertThat(dumpedString).contains(expected1) + assertThat(dumpedString).contains(expected2) + assertThat(dumpedString).contains(expected3) + assertThat(dumpedString).contains(expected4) } @Test @@ -214,10 +345,73 @@ class TableLogBufferTest : SysuiTestCase() { val dumpedString = dumpChanges() - assertThat(dumpedString).contains("status") - assertThat(dumpedString).contains("in progress") - assertThat(dumpedString).contains("connected") - assertThat(dumpedString).contains("false") + val timestamp = TABLE_LOG_DATE_FORMAT.format(100L) + val expected1 = timestamp + SEPARATOR + "status" + SEPARATOR + "in progress" + val expected2 = timestamp + SEPARATOR + "connected" + SEPARATOR + "false" + assertThat(dumpedString).contains(expected1) + assertThat(dumpedString).contains(expected2) + } + + @Test + fun logChange_rowInitializer_dumpsCorrectly() { + systemClock.setCurrentTimeMillis(100L) + + underTest.logChange("") { row -> + row.logChange("column1", "val1") + row.logChange("column2", 2) + row.logChange("column3", true) + } + + val dumpedString = dumpChanges() + + val timestamp = TABLE_LOG_DATE_FORMAT.format(100L) + val expected1 = timestamp + SEPARATOR + "column1" + SEPARATOR + "val1" + val expected2 = timestamp + SEPARATOR + "column2" + SEPARATOR + "2" + val expected3 = timestamp + SEPARATOR + "column3" + SEPARATOR + "true" + assertThat(dumpedString).contains(expected1) + assertThat(dumpedString).contains(expected2) + assertThat(dumpedString).contains(expected3) + } + + @Test + fun logChangeAndLogDiffs_bothLogged() { + systemClock.setCurrentTimeMillis(100L) + + underTest.logChange("") { row -> + row.logChange("column1", "val1") + row.logChange("column2", 2) + row.logChange("column3", true) + } + + systemClock.setCurrentTimeMillis(200L) + val prevDiffable = object : TestDiffable() {} + val nextDiffable = + object : TestDiffable() { + override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) { + row.logChange("column1", "newVal1") + row.logChange("column2", 222) + row.logChange("column3", false) + } + } + + underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable) + + val dumpedString = dumpChanges() + + val timestamp1 = TABLE_LOG_DATE_FORMAT.format(100L) + val expected1 = timestamp1 + SEPARATOR + "column1" + SEPARATOR + "val1" + val expected2 = timestamp1 + SEPARATOR + "column2" + SEPARATOR + "2" + val expected3 = timestamp1 + SEPARATOR + "column3" + SEPARATOR + "true" + val timestamp2 = TABLE_LOG_DATE_FORMAT.format(200L) + val expected4 = timestamp2 + SEPARATOR + "column1" + SEPARATOR + "newVal1" + val expected5 = timestamp2 + SEPARATOR + "column2" + SEPARATOR + "222" + val expected6 = timestamp2 + SEPARATOR + "column3" + SEPARATOR + "false" + assertThat(dumpedString).contains(expected1) + assertThat(dumpedString).contains(expected2) + assertThat(dumpedString).contains(expected3) + assertThat(dumpedString).contains(expected4) + assertThat(dumpedString).contains(expected5) + assertThat(dumpedString).contains(expected6) } @Test @@ -247,14 +441,24 @@ class TableLogBufferTest : SysuiTestCase() { } private fun dumpChanges(): String { - underTest.dumpChanges(PrintWriter(outputWriter)) + underTest.dump(PrintWriter(outputWriter), arrayOf()) return outputWriter.toString() } - private abstract class TestDiffable : Diffable<TestDiffable> { + private fun logLines(string: String): List<String> { + return string.split("\n").filter { it.isNotBlank() } + } + + private open class TestDiffable : Diffable<TestDiffable> { override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {} } } private const val NAME = "TestTableBuffer" private const val MAX_SIZE = 10 + +// Copying these here from [TableLogBuffer] so that we catch any accidental versioning change +private const val HEADER_PREFIX = "SystemUI StateChangeTableSection START: " +private const val FOOTER_PREFIX = "SystemUI StateChangeTableSection END: " +private const val SEPARATOR = "|" // TBD +private const val VERSION = "1" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt index 84fdfd78e9fc..136ace173795 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT +import com.android.systemui.settings.UserTracker import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -79,6 +80,7 @@ private fun <T> any(): T = Mockito.any<T>() class MediaResumeListenerTest : SysuiTestCase() { @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var device: MediaDeviceData @Mock private lateinit var token: MediaSession.Token @@ -131,12 +133,15 @@ class MediaResumeListenerTest : SysuiTestCase() { whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor) whenever(mockContext.packageManager).thenReturn(context.packageManager) whenever(mockContext.contentResolver).thenReturn(context.contentResolver) + whenever(mockContext.userId).thenReturn(context.userId) executor = FakeExecutor(clock) resumeListener = MediaResumeListener( mockContext, broadcastDispatcher, + userTracker, + executor, executor, tunerService, resumeBrowserFactory, @@ -177,6 +182,8 @@ class MediaResumeListenerTest : SysuiTestCase() { MediaResumeListener( context, broadcastDispatcher, + userTracker, + executor, executor, tunerService, resumeBrowserFactory, @@ -185,7 +192,7 @@ class MediaResumeListenerTest : SysuiTestCase() { ) listener.setManager(mediaDataManager) verify(broadcastDispatcher, never()) - .registerReceiver(eq(listener.userChangeReceiver), any(), any(), any(), anyInt(), any()) + .registerReceiver(eq(listener.userUnlockReceiver), any(), any(), any(), anyInt(), any()) // When data is loaded, we do NOT execute or update anything listener.onMediaDataLoaded(KEY, OLD_KEY, data) @@ -289,7 +296,7 @@ class MediaResumeListenerTest : SysuiTestCase() { resumeListener.setManager(mediaDataManager) verify(broadcastDispatcher) .registerReceiver( - eq(resumeListener.userChangeReceiver), + eq(resumeListener.userUnlockReceiver), any(), any(), any(), @@ -299,7 +306,8 @@ class MediaResumeListenerTest : SysuiTestCase() { // When we get an unlock event val intent = Intent(Intent.ACTION_USER_UNLOCKED) - resumeListener.userChangeReceiver.onReceive(context, intent) + intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId) + resumeListener.userUnlockReceiver.onReceive(context, intent) // Then we should attempt to find recent media for each saved component verify(resumeBrowser, times(3)).findRecentMedia() @@ -375,6 +383,8 @@ class MediaResumeListenerTest : SysuiTestCase() { MediaResumeListener( mockContext, broadcastDispatcher, + userTracker, + executor, executor, tunerService, resumeBrowserFactory, @@ -386,7 +396,8 @@ class MediaResumeListenerTest : SysuiTestCase() { // When we load a component that was played recently val intent = Intent(Intent.ACTION_USER_UNLOCKED) - resumeListener.userChangeReceiver.onReceive(mockContext, intent) + intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId) + resumeListener.userUnlockReceiver.onReceive(mockContext, intent) // We add its resume controls verify(resumeBrowser, times(1)).findRecentMedia() @@ -404,6 +415,8 @@ class MediaResumeListenerTest : SysuiTestCase() { MediaResumeListener( mockContext, broadcastDispatcher, + userTracker, + executor, executor, tunerService, resumeBrowserFactory, @@ -415,7 +428,8 @@ class MediaResumeListenerTest : SysuiTestCase() { // When we load a component that is not recent val intent = Intent(Intent.ACTION_USER_UNLOCKED) - resumeListener.userChangeReceiver.onReceive(mockContext, intent) + intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId) + resumeListener.userUnlockReceiver.onReceive(mockContext, intent) // We do not try to add resume controls verify(resumeBrowser, times(0)).findRecentMedia() @@ -443,6 +457,8 @@ class MediaResumeListenerTest : SysuiTestCase() { MediaResumeListener( mockContext, broadcastDispatcher, + userTracker, + executor, executor, tunerService, resumeBrowserFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index f43a34f6e89b..80adbf025e0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -44,14 +44,11 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.IntentFilter; import android.content.res.Resources; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; import android.os.SystemClock; -import android.os.UserHandle; import android.provider.DeviceConfig; import android.telecom.TelecomManager; import android.testing.AndroidTestingRunner; @@ -79,7 +76,6 @@ import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; @@ -119,6 +115,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Optional; +import java.util.concurrent.Executor; @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @@ -166,7 +163,7 @@ public class NavigationBarTest extends SysuiTestCase { @Mock private Handler mHandler; @Mock - private BroadcastDispatcher mBroadcastDispatcher; + private UserTracker mUserTracker; @Mock private UiEventLogger mUiEventLogger; @Mock @@ -315,14 +312,10 @@ public class NavigationBarTest extends SysuiTestCase { } @Test - public void testRegisteredWithDispatcher() { + public void testRegisteredWithUserTracker() { mNavigationBar.init(); mNavigationBar.onViewAttached(); - verify(mBroadcastDispatcher).registerReceiverWithHandler( - any(BroadcastReceiver.class), - any(IntentFilter.class), - any(Handler.class), - any(UserHandle.class)); + verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class)); } @Test @@ -463,7 +456,7 @@ public class NavigationBarTest extends SysuiTestCase { mStatusBarStateController, mStatusBarKeyguardViewManager, mMockSysUiState, - mBroadcastDispatcher, + mUserTracker, mCommandQueue, Optional.of(mock(Pip.class)), Optional.of(mock(Recents.class)), diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index c377c374148f..338182a3e304 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -48,6 +48,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.power.PowerUI.WarningsUI; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -85,6 +86,7 @@ public class PowerUITest extends SysuiTestCase { private PowerUI mPowerUI; @Mock private EnhancedEstimates mEnhancedEstimates; @Mock private PowerManager mPowerManager; + @Mock private UserTracker mUserTracker; @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private IThermalService mThermalServiceMock; private IThermalEventListener mUsbThermalEventListener; @@ -682,7 +684,8 @@ public class PowerUITest extends SysuiTestCase { private void createPowerUi() { mPowerUI = new PowerUI( mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy, - mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager); + mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager, + mUserTracker); mPowerUI.mThermalService = mThermalServiceMock; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt index 645b1cde632f..23466cc20f44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestCoroutineScheduler import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -55,7 +56,7 @@ class FooterActionsInteractorTest : SysuiTestCase() { @Before fun setUp() { - utils = FooterActionsTestUtils(context, TestableLooper.get(this)) + utils = FooterActionsTestUtils(context, TestableLooper.get(this), TestCoroutineScheduler()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 081a2181cfe5..47afa70fa84b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.qs.FakeFgsManagerController import com.android.systemui.qs.QSSecurityFooterUtils import com.android.systemui.qs.footer.FooterActionsTestUtils @@ -44,12 +45,9 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Before @@ -62,16 +60,20 @@ import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @RunWithLooper class FooterActionsViewModelTest : SysuiTestCase() { + private val testScope = TestScope() private lateinit var utils: FooterActionsTestUtils - private val testDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler()) @Before fun setUp() { - utils = FooterActionsTestUtils(context, TestableLooper.get(this)) + utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler) + } + + private fun runTest(block: suspend TestScope.() -> Unit) { + testScope.runTest(testBody = block) } @Test - fun settingsButton() = runBlockingTest { + fun settingsButton() = runTest { val underTest = utils.footerActionsViewModel(showPowerButton = false) val settings = underTest.settings @@ -87,7 +89,7 @@ class FooterActionsViewModelTest : SysuiTestCase() { } @Test - fun powerButton() = runBlockingTest { + fun powerButton() = runTest { // Without power button. val underTestWithoutPower = utils.footerActionsViewModel(showPowerButton = false) assertThat(underTestWithoutPower.power).isNull() @@ -114,7 +116,7 @@ class FooterActionsViewModelTest : SysuiTestCase() { } @Test - fun userSwitcher() = runBlockingTest { + fun userSwitcher() = runTest { val picture: Drawable = mock() val userInfoController = FakeUserInfoController(FakeInfo(picture = picture)) val settings = FakeSettings() @@ -135,7 +137,6 @@ class FooterActionsViewModelTest : SysuiTestCase() { showPowerButton = false, footerActionsInteractor = utils.footerActionsInteractor( - bgDispatcher = testDispatcher, userSwitcherRepository = utils.userSwitcherRepository( userTracker = userTracker, @@ -143,22 +144,12 @@ class FooterActionsViewModelTest : SysuiTestCase() { userManager = userManager, userInfoController = userInfoController, userSwitcherController = userSwitcherControllerWrapper.controller, - bgDispatcher = testDispatcher, ), ) ) // Collect the user switcher into currentUserSwitcher. - var currentUserSwitcher: FooterActionsButtonViewModel? = null - val job = launch { underTest.userSwitcher.collect { currentUserSwitcher = it } } - fun currentUserSwitcher(): FooterActionsButtonViewModel? { - // Make sure we finish collecting the current user switcher. This is necessary because - // combined flows launch multiple coroutines in the current scope so we need to make - // sure we process all coroutines triggered by our flow collection before we make - // assertions on the current buttons. - advanceUntilIdle() - return currentUserSwitcher - } + val currentUserSwitcher = collectLastValue(underTest.userSwitcher) // The user switcher is disabled. assertThat(currentUserSwitcher()).isNull() @@ -203,12 +194,10 @@ class FooterActionsViewModelTest : SysuiTestCase() { // in guest mode. userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() } assertThat(iconTint()).isNull() - - job.cancel() } @Test - fun security() = runBlockingTest { + fun security() = runTest { val securityController = FakeSecurityController() val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>() @@ -224,22 +213,15 @@ class FooterActionsViewModelTest : SysuiTestCase() { footerActionsInteractor = utils.footerActionsInteractor( qsSecurityFooterUtils = qsSecurityFooterUtils, - bgDispatcher = testDispatcher, securityRepository = utils.securityRepository( securityController = securityController, - bgDispatcher = testDispatcher, ), ), ) // Collect the security model into currentSecurity. - var currentSecurity: FooterActionsSecurityButtonViewModel? = null - val job = launch { underTest.security.collect { currentSecurity = it } } - fun currentSecurity(): FooterActionsSecurityButtonViewModel? { - advanceUntilIdle() - return currentSecurity - } + val currentSecurity = collectLastValue(underTest.security) // By default, we always return a null SecurityButtonConfig. assertThat(currentSecurity()).isNull() @@ -270,12 +252,10 @@ class FooterActionsViewModelTest : SysuiTestCase() { security = currentSecurity() assertThat(security).isNotNull() assertThat(security!!.onClick).isNull() - - job.cancel() } @Test - fun foregroundServices() = runBlockingTest { + fun foregroundServices() = runTest { val securityController = FakeSecurityController() val fgsManagerController = FakeFgsManagerController( @@ -300,21 +280,14 @@ class FooterActionsViewModelTest : SysuiTestCase() { securityRepository = utils.securityRepository( securityController, - bgDispatcher = testDispatcher, ), foregroundServicesRepository = utils.foregroundServicesRepository(fgsManagerController), - bgDispatcher = testDispatcher, ), ) // Collect the security model into currentSecurity. - var currentForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null - val job = launch { underTest.foregroundServices.collect { currentForegroundServices = it } } - fun currentForegroundServices(): FooterActionsForegroundServicesButtonViewModel? { - advanceUntilIdle() - return currentForegroundServices - } + val currentForegroundServices = collectLastValue(underTest.foregroundServices) // We don't show the foreground services button if the number of running packages is not // > 1. @@ -356,12 +329,10 @@ class FooterActionsViewModelTest : SysuiTestCase() { } securityController.updateState {} assertThat(currentForegroundServices()?.displayText).isFalse() - - job.cancel() } @Test - fun observeDeviceMonitoringDialogRequests() = runBlockingTest { + fun observeDeviceMonitoringDialogRequests() = runTest { val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>() val broadcastDispatcher = mock<BroadcastDispatcher>() @@ -390,7 +361,6 @@ class FooterActionsViewModelTest : SysuiTestCase() { utils.footerActionsInteractor( qsSecurityFooterUtils = qsSecurityFooterUtils, broadcastDispatcher = broadcastDispatcher, - bgDispatcher = testDispatcher, ), ) @@ -415,7 +385,4 @@ class FooterActionsViewModelTest : SysuiTestCase() { underTest.onVisibilityChangeRequested(visible = true) assertThat(underTest.isVisible.value).isTrue() } - - private fun runBlockingTest(block: suspend TestScope.() -> Unit) = - runTest(testDispatcher) { block() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index 013e58ed99d7..69f3e987ec1d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -33,6 +33,9 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -49,12 +52,16 @@ import org.mockito.MockitoAnnotations; */ public class RecordingControllerTest extends SysuiTestCase { + private FakeSystemClock mFakeSystemClock = new FakeSystemClock(); + private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @Mock private RecordingController.RecordingStateChangeCallback mCallback; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private UserContextProvider mUserContextProvider; + @Mock + private UserTracker mUserTracker; private RecordingController mController; @@ -63,7 +70,8 @@ public class RecordingControllerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mController = new RecordingController(mBroadcastDispatcher, mUserContextProvider); + mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, + mUserContextProvider, mUserTracker); mController.addCallback(mCallback); } @@ -176,9 +184,7 @@ public class RecordingControllerTest extends SysuiTestCase { mController.updateState(true); // and user is changed - Intent intent = new Intent(Intent.ACTION_USER_SWITCHED) - .putExtra(Intent.EXTRA_USER_HANDLE, USER_ID); - mController.mUserChangeReceiver.onReceive(mContext, intent); + mController.mUserChangedCallback.onUserChanged(USER_ID, mContext); // Ensure that the recording was stopped verify(mCallback).onRecordingEnd(); 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 6d9b01e28aa4..020a86611552 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 @@ class UserFileManagerImplTest : SysuiTestCase() { 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 @@ class UserFileManagerImplTest : SysuiTestCase() { @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 @@ class UserFileManagerImplTest : SysuiTestCase() { 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 @@ class UserFileManagerImplTest : SysuiTestCase() { @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/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 69a45599668b..b6f74f0a13ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -131,6 +131,7 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; +import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -383,7 +384,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mInteractionJankMonitor, mShadeExpansionStateManager), mKeyguardBypassController, mDozeParameters, - mScreenOffAnimationController); + mScreenOffAnimationController, + mock(NotificationWakeUpCoordinatorLogger.class)); mConfigurationController = new ConfigurationControllerImpl(mContext); PulseExpansionHandler expansionHandler = new PulseExpansionHandler( mContext, @@ -499,8 +501,18 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mDumpManager); mNotificationPanelViewController.initDependencies( mCentralSurfaces, + null, () -> {}, mNotificationShelfController); + mNotificationPanelViewController.setTrackingStartedListener(() -> {}); + mNotificationPanelViewController.setOpenCloseListener( + new NotificationPanelViewController.OpenCloseListener() { + @Override + public void onClosingFinished() {} + + @Override + public void onOpenStarted() {} + }); mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); @@ -831,7 +843,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() { when(mCommandQueue.panelsEnabled()).thenReturn(true); when(mView.isEnabled()).thenReturn(true); - MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0); + MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0); mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event); @@ -839,6 +851,17 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test + public void handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() { + when(mCommandQueue.panelsEnabled()).thenReturn(true); + when(mView.isEnabled()).thenReturn(true); + MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0); + + mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event); + + verify(mView, never()).dispatchTouchEvent(event); + } + + @Test public void testA11y_initializeNode() { AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo); 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 e1346ead3e7f..0000c32aa60d 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 @@ public class CommandQueueTest extends SysuiTestCase { } @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/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 15a687d2adc7..452606dfcca4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static android.content.Intent.ACTION_USER_SWITCHED; - import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -34,7 +32,6 @@ import android.app.Notification; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.os.Handler; @@ -293,11 +290,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test - public void testActionUserSwitchedCallsOnUserSwitched() { - Intent intent = new Intent() - .setAction(ACTION_USER_SWITCHED) - .putExtra(Intent.EXTRA_USER_HANDLE, mSecondaryUser.id); - mLockscreenUserManager.getBaseBroadcastReceiverForTest().onReceive(mContext, intent); + public void testUserSwitchedCallsOnUserSwitched() { + mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanged(mSecondaryUser.id, + mContext); verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id); } @@ -366,6 +361,10 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { return mBaseBroadcastReceiver; } + public UserTracker.Callback getUserTrackerCallbackForTest() { + return mUserChangedCallback; + } + public ContentObserver getLockscreenSettingsObserverForTest() { return mLockscreenSettingsObserver; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index 8b7b4dea155f..6bd3f7a27413 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -26,22 +26,17 @@ import static android.app.NotificationManager.IMPORTANCE_MIN; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry; -import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -54,10 +49,10 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.CoreStartable; import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -97,7 +92,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private SysuiStatusBarStateController mStatusBarStateController; - @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock private UserTracker mUserTracker; private final FakeSettings mFakeSettings = new FakeSettings(); private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; @@ -117,7 +112,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { mKeyguardUpdateMonitor, mHighPriorityProvider, mStatusBarStateController, - mBroadcastDispatcher, + mUserTracker, mFakeSettings, mFakeSettings); mKeyguardNotificationVisibilityProvider = component.getProvider(); @@ -205,23 +200,19 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { } @Test - public void notifyListeners_onReceiveUserSwitchBroadcast() { - ArgumentCaptor<BroadcastReceiver> callbackCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mBroadcastDispatcher).registerReceiver( + public void notifyListeners_onReceiveUserSwitchCallback() { + ArgumentCaptor<UserTracker.Callback> callbackCaptor = + ArgumentCaptor.forClass(UserTracker.Callback.class); + verify(mUserTracker).addCallback( callbackCaptor.capture(), - argThat(intentFilter -> intentFilter.hasAction(Intent.ACTION_USER_SWITCHED)), - isNull(), - isNull(), - eq(Context.RECEIVER_EXPORTED), - isNull()); - BroadcastReceiver callback = callbackCaptor.getValue(); + any()); + UserTracker.Callback callback = callbackCaptor.getValue(); Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); - callback.onReceive(mContext, new Intent(Intent.ACTION_USER_SWITCHED)); + callback.onUserChanged(CURR_USER_ID, mContext); verify(listener).accept(anyString()); } @@ -619,7 +610,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @BindsInstance KeyguardUpdateMonitor keyguardUpdateMonitor, @BindsInstance HighPriorityProvider highPriorityProvider, @BindsInstance SysuiStatusBarStateController statusBarStateController, - @BindsInstance BroadcastDispatcher broadcastDispatcher, + @BindsInstance UserTracker userTracker, @BindsInstance SecureSettings secureSettings, @BindsInstance GlobalSettings globalSettings ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index ea311da3e20b..21aae00f12ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.interruption; import static android.app.Notification.FLAG_BUBBLE; +import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.GROUP_ALERT_SUMMARY; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; @@ -33,6 +34,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -390,6 +393,127 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); } + private long makeWhenHoursAgo(long hoursAgo) { + return System.currentTimeMillis() - (1000 * 60 * 60 * hoursAgo); + } + + @Test + public void testShouldHeadsUp_oldWhen_flagDisabled() throws Exception { + ensureStateForHeadsUpWhenAwake(); + when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(false); + + NotificationEntry entry = createNotification(IMPORTANCE_HIGH); + entry.getSbn().getNotification().when = makeWhenHoursAgo(25); + + assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue(); + + verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong()); + verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any()); + } + + @Test + public void testShouldHeadsUp_oldWhen_whenNow() throws Exception { + ensureStateForHeadsUpWhenAwake(); + when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true); + + NotificationEntry entry = createNotification(IMPORTANCE_HIGH); + + assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue(); + + verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong()); + verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any()); + } + + @Test + public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception { + ensureStateForHeadsUpWhenAwake(); + when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true); + + NotificationEntry entry = createNotification(IMPORTANCE_HIGH); + entry.getSbn().getNotification().when = makeWhenHoursAgo(13); + + assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue(); + + verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong()); + verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any()); + } + + @Test + public void testShouldHeadsUp_oldWhen_whenZero() throws Exception { + ensureStateForHeadsUpWhenAwake(); + when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true); + + NotificationEntry entry = createNotification(IMPORTANCE_HIGH); + entry.getSbn().getNotification().when = 0L; + + assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue(); + + verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong()); + verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(0L), anyLong(), + eq("when <= 0")); + } + + @Test + public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception { + ensureStateForHeadsUpWhenAwake(); + when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true); + + NotificationEntry entry = createNotification(IMPORTANCE_HIGH); + entry.getSbn().getNotification().when = -1L; + + assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue(); + verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong()); + verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(-1L), anyLong(), + eq("when <= 0")); + } + + @Test + public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception { + ensureStateForHeadsUpWhenAwake(); + when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true); + long when = makeWhenHoursAgo(25); + + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silent= */ false); + entry.getSbn().getNotification().when = when; + + assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue(); + + verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong()); + verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(when), anyLong(), + eq("full-screen intent")); + } + + @Test + public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception { + ensureStateForHeadsUpWhenAwake(); + when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true); + long when = makeWhenHoursAgo(25); + + NotificationEntry entry = createFgsNotification(IMPORTANCE_HIGH); + entry.getSbn().getNotification().when = when; + + assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue(); + + verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong()); + verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(when), anyLong(), + eq("foreground service")); + } + + @Test + public void testShouldNotHeadsUp_oldWhen() throws Exception { + ensureStateForHeadsUpWhenAwake(); + when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true); + long when = makeWhenHoursAgo(25); + + NotificationEntry entry = createNotification(IMPORTANCE_HIGH); + entry.getSbn().getNotification().when = when; + + assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse(); + + verify(mLogger).logNoHeadsUpOldWhen(eq(entry), eq(when), anyLong()); + verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any()); + } + @Test public void testShouldNotFullScreen_notPendingIntent_withStrictFlag() throws Exception { when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true); @@ -763,6 +887,16 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { return createNotification(importance, n); } + private NotificationEntry createFgsNotification(int importance) { + Notification n = new Notification.Builder(getContext(), "a") + .setContentTitle("title") + .setContentText("content text") + .setFlag(FLAG_FOREGROUND_SERVICE, true) + .build(); + + return createNotification(importance, n); + } + private final NotificationInterruptSuppressor mSuppressAwakeHeadsUp = new NotificationInterruptSuppressor() { 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 ed2afe753a5e..915924f13197 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.SysuiTestCase; 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/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 496bf37ffaeb..088d1654d1f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -299,7 +299,8 @@ public class NotificationTestHelper { public ExpandableNotificationRow createBubble() throws Exception { Notification n = createNotification(false /* isGroupSummary */, - null /* groupKey */, makeBubbleMetadata(null)); + null /* groupKey */, + makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */)); n.flags |= FLAG_BUBBLE; ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE, mDefaultInflationFlags, IMPORTANCE_HIGH); @@ -332,7 +333,8 @@ public class NotificationTestHelper { public ExpandableNotificationRow createBubbleInGroup() throws Exception { Notification n = createNotification(false /* isGroupSummary */, - GROUP_KEY /* groupKey */, makeBubbleMetadata(null)); + GROUP_KEY /* groupKey */, + makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */)); n.flags |= FLAG_BUBBLE; ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE, mDefaultInflationFlags, IMPORTANCE_HIGH); @@ -348,7 +350,7 @@ public class NotificationTestHelper { * @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent} */ public NotificationEntry createBubble(@Nullable PendingIntent deleteIntent) { - return createBubble(makeBubbleMetadata(deleteIntent), USER_HANDLE); + return createBubble(makeBubbleMetadata(deleteIntent, false /* autoExpand */), USER_HANDLE); } /** @@ -357,7 +359,16 @@ public class NotificationTestHelper { * @param handle the user to associate with this bubble. */ public NotificationEntry createBubble(UserHandle handle) { - return createBubble(makeBubbleMetadata(null), handle); + return createBubble(makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */), + handle); + } + + /** + * Returns an {@link NotificationEntry} that should be shown as a auto-expanded bubble. + */ + public NotificationEntry createAutoExpandedBubble() { + return createBubble(makeBubbleMetadata(null /* deleteIntent */, true /* autoExpand */), + USER_HANDLE); } /** @@ -565,7 +576,7 @@ public class NotificationTestHelper { assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } - private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent) { + private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent, boolean autoExpand) { Intent target = new Intent(mContext, BubblesTestActivity.class); PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, PendingIntent.FLAG_MUTABLE); @@ -574,6 +585,7 @@ public class NotificationTestHelper { Icon.createWithResource(mContext, R.drawable.android)) .setDeleteIntent(deleteIntent) .setDesiredHeight(314) + .setAutoExpandBubble(autoExpand) .build(); } 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 d5bfe1f7c2ce..c17c5b097db3 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 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { 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 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { 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 013e7278753d..ed84e4268c90 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 @@ public class CentralSurfacesImplTest extends SysuiTestCase { 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 @@ public class CentralSurfacesImplTest extends SysuiTestCase { return mViewRootImpl; } }; + mCentralSurfaces.initShadeVisibilityListener(); when(mViewRootImpl.getOnBackInvokedDispatcher()) .thenReturn(mOnBackInvokedDispatcher); when(mKeyguardViewMediator.registerCentralSurfaces( @@ -807,7 +819,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true); mOnBackInvokedCallback.getValue().onBackInvoked(); - verify(mShadeController).animateCollapsePanels(); + verify(mShadeController).animateCollapseShade(); } @Test @@ -1030,7 +1042,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @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 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // 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 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // 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 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // 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 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // 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/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index de71e2c250c4..e4759057a59c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -1442,16 +1442,30 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testNotificationTransparency_followsTransitionToFullShade() { + mScrimController.setClipsQsScrim(true); + mScrimController.transitionTo(SHADE_LOCKED); mScrimController.setRawPanelExpansionFraction(1.0f); finishAnimationsImmediately(); + + assertScrimTinted(Map.of( + mScrimInFront, false, + mScrimBehind, true, + mNotificationsScrim, false + )); + float shadeLockedAlpha = mNotificationsScrim.getViewAlpha(); mScrimController.transitionTo(ScrimState.KEYGUARD); mScrimController.setRawPanelExpansionFraction(1.0f); finishAnimationsImmediately(); float keyguardAlpha = mNotificationsScrim.getViewAlpha(); - mScrimController.setClipsQsScrim(true); + assertScrimTinted(Map.of( + mScrimInFront, true, + mScrimBehind, true, + mNotificationsScrim, true + )); + float progress = 0.5f; float lsNotifProgress = 0.3f; mScrimController.setTransitionToFullShadeProgress(progress, lsNotifProgress); 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 bf5186b6324d..e467d9399059 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @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 ce54d784520c..cae414a3dc67 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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { 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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { 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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Then verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry())); - verify(mShadeController, atLeastOnce()).collapsePanel(); + verify(mShadeController, atLeastOnce()).collapseShade(); verify(mAssistManager).hideAssist(); @@ -357,7 +357,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // 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/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt index b7a6c0125cfa..d35ce76d7a9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt @@ -22,7 +22,7 @@ import android.os.UserHandle import android.provider.Settings.Global import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope @@ -45,7 +45,7 @@ class AirplaneModeRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: AirplaneModeRepositoryImpl - @Mock private lateinit var logger: ConnectivityPipelineLogger + @Mock private lateinit var logger: TableLogBuffer private lateinit var bgHandler: Handler private lateinit var scope: CoroutineScope private lateinit var settings: FakeSettings diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 2e527be1af89..034c618e55d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -95,6 +95,24 @@ class UserRepositoryImplTest : SysuiTestCase() { } @Test + fun userSwitcherSettings_isUserSwitcherEnabled_notInitialized() = runSelfCancelingTest { + underTest = create(this) + + var value: UserSwitcherSettingsModel? = null + underTest.userSwitcherSettings.onEach { value = it }.launchIn(this) + + assertUserSwitcherSettings( + model = value, + expectedSimpleUserSwitcher = false, + expectedAddUsersFromLockscreen = false, + expectedUserSwitcherEnabled = + context.resources.getBoolean( + com.android.internal.R.bool.config_showUserSwitcherByDefault + ), + ) + } + + @Test fun refreshUsers() = runSelfCancelingTest { underTest = create(this) val initialExpectedValue = 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 50d239d25607..5beb2b389349 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 @@ -761,7 +761,7 @@ class UserInteractorTest : SysuiTestCase() { } @Test - fun `users - secondary user - no guest user`() = + fun `users - secondary user - guest user can be switched to`() = runBlocking(IMMEDIATE) { val userInfos = createUserInfos(count = 3, includeGuest = true) userRepository.setUserInfos(userInfos) @@ -770,8 +770,8 @@ class UserInteractorTest : SysuiTestCase() { var res: List<UserModel>? = null val job = underTest.users.onEach { res = it }.launchIn(this) - assertThat(res?.size == 2).isTrue() - assertThat(res?.find { it.isGuest }).isNull() + assertThat(res?.size == 3).isTrue() + assertThat(res?.find { it.isGuest }).isNotNull() job.cancel() } @@ -813,7 +813,8 @@ class UserInteractorTest : SysuiTestCase() { 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/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt index 7df707789290..6bfc2f1a8b8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt @@ -51,15 +51,11 @@ class PairwiseFlowTest : SysuiTestCase() { ) } - @Test - fun notEnough() = runBlocking { - assertThatFlow(flowOf(1).pairwise()).emitsNothing() - } + @Test fun notEnough() = runBlocking { assertThatFlow(flowOf(1).pairwise()).emitsNothing() } @Test fun withInit() = runBlocking { - assertThatFlow(flowOf(2).pairwise(initialValue = 1)) - .emitsExactly(WithPrev(1, 2)) + assertThatFlow(flowOf(2).pairwise(initialValue = 1)).emitsExactly(WithPrev(1, 2)) } @Test @@ -68,25 +64,78 @@ class PairwiseFlowTest : SysuiTestCase() { } @Test - fun withStateFlow() = runBlocking(Dispatchers.Main.immediate) { - val state = MutableStateFlow(1) - val stop = MutableSharedFlow<Unit>() - - val stoppable = merge(state, stop) - .takeWhile { it is Int } - .filterIsInstance<Int>() + fun withTransform() = runBlocking { + assertThatFlow( + flowOf("val1", "val2", "val3").pairwiseBy { prev: String, next: String -> + "$prev|$next" + } + ) + .emitsExactly("val1|val2", "val2|val3") + } - val job1 = launch { - assertThatFlow(stoppable.pairwise()).emitsExactly(WithPrev(1, 2)) - } - state.value = 2 - val job2 = launch { assertThatFlow(stoppable.pairwise()).emitsNothing() } + @Test + fun withGetInit() = runBlocking { + var initRun = false + assertThatFlow( + flowOf("val1", "val2").pairwiseBy( + getInitialValue = { + initRun = true + "initial" + } + ) { prev: String, next: String -> "$prev|$next" } + ) + .emitsExactly("initial|val1", "val1|val2") + assertThat(initRun).isTrue() + } - stop.emit(Unit) + @Test + fun notEnoughWithGetInit() = runBlocking { + var initRun = false + assertThatFlow( + emptyFlow<String>().pairwiseBy( + getInitialValue = { + initRun = true + "initial" + } + ) { prev: String, next: String -> "$prev|$next" } + ) + .emitsNothing() + // Even though the flow will not emit anything, the initial value function should still get + // run. + assertThat(initRun).isTrue() + } - assertThatJob(job1).isCompleted() - assertThatJob(job2).isCompleted() + @Test + fun getInitNotRunWhenFlowNotCollected() = runBlocking { + var initRun = false + flowOf("val1", "val2").pairwiseBy( + getInitialValue = { + initRun = true + "initial" + } + ) { prev: String, next: String -> "$prev|$next" } + + // Since the flow isn't collected, ensure [initialValueFun] isn't run. + assertThat(initRun).isFalse() } + + @Test + fun withStateFlow() = + runBlocking(Dispatchers.Main.immediate) { + val state = MutableStateFlow(1) + val stop = MutableSharedFlow<Unit>() + + val stoppable = merge(state, stop).takeWhile { it is Int }.filterIsInstance<Int>() + + val job1 = launch { assertThatFlow(stoppable.pairwise()).emitsExactly(WithPrev(1, 2)) } + state.value = 2 + val job2 = launch { assertThatFlow(stoppable.pairwise()).emitsNothing() } + + stop.emit(Unit) + + assertThatJob(job1).isCompleted() + assertThatJob(job2).isCompleted() + } } @SmallTest @@ -94,18 +143,17 @@ class PairwiseFlowTest : SysuiTestCase() { class SetChangesFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { - assertThatFlow( - flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges() - ).emitsExactly( - SetChanges( - added = setOf(1, 2, 3), - removed = emptySet(), - ), - SetChanges( - added = setOf(4), - removed = setOf(1), - ), - ) + assertThatFlow(flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges()) + .emitsExactly( + SetChanges( + added = setOf(1, 2, 3), + removed = emptySet(), + ), + SetChanges( + added = setOf(4), + removed = setOf(1), + ), + ) } @Test @@ -147,14 +195,19 @@ class SetChangesFlowTest : SysuiTestCase() { class SampleFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { - assertThatFlow(flow { yield(); emit(1) }.sample(flowOf(2)) { a, b -> a to b }) + assertThatFlow( + flow { + yield() + emit(1) + } + .sample(flowOf(2)) { a, b -> a to b } + ) .emitsExactly(1 to 2) } @Test fun otherFlowNoValueYet() = runBlocking { - assertThatFlow(flowOf(1).sample(emptyFlow<Unit>())) - .emitsNothing() + assertThatFlow(flowOf(1).sample(emptyFlow<Unit>())).emitsNothing() } @Test @@ -178,13 +231,14 @@ class SampleFlowTest : SysuiTestCase() { } } -private fun <T> assertThatFlow(flow: Flow<T>) = object { - suspend fun emitsExactly(vararg emissions: T) = - assertThat(flow.toList()).containsExactly(*emissions).inOrder() - suspend fun emitsNothing() = - assertThat(flow.toList()).isEmpty() -} +private fun <T> assertThatFlow(flow: Flow<T>) = + object { + suspend fun emitsExactly(vararg emissions: T) = + assertThat(flow.toList()).containsExactly(*emissions).inOrder() + suspend fun emitsNothing() = assertThat(flow.toList()).isEmpty() + } -private fun assertThatJob(job: Job) = object { - fun isCompleted() = assertThat(job.isCompleted).isTrue() -} +private fun assertThatJob(job: Job) = + object { + fun isCompleted() = assertThat(job.isCompleted).isTrue() + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index db3b9b5c8f4e..d31f0bbf49af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -1423,13 +1423,43 @@ public class BubblesTest extends SysuiTestCase { assertStackCollapsed(); // Post status bar state change update with the same value mBubbleController.onStatusBarStateChanged(false); - // Stack should remain collapsedb + // Stack should remain collapsed assertStackCollapsed(); // Post status bar state change which should trigger bubble to expand mBubbleController.onStatusBarStateChanged(true); assertStackExpanded(); } + /** + * Test to verify behavior for the following scenario: + * <ol> + * <li>device is locked with keyguard on, status bar shade state updates to + * <code>false</code></li> + * <li>notification entry is marked to be a bubble and it is set to auto-expand</li> + * <li>device unlock starts, status bar shade state receives another update to + * <code>false</code></li> + * <li>device is unlocked and status bar shade state is set to <code>true</code></li> + * <li>bubble should be expanded</li> + * </ol> + */ + @Test + public void testOnStatusBarStateChanged_newAutoExpandedBubbleRemainsExpanded() { + // Set device as locked + mBubbleController.onStatusBarStateChanged(false); + + // Create a auto-expanded bubble + NotificationEntry entry = mNotificationTestHelper.createAutoExpandedBubble(); + mEntryListener.onEntryAdded(entry); + + // When unlocking, we may receive duplicate updates with shade=false, ensure they don't + // clear the expanded state + mBubbleController.onStatusBarStateChanged(false); + mBubbleController.onStatusBarStateChanged(true); + + // After unlocking, stack should be expanded + assertStackExpanded(); + } + @Test public void testSetShouldAutoExpand_notifiesFlagChanged() { mBubbleController.updateBubble(mBubbleEntry); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt new file mode 100644 index 000000000000..8176dd07b84a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt @@ -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.biometrics.udfps + +import android.graphics.Rect + +class FakeOverlapDetector : OverlapDetector { + var shouldReturn: Boolean = false + + override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean { + return shouldReturn + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt new file mode 100644 index 000000000000..b7a8d2e9f684 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt @@ -0,0 +1,40 @@ +/* + * 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.coroutines + +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent + +/** Collect [flow] in a new [Job] and return a getter for the last collected value. */ +fun <T> TestScope.collectLastValue( + flow: Flow<T>, + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, +): () -> T? { + var lastValue: T? = null + backgroundScope.launch(context, start) { flow.collect { lastValue = it } } + return { + runCurrent() + lastValue + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt new file mode 100644 index 000000000000..d85dd2e7bc8a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt @@ -0,0 +1,34 @@ +/* + * 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.data.quickaffordance + +import com.android.systemui.settings.UserTracker +import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient + +class FakeKeyguardQuickAffordanceProviderClientFactory( + private val userTracker: UserTracker, + private val callback: (Int) -> KeyguardQuickAffordanceProviderClient = { + FakeKeyguardQuickAffordanceProviderClient() + }, +) : KeyguardQuickAffordanceProviderClientFactory { + + override fun create(): KeyguardQuickAffordanceProviderClient { + return callback(userTracker.userId) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt index 63448e236867..1a893f8c523c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt @@ -56,7 +56,8 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.GlobalSettings import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler /** * Util class to create real implementations of the FooterActions repositories, viewModel and @@ -65,6 +66,7 @@ import kotlinx.coroutines.test.TestCoroutineDispatcher class FooterActionsTestUtils( private val context: Context, private val testableLooper: TestableLooper, + private val scheduler: TestCoroutineScheduler, ) { /** Enable or disable the user switcher in the settings. */ fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) { @@ -105,7 +107,7 @@ class FooterActionsTestUtils( foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(), userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(), broadcastDispatcher: BroadcastDispatcher = mock(), - bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(), + bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler), ): FooterActionsInteractor { return FooterActionsInteractorImpl( activityStarter, @@ -126,7 +128,7 @@ class FooterActionsTestUtils( /** Create a [SecurityRepository] to be used in tests. */ fun securityRepository( securityController: SecurityController = FakeSecurityController(), - bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(), + bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler), ): SecurityRepository { return SecurityRepositoryImpl( securityController, @@ -145,7 +147,7 @@ class FooterActionsTestUtils( fun userSwitcherRepository( @Application context: Context = this.context.applicationContext, bgHandler: Handler = Handler(testableLooper.looper), - bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(), + bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler), userManager: UserManager = mock(), userTracker: UserTracker = FakeUserTracker(), userSwitcherController: UserSwitcherController = mock(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt index a7eadba60ddc..0dd1fc758a27 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt @@ -66,7 +66,8 @@ class FakeUserTracker( _userId = _userInfo.id _userHandle = UserHandle.of(_userId) - callbacks.forEach { it.onUserChanged(_userId, userContext) } + val copy = callbacks.toList() + copy.forEach { it.onUserChanged(_userId, userContext) } } fun onProfileChanged() { diff --git a/services/api/current.txt b/services/api/current.txt index 42ae10ef8d5e..834ed2f67865 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -76,6 +76,7 @@ package com.android.server.pm.pkg { 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 @@ package com.android.server.pm.pkg { 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 000000000000..ec7e993ec30e --- /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 7e82918d621f..828f302e631a 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.VirtualDeviceManager.ActivityListener; 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.FileDescriptor; 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub ownerUid, deviceId, /* inputController= */ null, + /* sensorController= */ null, listener, pendingTrampolineCallback, activityListener, @@ -175,6 +180,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub int ownerUid, int deviceId, InputController inputController, + SensorController sensorController, OnDeviceCloseListener listener, PendingTrampolineCallback pendingTrampolineCallback, IVirtualDeviceActivityListener activityListener, @@ -198,6 +204,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub + "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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub + "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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub + "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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub + 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } } 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 fe26700f180b..2b62f69d78f3 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.VirtualDevice; 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 @@ public class VirtualDeviceManagerService extends SystemService { @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 68880bd09a40..6cd7ce8a1c43 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -310,27 +310,27 @@ public class BinaryTransparencyService extends SystemService { 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 @@ public class BinaryTransparencyService extends SystemService { } // 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 @@ public class BinaryTransparencyService extends SystemService { installer, originator ); - */ } } if (DEBUG) { @@ -489,24 +489,10 @@ public class BinaryTransparencyService extends SystemService { 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 @@ public class BinaryTransparencyService extends SystemService { 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 @@ public class BinaryTransparencyService extends SystemService { pw.print(packageName + "," + packageInfo.getLongVersionCode() + ","); printPackageMeasurements(packageInfo, pw); - pw.print("\n"); if (verbose) { ModuleInfo moduleInfo; @@ -802,7 +769,6 @@ public class BinaryTransparencyService extends SystemService { pw.print(packageInfo.packageName + ","); pw.print(packageInfo.getLongVersionCode() + ","); printPackageMeasurements(packageInfo, pw); - pw.print("\n"); if (verbose) { printModuleDetails(module, pw); @@ -858,7 +824,6 @@ public class BinaryTransparencyService extends SystemService { pw.print(packageInfo.packageName + ","); pw.print(packageInfo.getLongVersionCode() + ","); printPackageMeasurements(packageInfo, pw); - pw.print("\n"); if (verbose) { printAppDetails(packageInfo, printLibraries, pw); @@ -1079,6 +1044,21 @@ public class BinaryTransparencyService extends SystemService { 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 540ed4cdb330..3487613d313c 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -19,6 +19,7 @@ package com.android.server; 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 @@ final class DockObserver extends SystemService { 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 @@ final class DockObserver extends SystemService { 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 @@ final class DockObserver extends SystemService { 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 @@ final class DockObserver extends SystemService { 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 @@ final class DockObserver extends SystemService { 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 @@ final class DockObserver extends SystemService { // 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 @@ final class DockObserver extends SystemService { } } } + + 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 b56d1fca7f6a..b4ab254b06e7 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -447,6 +447,13 @@ public class RescueParty { 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 ca86021cd629..bd90d85e3fd0 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; 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.TelephonyDisplayInfo; 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { cutListToSize(mLinkCapacityEstimateLists, mNumPhones); cutListToSize(mCarrierPrivilegeStates, mNumPhones); cutListToSize(mCarrierServiceStates, mNumPhones); + cutListToSize(mCallStateLists, mNumPhones); return; } @@ -730,8 +731,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } 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 class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { // 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 50be45804e4f..c51e14fdc8a2 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5149,8 +5149,10 @@ public class ActivityManagerService extends IActivityManager.Stub ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, ApplicationExitInfo.SUBREASON_UNKNOWN, "wrong startSeq"); - app.killLocked("unexpected process record", - ApplicationExitInfo.REASON_OTHER, true); + synchronized (this) { + app.killLocked("unexpected process record", + ApplicationExitInfo.REASON_OTHER, true); + } return; } diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 04ba757d03e7..1eebd0127818 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 @@ public class BroadcastConstants { "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 @@ public class BroadcastConstants { 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 0f9c775751af..66d7fc92340a 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 @@ class BroadcastProcessQueue { 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 @@ class BroadcastProcessQueue { * {@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 @@ class BroadcastProcessQueue { } } + 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 @@ class BroadcastProcessQueue { 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/UserController.java b/services/core/java/com/android/server/am/UserController.java index 4d86140816ea..2ea49b338b38 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.EventLog; 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 @@ class UserController implements Handler.Callback { 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 @@ class UserController implements Handler.Callback { /** @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 @@ class UserController implements Handler.Callback { // 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 @@ class UserController implements Handler.Callback { } } - 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 @@ class UserController implements Handler.Callback { 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 @@ class UserController implements Handler.Callback { 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 @@ class UserController implements Handler.Callback { // 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 @@ class UserController implements Handler.Callback { 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 @@ class UserController implements Handler.Callback { } 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 @@ class UserController implements Handler.Callback { // 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 @@ class UserController implements Handler.Callback { 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 @@ class UserController implements Handler.Callback { // 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 @@ class UserController implements Handler.Callback { 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 @@ class UserController implements Handler.Callback { pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage); } pw.println(" mLastUserUnlockingUptime: " + mLastUserUnlockingUptime); - pw.println(" mVisibleUsers: " + mVisibleUsers); } } @@ -3212,10 +3102,6 @@ class UserController implements Handler.Callback { 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 @@ class UserController implements Handler.Callback { 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 b92c1635d7c6..cda18b07c02e 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.GameModeInfo; 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.AtomicFile; 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.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * Service to manage game related features. @@ -115,6 +118,14 @@ import java.util.Map; */ public final class GameManagerService extends IGameManagerService.Stub { public static final String TAG = "GameManagerService"; + // event strings used for logging + private static final String EVENT_SET_GAME_MODE = "SET_GAME_MODE"; + private static final String EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG = + "UPDATE_CUSTOM_GAME_MODE_CONFIG"; + private static final String EVENT_RECEIVE_SHUTDOWN_INDENT = "RECEIVE_SHUTDOWN_INDENT"; + private static final String EVENT_ON_USER_STARTING = "ON_USER_STARTING"; + private static final String EVENT_ON_USER_SWITCHING = "ON_USER_SWITCHING"; + private static final String EVENT_ON_USER_STOPPING = "ON_USER_STOPPING"; private static final boolean DEBUG = false; @@ -338,7 +349,18 @@ public final class GameManagerService extends IGameManagerService.Stub { + " 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 +460,7 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 +944,7 @@ public final class GameManagerService extends IGameManagerService.Stub { public void onBootPhase(int phase) { if (phase == PHASE_BOOT_COMPLETED) { mService.onBootCompleted(); + mService.registerStatsCallbacks(); } } @@ -964,11 +988,8 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - 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 +1012,13 @@ public final class GameManagerService extends IGameManagerService.Stub { */ @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 +1085,6 @@ public final class GameManagerService extends IGameManagerService.Stub { // 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 +1114,7 @@ public final class GameManagerService extends IGameManagerService.Stub { } else { return new GameModeInfo.Builder() .setActiveGameMode(activeGameMode) - .setAvailableGameModes(getAvailableGameModesUnchecked(packageName)) + .setAvailableGameModes(getAvailableGameModesUnchecked(packageName, userId)) .build(); } } @@ -1104,9 +1128,11 @@ public final class GameManagerService extends IGameManagerService.Stub { 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; @@ -1136,9 +1162,18 @@ public final class GameManagerService extends IGameManagerService.Stub { } } } - sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS); + sendUserMessage(userId, WRITE_SETTINGS, EVENT_SET_GAME_MODE, WRITE_DELAY_MILLIS); sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE, - "SET_GAME_MODE", 0 /*delayMillis*/); + EVENT_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 +1240,16 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 +1364,11 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 +1392,26 @@ public final class GameManagerService extends IGameManagerService.Stub { } 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); + + sendUserMessage(userId, WRITE_SETTINGS, EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG, + WRITE_DELAY_MILLIS); + sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE, + EVENT_UPDATE_CUSTOM_GAME_MODE_CONFIG, WRITE_DELAY_MILLIS /*delayMillis*/); } /** @@ -1432,9 +1486,10 @@ public final class GameManagerService extends IGameManagerService.Stub { for (Map.Entry<Integer, GameManagerSettings> entry : mSettings.entrySet()) { final int userId = entry.getKey(); sendUserMessage(userId, WRITE_SETTINGS, - Intent.ACTION_SHUTDOWN, 0 /*delayMillis*/); + EVENT_RECEIVE_SHUTDOWN_INDENT, 0 /*delayMillis*/); sendUserMessage(userId, - WRITE_GAME_MODE_INTERVENTION_LIST_FILE, Intent.ACTION_SHUTDOWN, + WRITE_GAME_MODE_INTERVENTION_LIST_FILE, + EVENT_RECEIVE_SHUTDOWN_INDENT, 0 /*delayMillis*/); } } @@ -1459,7 +1514,8 @@ public final class GameManagerService extends IGameManagerService.Stub { userSettings.readPersistentDataLocked(); } } - sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_STARTING", 0 /*delayMillis*/); + sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_STARTING, + 0 /*delayMillis*/); if (mGameServiceController != null) { mGameServiceController.notifyUserStarted(user); @@ -1479,7 +1535,7 @@ public final class GameManagerService extends IGameManagerService.Stub { if (!mSettings.containsKey(userId)) { return; } - sendUserMessage(userId, REMOVE_SETTINGS, "ON_USER_STOPPING", 0 /*delayMillis*/); + sendUserMessage(userId, REMOVE_SETTINGS, EVENT_ON_USER_STOPPING, 0 /*delayMillis*/); } if (mGameServiceController != null) { @@ -1492,7 +1548,7 @@ public final class GameManagerService extends IGameManagerService.Stub { // we want to re-populate the setting when switching user as the device config may have // changed, which will only update for the previous user, see // DeviceConfigListener#onPropertiesChanged. - sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_SWITCHING", + sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, EVENT_ON_USER_SWITCHING, 0 /*delayMillis*/); if (mGameServiceController != null) { @@ -1576,6 +1632,34 @@ public final class GameManagerService extends IGameManagerService.Stub { public void setGameModeConfigOverride(String packageName, @UserIdInt int userId, @GameMode int gameMode, String fpsStr, String scaling) throws SecurityException { checkPermission(Manifest.permission.MANAGE_GAME_MODE); + 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); + } + GamePackageConfiguration pkgConfig = getConfig(packageName, userId); + if (pkgConfig != null && pkgConfig.getGameModeConfiguration(gameMode) != null) { + final GamePackageConfiguration.GameModeConfiguration currentModeConfig = + pkgConfig.getGameModeConfiguration(gameMode); + FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CONFIGURATION_CHANGED, gameUid, + Binder.getCallingUid(), gameModeToStatsdGameMode(gameMode), + currentModeConfig.getScaling() /* fromScaling */, + scaling == null ? currentModeConfig.getScaling() + : Float.parseFloat(scaling) /* toScaling */, + currentModeConfig.getFps() /* fromFps */, + fpsStr == null ? currentModeConfig.getFps() + : Integer.parseInt(fpsStr)) /* toFps */; + } else { + FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CONFIGURATION_CHANGED, gameUid, + Binder.getCallingUid(), gameModeToStatsdGameMode(gameMode), + GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING /* fromScaling*/, + scaling == null ? GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING + : Float.parseFloat(scaling) /* toScaling */, + 0 /* fromFps */, + fpsStr == null ? 0 : Integer.parseInt(fpsStr) /* toFps */); + } + // Adding game mode config override of the given package name GamePackageConfiguration configOverride; synchronized (mLock) { @@ -1618,11 +1702,6 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 +2017,99 @@ public final class GameManagerService extends IGameManagerService.Stub { 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 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; } - return out.toString(); } private static int gameStateModeToStatsdGameState(int mode) { diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java index 638bc4e7a070..5189017f5bf0 100644 --- a/services/core/java/com/android/server/app/GameManagerSettings.java +++ b/services/core/java/com/android/server/app/GameManagerSettings.java @@ -19,6 +19,7 @@ package com.android.server.app; import android.app.GameManager; import android.os.FileUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; @@ -37,7 +38,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.util.Map; /** * Persists all GameService related settings. @@ -49,7 +49,11 @@ public class GameManagerSettings { // The XML file follows the below format: // <?xml> // <packages> - // <package></package> + // <package name="" gameMode=""> + // <gameModeConfig gameMode="" fps="" scaling="" useAngle="" loadingBoost=""> + // </gameModeConfig> + // ... + // </package> // ... // </packages> private static final String GAME_SERVICE_FILE_NAME = "game-manager-service.xml"; @@ -155,11 +159,14 @@ public class GameManagerSettings { serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startTag(null, TAG_PACKAGES); - for (Map.Entry<String, Integer> entry : mGameModes.entrySet()) { - String packageName = entry.getKey(); + final ArraySet<String> packageNames = new ArraySet<>(mGameModes.keySet()); + packageNames.addAll(mConfigOverrides.keySet()); + for (String packageName : packageNames) { serializer.startTag(null, TAG_PACKAGE); serializer.attribute(null, ATTR_NAME, packageName); - serializer.attributeInt(null, ATTR_GAME_MODE, entry.getValue()); + if (mGameModes.containsKey(packageName)) { + serializer.attributeInt(null, ATTR_GAME_MODE, mGameModes.get(packageName)); + } writeGameModeConfigTags(serializer, mConfigOverrides.get(packageName)); serializer.endTag(null, TAG_PACKAGE); } @@ -224,7 +231,7 @@ public class GameManagerSettings { // Do nothing } if (type != XmlPullParser.START_TAG) { - Slog.wtf(TAG, "No start tag found in package manager settings"); + Slog.wtf(TAG, "No start tag found in game manager settings"); return false; } @@ -245,7 +252,7 @@ public class GameManagerSettings { } } } catch (XmlPullParserException | java.io.IOException e) { - Slog.wtf(TAG, "Error reading package manager settings", e); + Slog.wtf(TAG, "Error reading game manager settings", e); return false; } return true; @@ -260,15 +267,12 @@ public class GameManagerSettings { XmlUtils.skipCurrentTag(parser); return; } - int gameMode; try { - gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE); + final int gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE); + mGameModes.put(name, gameMode); } catch (XmlPullParserException e) { - Slog.wtf(TAG, "Invalid game mode in package tag: " - + parser.getAttributeValue(null, ATTR_GAME_MODE), e); - return; + Slog.v(TAG, "No game mode selected by user for package" + name); } - mGameModes.put(name, gameMode); final int packageTagDepth = parser.getDepth(); int type; final GamePackageConfiguration config = new GamePackageConfiguration(name); diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java index cdbffbeae80b..abab0e7ae3b9 100644 --- a/services/core/java/com/android/server/app/GameManagerShellCommand.java +++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java @@ -27,6 +27,7 @@ import android.os.ShellCommand; import java.io.PrintWriter; import java.util.Locale; +import java.util.StringJoiner; /** * ShellCommands for GameManagerService. @@ -34,8 +35,20 @@ import java.util.Locale; * Use with {@code adb shell cmd game ...}. */ public class GameManagerShellCommand extends ShellCommand { + private static final String STANDARD_MODE_STR = "standard"; + private static final String STANDARD_MODE_NUM = "1"; + private static final String PERFORMANCE_MODE_STR = "performance"; + private static final String PERFORMANCE_MODE_NUM = "2"; + private static final String BATTERY_MODE_STR = "battery"; + private static final String BATTERY_MODE_NUM = "3"; + private static final String CUSTOM_MODE_STR = "custom"; + private static final String CUSTOM_MODE_NUM = "4"; + private static final String UNSUPPORTED_MODE_STR = "unsupported"; + private static final String UNSUPPORTED_MODE_NUM = String.valueOf( + GameManager.GAME_MODE_UNSUPPORTED); - public GameManagerShellCommand() {} + public GameManagerShellCommand() { + } @Override public int onCommand(String cmd) { @@ -46,10 +59,10 @@ public class GameManagerShellCommand extends ShellCommand { try { switch (cmd) { case "set": { - return runSetGameMode(pw); + return runSetGameModeConfig(pw); } case "reset": { - return runResetGameMode(pw); + return runResetGameModeConfig(pw); } case "mode": { /** The "mode" command allows setting a package's current game mode outside of @@ -61,10 +74,13 @@ public class GameManagerShellCommand extends ShellCommand { * <PACKAGE_NAME> <CONFIG_STRING>` * see: {@link GameManagerServiceTests#mockDeviceConfigAll()} */ - return runGameMode(pw); + return runSetGameMode(pw); + } + case "list-modes": { + return runListGameModes(pw); } - case "list": { - return runGameList(pw); + case "list-configs": { + return runListGameModeConfigs(pw); } default: return handleDefaultCommands(cmd); @@ -75,7 +91,21 @@ public class GameManagerShellCommand extends ShellCommand { return -1; } - private int runGameList(PrintWriter pw) throws ServiceNotFoundException, RemoteException { + private int runListGameModes(PrintWriter pw) throws ServiceNotFoundException, RemoteException { + final String packageName = getNextArgRequired(); + final GameManagerService gameManagerService = (GameManagerService) + ServiceManager.getService(Context.GAME_SERVICE); + final StringJoiner sj = new StringJoiner(","); + for (int mode : gameManagerService.getAvailableGameModes(packageName, + ActivityManager.getCurrentUser())) { + sj.add(gameModeIntToString(mode)); + } + pw.println(packageName + " has available game modes: [" + sj + "]"); + return 0; + } + + private int runListGameModeConfigs(PrintWriter pw) + throws ServiceNotFoundException, RemoteException { final String packageName = getNextArgRequired(); final GameManagerService gameManagerService = (GameManagerService) @@ -92,7 +122,7 @@ public class GameManagerShellCommand extends ShellCommand { return 0; } - private int runGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { + private int runSetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { final String option = getNextOption(); String userIdStr = null; if (option != null && option.equals("--user")) { @@ -105,7 +135,9 @@ public class GameManagerShellCommand extends ShellCommand { 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,37 +145,47 @@ public class GameManagerShellCommand extends ShellCommand { batteryModeSupported = true; } } - int userId = userIdStr != null ? Integer.parseInt(userIdStr) - : ActivityManager.getCurrentUser(); switch (gameMode.toLowerCase()) { - case "1": - case "standard": + case STANDARD_MODE_NUM: + case STANDARD_MODE_STR: // Standard mode can be used to specify loading ANGLE as the default OpenGL ES // driver, so it should always be available. service.setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId); + pw.println("Set game mode to `STANDARD` for user `" + userId + "` in game `" + + packageName + "`"); break; - case "2": - case "performance": + case PERFORMANCE_MODE_NUM: + case PERFORMANCE_MODE_STR: if (perfModeSupported) { service.setGameMode(packageName, GameManager.GAME_MODE_PERFORMANCE, userId); + pw.println("Set game mode to `PERFORMANCE` for user `" + userId + "` in game `" + + packageName + "`"); } else { pw.println("Game mode: " + gameMode + " not supported by " + packageName); return -1; } break; - case "3": - case "battery": + case BATTERY_MODE_NUM: + case BATTERY_MODE_STR: if (batteryModeSupported) { service.setGameMode(packageName, GameManager.GAME_MODE_BATTERY, userId); + pw.println("Set game mode to `BATTERY` for user `" + userId + "` in game `" + + packageName + "`"); } else { pw.println("Game mode: " + gameMode + " not supported by " + packageName); return -1; } break; + case CUSTOM_MODE_NUM: + case CUSTOM_MODE_STR: + service.setGameMode(packageName, GameManager.GAME_MODE_CUSTOM, userId); + pw.println("Set game mode to `CUSTOM` for user `" + userId + "` in game `" + + packageName + "`"); + break; default: pw.println("Invalid game mode: " + gameMode); return -1; @@ -151,15 +193,9 @@ public class GameManagerShellCommand extends ShellCommand { return 0; } - private int runSetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { - String option = getNextArgRequired(); - if (!option.equals("--mode")) { - pw.println("Invalid option '" + option + "'"); - return -1; - } - - final String gameMode = getNextArgRequired(); - + private int runSetGameModeConfig(PrintWriter pw) + throws ServiceNotFoundException, RemoteException { + String option; /** * handling optional input * "--user", "--downscale" and "--fps" can come in any order @@ -167,8 +203,12 @@ public class GameManagerShellCommand extends ShellCommand { String userIdStr = null; String fpsStr = null; String downscaleRatio = null; + int gameMode = GameManager.GAME_MODE_CUSTOM; while ((option = getNextOption()) != null) { switch (option) { + case "--mode": + gameMode = Integer.parseInt(getNextArgRequired()); + break; case "--user": if (userIdStr == null) { userIdStr = getNextArgRequired(); @@ -220,50 +260,21 @@ public class GameManagerShellCommand extends ShellCommand { final GameManagerService gameManagerService = (GameManagerService) ServiceManager.getService(Context.GAME_SERVICE); - - boolean batteryModeSupported = false; - boolean perfModeSupported = false; - int [] modes = gameManagerService.getAvailableGameModes(packageName); - - for (int mode : modes) { - if (mode == GameManager.GAME_MODE_PERFORMANCE) { - perfModeSupported = true; - } else if (mode == GameManager.GAME_MODE_BATTERY) { - batteryModeSupported = true; - } - } - - switch (gameMode.toLowerCase(Locale.getDefault())) { - case "2": - case "performance": - if (perfModeSupported) { - gameManagerService.setGameModeConfigOverride(packageName, userId, - GameManager.GAME_MODE_PERFORMANCE, fpsStr, downscaleRatio); - } else { - pw.println("Game mode: " + gameMode + " not supported by " - + packageName); - return -1; - } - break; - case "3": - case "battery": - if (batteryModeSupported) { - gameManagerService.setGameModeConfigOverride(packageName, userId, - GameManager.GAME_MODE_BATTERY, fpsStr, downscaleRatio); - } else { - pw.println("Game mode: " + gameMode + " not supported by " - + packageName); - return -1; - } - break; - default: - pw.println("Invalid game mode: " + gameMode); - return -1; + if (gameManagerService == null) { + pw.println("Failed to find GameManagerService on device"); + return -1; } + gameManagerService.setGameModeConfigOverride(packageName, userId, gameMode, + fpsStr, downscaleRatio); + pw.println("Set custom mode intervention config for user `" + userId + "` in game `" + + packageName + "` as: `" + + "downscaling-ratio: " + downscaleRatio + ";" + + "fps-override: " + fpsStr + "`"); return 0; } - private int runResetGameMode(PrintWriter pw) throws ServiceNotFoundException, RemoteException { + private int runResetGameModeConfig(PrintWriter pw) + throws ServiceNotFoundException, RemoteException { String option = null; String gameMode = null; String userIdStr = null; @@ -305,13 +316,13 @@ public class GameManagerShellCommand extends ShellCommand { } switch (gameMode.toLowerCase(Locale.getDefault())) { - case "2": - case "performance": + case PERFORMANCE_MODE_NUM: + case PERFORMANCE_MODE_STR: gameManagerService.resetGameModeConfigOverride(packageName, userId, GameManager.GAME_MODE_PERFORMANCE); break; - case "3": - case "battery": + case BATTERY_MODE_NUM: + case BATTERY_MODE_STR: gameManagerService.resetGameModeConfigOverride(packageName, userId, GameManager.GAME_MODE_BATTERY); break; @@ -322,6 +333,22 @@ public class GameManagerShellCommand extends ShellCommand { return 0; } + private static String gameModeIntToString(@GameManager.GameMode int gameMode) { + switch (gameMode) { + case GameManager.GAME_MODE_BATTERY: + return BATTERY_MODE_STR; + case GameManager.GAME_MODE_PERFORMANCE: + return PERFORMANCE_MODE_STR; + case GameManager.GAME_MODE_CUSTOM: + return CUSTOM_MODE_STR; + case GameManager.GAME_MODE_STANDARD: + return STANDARD_MODE_STR; + case GameManager.GAME_MODE_UNSUPPORTED: + return UNSUPPORTED_MODE_STR; + } + return ""; + } + @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); @@ -329,21 +356,28 @@ public class GameManagerShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Print this help text."); pw.println(" downscale"); - pw.println(" Deprecated. Please use `set` command."); - pw.println(" mode [--user <USER_ID>] [1|2|3|standard|performance|battery] <PACKAGE_NAME>"); + pw.println(" Deprecated. Please use `custom` command."); + pw.println(" list-configs <PACKAGE_NAME>"); + pw.println(" Lists the current intervention configs of an app."); + pw.println(" list-modes <PACKAGE_NAME>"); + pw.println(" Lists the current available game modes of an app."); + pw.println(" mode [--user <USER_ID>] [1|2|3|4|standard|performance|battery|custom] " + + "<PACKAGE_NAME>"); pw.println(" Set app to run in the specified game mode, if supported."); pw.println(" --user <USER_ID>: apply for the given user,"); pw.println(" the current user is used when unspecified."); - pw.println(" set --mode [2|3|performance|battery] [intervention configs] <PACKAGE_NAME>"); - pw.println(" Set app to run at given game mode with configs, if supported."); + pw.println(" set [intervention configs] <PACKAGE_NAME>"); + pw.println(" Set app to run at custom mode using provided intervention configs"); pw.println(" Intervention configs consists of:"); pw.println(" --downscale [0.3|0.35|0.4|0.45|0.5|0.55|0.6|0.65"); - pw.println(" |0.7|0.75|0.8|0.85|0.9|disable]"); - pw.println(" Set app to run at the specified scaling ratio."); - pw.println(" --fps [30|45|60|90|120|disable]"); - pw.println(" Set app to run at the specified fps, if supported."); + pw.println(" |0.7|0.75|0.8|0.85|0.9|disable]: Set app to run at the"); + pw.println(" specified scaling ratio."); + pw.println(" --fps [30|45|60|90|120|disable]: Set app to run at the specified fps,"); + pw.println(" if supported."); pw.println(" reset [--mode [2|3|performance|battery] --user <USER_ID>] <PACKAGE_NAME>"); pw.println(" Resets the game mode of the app to device configuration."); + pw.println(" This should only be used to reset any override to non custom game mode"); + pw.println(" applied using the deprecated `set` command"); pw.println(" --mode [2|3|performance|battery]: apply for the given mode,"); pw.println(" resets all modes when unspecified."); pw.println(" --user <USER_ID>: apply for the given user,"); diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java index f6fff351c232..8d1da71c95c1 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.OP_NONE; 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 @@ import java.util.Objects; * 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 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface 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 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface 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 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface 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 000000000000..d8d0d48965ea --- /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 adfd2afffd78..af5b07e0bffc 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 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { 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 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { 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 7e00c3212c95..39338c6f43ad 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 @@ package com.android.server.appop; 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.ActivityManager; 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.PackageInfo; 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.ResultReceiver; 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.IAppOpsNotedCallback; 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch /** 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 class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } public AppOpsService(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 LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps); - mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler, - mAppOpsServiceInterface); + this(handler, context, new AppOpsServiceImpl(storagePath, handler, context)); + } - 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } }); - - mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } /** @@ -1185,132 +322,18 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ @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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch @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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } + /** + * Sets the appops {@link CheckOpsDelegate} + */ public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) { synchronized (AppOpsService.this) { final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher; @@ -2269,58 +467,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch @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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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); } - 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); - } - - 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()); - } + int result = mAppOpsService.noteOperation(code, uid, packageName, + attributionTag, message); - 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); + String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); - if (shouldCollectAsyncNotedOp) { - collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message, - shouldCollectMessage); - } + boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid, + resolvedPackageName, attributionTag, null); - return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag, - packageName); + if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) { + collectAsyncNotedOp(uid, resolvedPackageName, code, + isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF, + message, shouldCollectMessage); } + + 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch int uid = Binder.getCallingUid(); - verifyAndGetBypass(uid, packageName, null); + mAppOpsService.verifyPackage(uid, packageName); synchronized (this) { return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid)); @@ -2889,54 +844,49 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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); - } - // 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); - } + 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); } - 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,145 +934,66 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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; - } - } - return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, - proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag, - proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, proxiedAttributionFlags, attributionChainId, - /*dryRun*/ false); - } + boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid, + resolvedProxyPackageName, proxyAttributionTag, null); - 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; + if (!shouldStartForMode(proxyAppOp, startIfModeDefault)) { + return new SyncNotedAppOp(proxyAppOp, code, + isProxyAttributionTagValid ? proxyAttributionTag : null, + resolvedProxyPackageName); } - } 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) { + collectAsyncNotedOp(proxyUid, resolvedProxyPackageName, code, + isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags, + message, shouldCollectMessage); } } - if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) { - collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF, - message, shouldCollectMessage); + 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(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag, - packageName); + return new SyncNotedAppOp(proxiedAppOp, code, + isProxiedAttributionTagValid ? proxiedAttributionTag : null, + resolvedProxiedPackageName); + } + + private boolean shouldStartForMode(int mode, boolean startIfModeDefault) { + return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault)); } @Override @@ -3134,22 +1005,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } if (!skipProxyOperation) { - finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, - proxyAttributionTag); + mAppOpsService.finishOperationUnchecked(clientId, code, proxyUid, + resolvedProxyPackageName, proxyAttributionTag); } String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid, @@ -3191,209 +1051,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch || 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch int mode; int packageUid; int nonpackageUid; - final static Binder sBinder = new Binder(); IBinder mToken; boolean targetsUid; @@ -4330,7 +1198,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - 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 000000000000..70f3bcc64ef0 --- /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 18f659e4c62a..8420fcbd346f 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 { +public interface AppOpsServiceInterface extends PersistenceScheduler { + + /** + * + */ + void systemReady(); + + /** + * + */ + void shutdown(); + + /** + * + * @param uid + * @param packageName + */ + void verifyPackage(int uid, String packageName); + + /** + * + * @param op + * @param packageName + * @param flags + * @param callback + */ + void startWatchingModeWithFlags(int op, String packageName, int flags, + IAppOpsCallback callback); + + /** + * + * @param callback + */ + void stopWatchingMode(IAppOpsCallback callback); + + /** + * + * @param ops + * @param callback + */ + void startWatchingActive(int[] ops, IAppOpsActiveCallback callback); + + /** + * + * @param callback + */ + void stopWatchingActive(IAppOpsActiveCallback callback); + + /** + * + * @param ops + * @param callback + */ + void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback); + /** - * 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. + * + * @param callback */ - SparseIntArray getNonDefaultUidModes(int uid); + void stopWatchingStarted(IAppOpsStartedCallback callback); /** - * 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. + * + * @param ops + * @param callback */ - int getUidMode(int uid, int op); + void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback); /** - * 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. + * + * @param callback */ - boolean setUidMode(int uid, int op, @Mode int mode); + void stopWatchingNoted(IAppOpsNotedCallback callback); /** - * 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. + * @param clientId + * @param code + * @param uid + * @param packageName + * @param attributionTag + * @param startIfModeDefault + * @param message + * @param attributionFlags + * @param attributionChainId + * @return */ - int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId); + 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); /** - * 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. * + * @param clientId + * @param code + * @param uid + * @param packageName + * @param attributionTag */ - void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId); + void finishOperation(IBinder clientId, int code, int uid, String packageName, + String attributionTag); /** - * 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. + * + * @param clientId + * @param code + * @param uid + * @param packageName + * @param attributionTag */ - boolean removePackage(@NonNull String packageName, @UserIdInt int userId); + void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName, + String attributionTag); /** - * Stop tracking any app-op modes for this uid. - * @param uid user id for which we want to remove all tracking. + * + * @param uidPackageNames + * @param visible */ - void removeUid(int uid); + void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible); /** - * Returns true if all uid modes for this uid are - * in default state. - * @param uid user id + * */ - boolean areUidModesDefault(int uid); + void readState(); /** - * 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); + void writeState(); /** - * Stop tracking app-op modes for all uid and packages. + * + * @param uid + * @param packageName */ - void clearAllModes(); + void packageRemoved(int uid, String packageName); /** - * 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 uid */ - void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op); + void uidRemoved(int uid); /** - * 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 uid + * @param procState + * @param capability */ - void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, - @NonNull String packageName); + void updateUidProcState(int uid, int procState, + @ActivityManager.ProcessCapability int capability); /** - * Stop the changedListener from triggering on any mode change. - * @param changedListener the listener that needs to be removed. + * + * @param ops + * @return */ - void removeListener(@NonNull OnOpModeChangedListener changedListener); + List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops); /** - * 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 uid + * @param packageName + * @param ops + * @return */ - ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op); + List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, + int[] ops); /** - * 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 uid + * @param packageName + * @param attributionTag + * @param opNames + * @param dataType + * @param filter + * @param beginTimeMillis + * @param endTimeMillis + * @param flags + * @param callback */ - ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName); + void getHistoricalOps(int uid, String packageName, String attributionTag, + List<String> opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback 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 uid + * @param packageName + * @param attributionTag + * @param opNames + * @param dataType + * @param filter + * @param beginTimeMillis + * @param endTimeMillis + * @param flags + * @param callback */ - void notifyWatchersOfChange(int op, int uid); + void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag, + List<String> opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback 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 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); + void reloadNonHistoricalState(); /** - * 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 uid + * @param ops + * @return */ - void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground, - @Nullable OnOpModeChangedListener callbackToIgnore); + List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops); /** - * 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 owners */ - SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps); + void setDeviceAndProfileOwners(SparseIntArray owners); + // used in audio restriction calls, might just copy the logic to avoid having this call. /** - * 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. + * + * @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 */ - SparseBooleanArray evalForegroundPackageOps(String packageName, - SparseBooleanArray foregroundOps, @UserIdInt int userId); + void setMode(int code, int uid, @NonNull String packageName, int mode, + @Nullable IAppOpsCallback permissionPolicyCallback); /** - * 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. + * + * @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 */ - boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter); + 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 5114bd59f084..c1434e4d9f4d 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal, Handler handler, Executor lockingExecutor, Clock clock, - AppOpsService.Constants constants) { + AppOpsServiceImpl.Constants constants) { this(activityManagerInternal, new DelayableExecutor() { @Override @@ -102,7 +102,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { @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 dcc36bcf6149..797026908619 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.List; 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 @@ final class AttributedOp { // @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 @@ final class AttributedOp { 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 @@ final class AttributedOp { 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 @@ final class AttributedOp { 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 @@ final class AttributedOp { 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 @@ final class AttributedOp { @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 @@ final class AttributedOp { 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 @@ final class AttributedOp { 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 @@ final class AttributedOp { 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 @@ final class AttributedOp { /** * 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 @@ final class AttributedOp { /** * 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 2e810672fd64..8aa898edff9a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -7099,9 +7099,10 @@ public class AudioService extends IAudioService.Stub 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/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java index a8e4034e3f86..408fba1bff3b 100644 --- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java +++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java @@ -27,6 +27,7 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.broadcastradio.hal2.AnnouncementAggregator; @@ -51,15 +52,17 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub { private final Object mLock = new Object(); private final BroadcastRadioService mService; + + @GuardedBy("mLock") private final List<RadioManager.ModuleProperties> mV1Modules; IRadioServiceHidlImpl(BroadcastRadioService service) { mService = Objects.requireNonNull(service, "broadcast radio service cannot be null"); - mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(mLock); + mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(); mV1Modules = mHal1.loadModules(); OptionalInt max = mV1Modules.stream().mapToInt(RadioManager.ModuleProperties::getId).max(); mHal2 = new com.android.server.broadcastradio.hal2.BroadcastRadioService( - max.isPresent() ? max.getAsInt() + 1 : 0, mLock); + max.isPresent() ? max.getAsInt() + 1 : 0); } @VisibleForTesting @@ -78,9 +81,11 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub { public List<RadioManager.ModuleProperties> listModules() { mService.enforcePolicyAccess(); Collection<RadioManager.ModuleProperties> v2Modules = mHal2.listModules(); - List<RadioManager.ModuleProperties> modules = new ArrayList<>( - mV1Modules.size() + v2Modules.size()); - modules.addAll(mV1Modules); + List<RadioManager.ModuleProperties> modules; + synchronized (mLock) { + modules = new ArrayList<>(mV1Modules.size() + v2Modules.size()); + modules.addAll(mV1Modules); + } modules.addAll(v2Modules); return modules; } @@ -131,7 +136,9 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub { radioPw.printf("HAL1: %s\n", mHal1); radioPw.increaseIndent(); - radioPw.printf("Modules of HAL1: %s\n", mV1Modules); + synchronized (mLock) { + radioPw.printf("Modules of HAL1: %s\n", mV1Modules); + } radioPw.decreaseIndent(); radioPw.printf("HAL2:\n"); diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java index 1d7112133b48..03acf72725e7 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java @@ -77,7 +77,7 @@ public final class BroadcastRadioServiceImpl { } RadioModule radioModule = - RadioModule.tryLoadingModule(moduleId, name, newBinder, mLock); + RadioModule.tryLoadingModule(moduleId, name, newBinder); if (radioModule == null) { Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId); return; diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java index eb9dafbe5281..e956a9c2038c 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java @@ -55,7 +55,7 @@ final class RadioModule { private final IBroadcastRadio mService; - private final Object mLock; + private final Object mLock = new Object(); private final Handler mHandler; private final RadioLogger mLogger; private final RadioManager.ModuleProperties mProperties; @@ -165,18 +165,15 @@ final class RadioModule { }; @VisibleForTesting - RadioModule(IBroadcastRadio service, - RadioManager.ModuleProperties properties, Object lock) { + RadioModule(IBroadcastRadio service, RadioManager.ModuleProperties properties) { mProperties = Objects.requireNonNull(properties, "properties cannot be null"); mService = Objects.requireNonNull(service, "service cannot be null"); - mLock = Objects.requireNonNull(lock, "lock cannot be null"); mHandler = new Handler(Looper.getMainLooper()); mLogger = new RadioLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE); } @Nullable - static RadioModule tryLoadingModule(int moduleId, String moduleName, - IBinder serviceBinder, Object lock) { + static RadioModule tryLoadingModule(int moduleId, String moduleName, IBinder serviceBinder) { try { Slogf.i(TAG, "Try loading module for module id = %d, module name = %s", moduleId, moduleName); @@ -206,7 +203,7 @@ final class RadioModule { RadioManager.ModuleProperties prop = ConversionUtils.propertiesFromHalProperties( moduleId, moduleName, service.getProperties(), amfmConfig, dabConfig); - return new RadioModule(service, prop, lock); + return new RadioModule(service, prop); } catch (RemoteException ex) { Slogf.e(TAG, ex, "Failed to load module %s", moduleName); return null; @@ -222,9 +219,7 @@ final class RadioModule { } void setInternalHalCallback() throws RemoteException { - synchronized (mLock) { - mService.setTunerCallback(mHalTunerCallback); - } + mService.setTunerCallback(mHalTunerCallback); } TunerSession openSession(android.hardware.radio.ITunerCallback userCb) @@ -234,7 +229,7 @@ final class RadioModule { Boolean antennaConnected; RadioManager.ProgramInfo currentProgramInfo; synchronized (mLock) { - tunerSession = new TunerSession(this, mService, userCb, mLock); + tunerSession = new TunerSession(this, mService, userCb); mAidlTunerSessions.add(tunerSession); antennaConnected = mAntennaConnected; currentProgramInfo = mCurrentProgramInfo; @@ -356,14 +351,14 @@ final class RadioModule { // Otherwise, update the HAL's filter, and AIDL clients will be updated when // mHalTunerCallback.onProgramListUpdated() is called. mUnionOfAidlProgramFilters = newFilter; - try { - mService.startProgramListUpdates( - ConversionUtils.filterToHalProgramFilter(newFilter)); - } catch (RuntimeException ex) { - throw ConversionUtils.throwOnError(ex, /* action= */ "Start Program ListUpdates"); - } catch (RemoteException ex) { - Slogf.e(TAG, ex, "mHalTunerSession.startProgramListUpdates() failed"); - } + } + try { + mService.startProgramListUpdates( + ConversionUtils.filterToHalProgramFilter(newFilter)); + } catch (RuntimeException ex) { + throw ConversionUtils.throwOnError(ex, /* action= */ "Start Program ListUpdates"); + } catch (RemoteException ex) { + Slogf.e(TAG, ex, "mHalTunerSession.startProgramListUpdates() failed"); } } @@ -453,12 +448,10 @@ final class RadioModule { } }; - synchronized (mLock) { - try { - hwCloseHandle[0] = mService.registerAnnouncementListener(hwListener, enabledList); - } catch (RuntimeException ex) { - throw ConversionUtils.throwOnError(ex, /* action= */ "AnnouncementListener"); - } + try { + hwCloseHandle[0] = mService.registerAnnouncementListener(hwListener, enabledList); + } catch (RuntimeException ex) { + throw ConversionUtils.throwOnError(ex, /* action= */ "AnnouncementListener"); } return new android.hardware.radio.ICloseHandle.Stub() { @@ -478,12 +471,10 @@ final class RadioModule { if (id == 0) throw new IllegalArgumentException("Image ID is missing"); byte[] rawImage; - synchronized (mLock) { - try { - rawImage = mService.getImage(id); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } + try { + rawImage = mService.getImage(id); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } if (rawImage == null || rawImage.length == 0) return null; diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java index d33633c435b1..1ce4044d4c51 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java @@ -42,7 +42,7 @@ final class TunerSession extends ITuner.Stub { private static final String TAG = "BcRadioAidlSrv.session"; private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25; - private final Object mLock; + private final Object mLock = new Object(); private final RadioLogger mLogger; private final RadioModule mModule; @@ -61,12 +61,10 @@ final class TunerSession extends ITuner.Stub { private RadioManager.BandConfig mPlaceHolderConfig; TunerSession(RadioModule radioModule, IBroadcastRadio service, - android.hardware.radio.ITunerCallback callback, - Object lock) { + android.hardware.radio.ITunerCallback callback) { mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null"); mService = Objects.requireNonNull(service, "service cannot be null"); mCallback = Objects.requireNonNull(callback, "callback cannot be null"); - mLock = Objects.requireNonNull(lock, "lock cannot be null"); mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE); } @@ -91,17 +89,19 @@ final class TunerSession extends ITuner.Stub { mLogger.logRadioEvent("Close tuner session on error %d", error); } synchronized (mLock) { - if (mIsClosed) return; - if (error != null) { - try { - mCallback.onError(error); - } catch (RemoteException ex) { - Slogf.w(TAG, ex, "mCallback.onError(%s) failed", error); - } + if (mIsClosed) { + return; } mIsClosed = true; - mModule.onTunerSessionClosed(this); } + if (error != null) { + try { + mCallback.onError(error); + } catch (RemoteException ex) { + Slogf.w(TAG, ex, "mCallback.onError(%s) failed", error); + } + } + mModule.onTunerSessionClosed(this); } @Override diff --git a/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java index e50c6e8c21b8..fb42c94b56f4 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java @@ -36,7 +36,7 @@ public class BroadcastRadioService { */ private final long mNativeContext = nativeInit(); - private final Object mLock; + private final Object mLock = new Object(); @Override protected void finalize() throws Throwable { @@ -50,14 +50,6 @@ public class BroadcastRadioService { private native Tuner nativeOpenTuner(long nativeContext, int moduleId, RadioManager.BandConfig config, boolean withAudio, ITunerCallback callback); - /** - * Constructor. should pass - * {@code com.android.server.broadcastradio.BroadcastRadioService#mLock} for lock. - */ - public BroadcastRadioService(Object lock) { - mLock = lock; - } - public @NonNull List<RadioManager.ModuleProperties> loadModules() { synchronized (mLock) { return Objects.requireNonNull(nativeLoadModules(mNativeContext)); diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index 3d6962783f4a..984bf5125582 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -43,13 +43,16 @@ import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; -public class BroadcastRadioService { +/** + * Broadcast radio service using BroadcastRadio HIDL 2.0 HAL + */ +public final class BroadcastRadioService { private static final String TAG = "BcRadio2Srv"; - private final Object mLock; + private final Object mLock = new Object(); @GuardedBy("mLock") - private int mNextModuleId = 0; + private int mNextModuleId; @GuardedBy("mLock") private final Map<String, Integer> mServiceNameToModuleIdMap = new HashMap<>(); @@ -72,7 +75,7 @@ public class BroadcastRadioService { moduleId = mNextModuleId; } - RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName, mLock); + RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName); if (module == null) { return; } @@ -120,9 +123,8 @@ public class BroadcastRadioService { } }; - public BroadcastRadioService(int nextModuleId, Object lock) { + public BroadcastRadioService(int nextModuleId) { mNextModuleId = nextModuleId; - mLock = lock; try { IServiceManager manager = IServiceManager.getService(); if (manager == null) { @@ -136,9 +138,8 @@ public class BroadcastRadioService { } @VisibleForTesting - BroadcastRadioService(int nextModuleId, Object lock, IServiceManager manager) { + BroadcastRadioService(int nextModuleId, IServiceManager manager) { mNextModuleId = nextModuleId; - mLock = lock; Objects.requireNonNull(manager, "Service manager cannot be null"); try { manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener); @@ -180,7 +181,7 @@ public class BroadcastRadioService { throw new IllegalArgumentException("Non-audio sessions not supported with HAL 2.0"); } - RadioModule module = null; + RadioModule module; synchronized (mLock) { module = mModules.get(moduleId); if (module == null) { diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index cf1b504037bc..0ea5f0fc1d6a 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -60,7 +60,7 @@ final class RadioModule { @NonNull private final IBroadcastRadio mService; @NonNull private final RadioManager.ModuleProperties mProperties; - private final Object mLock; + private final Object mLock = new Object(); @NonNull private final Handler mHandler; @NonNull private final RadioEventLogger mEventLogger; @@ -75,7 +75,7 @@ final class RadioModule { private RadioManager.ProgramInfo mCurrentProgramInfo = null; @GuardedBy("mLock") - private final ProgramInfoCache mProgramInfoCache = new ProgramInfoCache(null); + private final ProgramInfoCache mProgramInfoCache = new ProgramInfoCache(/* filter= */ null); @GuardedBy("mLock") private android.hardware.radio.ProgramList.Filter mUnionOfAidlProgramFilters = null; @@ -84,47 +84,59 @@ final class RadioModule { private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() { @Override public void onTuneFailed(int result, ProgramSelector programSelector) { - lockAndFireLater(() -> { + fireLater(() -> { android.hardware.radio.ProgramSelector csel = Convert.programSelectorFromHal(programSelector); - fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel)); + synchronized (mLock) { + fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel)); + } }); } @Override public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) { - lockAndFireLater(() -> { - mCurrentProgramInfo = Convert.programInfoFromHal(halProgramInfo); - fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(mCurrentProgramInfo)); + fireLater(() -> { + synchronized (mLock) { + mCurrentProgramInfo = Convert.programInfoFromHal(halProgramInfo); + RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo; + fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged( + currentProgramInfo)); + } }); } @Override public void onProgramListUpdated(ProgramListChunk programListChunk) { - lockAndFireLater(() -> { + fireLater(() -> { android.hardware.radio.ProgramList.Chunk chunk = Convert.programListChunkFromHal(programListChunk); - mProgramInfoCache.filterAndApplyChunk(chunk); + synchronized (mLock) { + mProgramInfoCache.filterAndApplyChunk(chunk); - for (TunerSession tunerSession : mAidlTunerSessions) { - tunerSession.onMergedProgramListUpdateFromHal(chunk); + for (TunerSession tunerSession : mAidlTunerSessions) { + tunerSession.onMergedProgramListUpdateFromHal(chunk); + } } }); } @Override public void onAntennaStateChange(boolean connected) { - lockAndFireLater(() -> { - mAntennaConnected = connected; - fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected)); + fireLater(() -> { + synchronized (mLock) { + mAntennaConnected = connected; + fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected)); + } }); } @Override public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) { - lockAndFireLater(() -> { + fireLater(() -> { Map<String, String> cparam = Convert.vendorInfoFromHal(parameters); - fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam)); + synchronized (mLock) { + fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam)); + } }); } }; @@ -135,17 +147,15 @@ final class RadioModule { @VisibleForTesting RadioModule(@NonNull IBroadcastRadio service, - @NonNull RadioManager.ModuleProperties properties, @NonNull Object lock) { + @NonNull RadioManager.ModuleProperties properties) { mProperties = Objects.requireNonNull(properties); mService = Objects.requireNonNull(service); - mLock = Objects.requireNonNull(lock); mHandler = new Handler(Looper.getMainLooper()); mEventLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE); } @Nullable - static RadioModule tryLoadingModule(int idx, @NonNull String fqName, - Object lock) { + static RadioModule tryLoadingModule(int idx, @NonNull String fqName) { try { Slog.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName); IBroadcastRadio service = IBroadcastRadio.getService(fqName); @@ -167,7 +177,7 @@ final class RadioModule { RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName, service.getProperties(), amfmConfig.value, dabConfig.value); - return new RadioModule(service, prop, lock); + return new RadioModule(service, prop); } catch (RemoteException ex) { Slog.e(TAG, "Failed to load module " + fqName, ex); return null; @@ -196,8 +206,7 @@ final class RadioModule { }); mHalTunerSession = Objects.requireNonNull(hwSession.value); } - TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb, - mLock); + TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb); mAidlTunerSessions.add(tunerSession); // Propagate state to new client. Note: These callbacks are invoked while holding mLock @@ -229,6 +238,7 @@ final class RadioModule { } } + @GuardedBy("mLock") @Nullable private android.hardware.radio.ProgramList.Filter buildUnionOfTunerSessionFiltersLocked() { @@ -281,6 +291,7 @@ final class RadioModule { } } + @GuardedBy("mLock") private void onTunerSessionProgramListFilterChangedLocked(@Nullable TunerSession session) { android.hardware.radio.ProgramList.Filter newFilter = buildUnionOfTunerSessionFiltersLocked(); @@ -325,6 +336,7 @@ final class RadioModule { } } + @GuardedBy("mLock") private void onTunerSessionsClosedLocked(TunerSession... tunerSessions) { for (TunerSession tunerSession : tunerSessions) { mAidlTunerSessions.remove(tunerSession); @@ -342,12 +354,8 @@ final class RadioModule { } // add to mHandler queue, but ensure the runnable holds mLock when it gets executed - private void lockAndFireLater(Runnable r) { - mHandler.post(() -> { - synchronized (mLock) { - r.run(); - } - }); + private void fireLater(Runnable r) { + mHandler.post(() -> r.run()); } interface AidlCallbackRunnable { @@ -356,9 +364,14 @@ final class RadioModule { // Invokes runnable with each TunerSession currently open. void fanoutAidlCallback(AidlCallbackRunnable runnable) { - lockAndFireLater(() -> fanoutAidlCallbackLocked(runnable)); + fireLater(() -> { + synchronized (mLock) { + fanoutAidlCallbackLocked(runnable); + } + }); } + @GuardedBy("mLock") private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) { List<TunerSession> deadSessions = null; for (TunerSession tunerSession : mAidlTunerSessions) { @@ -399,12 +412,10 @@ final class RadioModule { } }; - synchronized (mLock) { - mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> { - halResult.value = result; - hwCloseHandle.value = closeHnd; - }); - } + mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHandle) -> { + halResult.value = result; + hwCloseHandle.value = closeHandle; + }); Convert.throwOnError("addAnnouncementListener", halResult.value); return new android.hardware.radio.ICloseHandle.Stub() { @@ -424,12 +435,10 @@ final class RadioModule { if (id == 0) throw new IllegalArgumentException("Image ID is missing"); byte[] rawImage; - synchronized (mLock) { - List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id)); - rawImage = new byte[rawList.size()]; - for (int i = 0; i < rawList.size(); i++) { - rawImage[i] = rawList.get(i); - } + List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id)); + rawImage = new byte[rawList.size()]; + for (int i = 0; i < rawList.size(); i++) { + rawImage[i] = rawList.get(i); } if (rawImage == null || rawImage.length == 0) return null; @@ -440,17 +449,17 @@ final class RadioModule { void dumpInfo(IndentingPrintWriter pw) { pw.printf("RadioModule\n"); pw.increaseIndent(); + pw.printf("BroadcastRadioService: %s\n", mService); + pw.printf("Properties: %s\n", mProperties); synchronized (mLock) { - pw.printf("BroadcastRadioService: %s\n", mService); - pw.printf("Properties: %s\n", mProperties); - pw.printf("HIDL2.0 HAL TunerSession: %s\n", mHalTunerSession); + pw.printf("HIDL 2.0 HAL TunerSession: %s\n", mHalTunerSession); pw.printf("Is antenna connected? "); if (mAntennaConnected == null) { pw.printf("null\n"); } else { pw.printf("%s\n", mAntennaConnected ? "Yes" : "No"); } - pw.printf("current ProgramInfo: %s\n", mCurrentProgramInfo); + pw.printf("Current ProgramInfo: %s\n", mCurrentProgramInfo); pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache); pw.printf("Union of AIDL ProgramFilters: %s\n", mUnionOfAidlProgramFilters); pw.printf("AIDL TunerSessions:\n"); diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java index 12211eed47fd..7afee277fe1c 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java @@ -33,6 +33,7 @@ import android.util.MutableBoolean; import android.util.MutableInt; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.server.broadcastradio.RadioServiceUserController; import com.android.server.utils.Slogf; @@ -46,26 +47,28 @@ class TunerSession extends ITuner.Stub { private static final String kAudioDeviceName = "Radio tuner source"; private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25; - private final Object mLock; + private final Object mLock = new Object(); @NonNull private final RadioEventLogger mEventLogger; private final RadioModule mModule; private final ITunerSession mHwSession; final android.hardware.radio.ITunerCallback mCallback; + + @GuardedBy("mLock") private boolean mIsClosed = false; + @GuardedBy("mLock") private boolean mIsMuted = false; + @GuardedBy("mLock") private ProgramInfoCache mProgramInfoCache = null; // necessary only for older APIs compatibility private RadioManager.BandConfig mDummyConfig = null; TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession, - @NonNull android.hardware.radio.ITunerCallback callback, - @NonNull Object lock) { + @NonNull android.hardware.radio.ITunerCallback callback) { mModule = Objects.requireNonNull(module); mHwSession = Objects.requireNonNull(hwSession); mCallback = Objects.requireNonNull(callback); - mLock = Objects.requireNonNull(lock); mEventLogger = new RadioEventLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE); } @@ -86,23 +89,26 @@ class TunerSession extends ITuner.Stub { mEventLogger.logRadioEvent("Close on error %d", error); synchronized (mLock) { if (mIsClosed) return; - if (error != null) { - try { - mCallback.onError(error); - } catch (RemoteException ex) { - Slog.w(TAG, "mCallback.onError() failed: ", ex); - } - } mIsClosed = true; - mModule.onTunerSessionClosed(this); } + if (error != null) { + try { + mCallback.onError(error); + } catch (RemoteException ex) { + Slog.w(TAG, "mCallback.onError() failed: ", ex); + } + } + mModule.onTunerSessionClosed(this); } @Override public boolean isClosed() { - return mIsClosed; + synchronized (mLock) { + return mIsClosed; + } } + @GuardedBy("mLock") private void checkNotClosedLocked() { if (mIsClosed) { throw new IllegalStateException("Tuner is closed, no further operations are allowed"); @@ -118,9 +124,9 @@ class TunerSession extends ITuner.Stub { synchronized (mLock) { checkNotClosedLocked(); mDummyConfig = Objects.requireNonNull(config); - Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0"); - mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config)); } + Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0"); + mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config)); } @Override @@ -137,8 +143,8 @@ class TunerSession extends ITuner.Stub { checkNotClosedLocked(); if (mIsMuted == mute) return; mIsMuted = mute; - Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app"); } + Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app"); } @Override @@ -383,8 +389,8 @@ class TunerSession extends ITuner.Stub { void dumpInfo(IndentingPrintWriter pw) { pw.printf("TunerSession\n"); pw.increaseIndent(); + pw.printf("HIDL HAL Session: %s\n", mHwSession); synchronized (mLock) { - pw.printf("HIDL HAL Session: %s\n", mHwSession); pw.printf("Is session closed? %s\n", mIsClosed ? "Yes" : "No"); pw.printf("Is muted? %s\n", mIsMuted ? "Yes" : "No"); pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache); 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 000000000000..06b45bf0fb4b --- /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 000000000000..a3c4c9e828b4 --- /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/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java new file mode 100644 index 000000000000..680829d2bfc3 --- /dev/null +++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java @@ -0,0 +1,453 @@ +/* + * 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.annotation.Nullable; +import android.system.Os; +import android.system.OsConstants; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.utils.Slogf; + +import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** Reader to read CPU information from proc and sys fs files exposed by the Kernel. */ +public final class CpuInfoReader { + static final String TAG = CpuInfoReader.class.getSimpleName(); + static final int FLAG_CPUSET_CATEGORY_TOP_APP = 1 << 0; + static final int FLAG_CPUSET_CATEGORY_BACKGROUND = 1 << 1; + + private static final String CPUFREQ_DIR_PATH = "/sys/devices/system/cpu/cpufreq"; + private static final String POLICY_DIR_PREFIX = "policy"; + private static final String RELATED_CPUS_FILE = "related_cpus"; + private static final String MAX_CPUFREQ_FILE = "cpuinfo_max_freq"; + private static final String MAX_SCALING_FREQ_FILE = "scaling_max_freq"; + private static final String CPUSET_DIR_PATH = "/dev/cpuset"; + private static final String CPUSET_TOP_APP_DIR = "top-app"; + private static final String CPUSET_BACKGROUND_DIR = "background"; + private static final String CPUS_FILE = "cpus"; + private static final String PROC_STAT_FILE_PATH = "/proc/stat"; + private static final Pattern PROC_STAT_PATTERN = + Pattern.compile("cpu(?<core>[0-9]+)\\s(?<userClockTicks>[0-9]+)\\s" + + "(?<niceClockTicks>[0-9]+)\\s(?<sysClockTicks>[0-9]+)\\s" + + "(?<idleClockTicks>[0-9]+)\\s(?<iowaitClockTicks>[0-9]+)\\s" + + "(?<irqClockTicks>[0-9]+)\\s(?<softirqClockTicks>[0-9]+)\\s" + + "(?<stealClockTicks>[0-9]+)\\s(?<guestClockTicks>[0-9]+)\\s" + + "(?<guestNiceClockTicks>[0-9]+)"); + private static final long MILLIS_PER_JIFFY = 1000L / Os.sysconf(OsConstants._SC_CLK_TCK); + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"FLAG_CPUSET_CATEGORY_"}, flag = true, value = { + FLAG_CPUSET_CATEGORY_TOP_APP, + FLAG_CPUSET_CATEGORY_BACKGROUND + }) + private @interface CpusetCategory{} + + private final File mCpusetDir; + private final File mCpuFreqDir; + private final File mProcStatFile; + private final SparseIntArray mCpusetCategoriesByCpus = new SparseIntArray(); + private final SparseArray<Long> mMaxCpuFrequenciesByCpus = new SparseArray<>(); + + private File[] mCpuFreqPolicyDirs; + private SparseArray<CpuUsageStats> mCumulativeCpuUsageStats = new SparseArray<>(); + private boolean mIsEnabled; + + public CpuInfoReader() { + this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH)); + } + + @VisibleForTesting + CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile) { + mCpusetDir = cpusetDir; + mCpuFreqDir = cpuFreqDir; + mProcStatFile = procStatFile; + } + + /** Inits CpuInfoReader and returns a boolean to indicate whether the reader is enabled. */ + public boolean init() { + mCpuFreqPolicyDirs = mCpuFreqDir.listFiles( + file -> file.isDirectory() && file.getName().startsWith(POLICY_DIR_PREFIX)); + if (mCpuFreqPolicyDirs == null || mCpuFreqPolicyDirs.length == 0) { + Slogf.w(TAG, "Missing CPU frequency policy directories at %s", + mCpuFreqDir.getAbsolutePath()); + return false; + } + if (!mProcStatFile.exists()) { + Slogf.e(TAG, "Missing proc stat file at %s", mProcStatFile.getAbsolutePath()); + return false; + } + readCpusetCategories(); + if (mCpusetCategoriesByCpus.size() == 0) { + Slogf.e(TAG, "Failed to read cpuset information read from %s", + mCpusetDir.getAbsolutePath()); + return false; + } + readMaxCpuFrequencies(); + if (mMaxCpuFrequenciesByCpus.size() == 0) { + Slogf.e(TAG, "Failed to read max CPU frequencies from policy directories at %s", + mCpuFreqDir.getAbsolutePath()); + return false; + } + mIsEnabled = true; + return true; + } + + /** Reads CPU information from proc and sys fs files exposed by the Kernel. */ + public List<CpuInfo> readCpuInfos() { + if (!mIsEnabled) { + return Collections.emptyList(); + } + SparseArray<CpuUsageStats> latestCpuUsageStats = readLatestCpuUsageStats(); + if (latestCpuUsageStats == null) { + Slogf.e(TAG, "Failed to read latest CPU usage stats"); + return Collections.emptyList(); + } + // TODO(b/217422127): Read current CPU frequencies and populate the CpuInfo. + return Collections.emptyList(); + } + + private void readCpusetCategories() { + File[] cpusetDirs = mCpusetDir.listFiles(File::isDirectory); + if (cpusetDirs == null) { + Slogf.e(TAG, "Missing cpuset directories at %s", mCpusetDir.getAbsolutePath()); + return; + } + for (int i = 0; i < cpusetDirs.length; i++) { + File dir = cpusetDirs[i]; + @CpusetCategory int cpusetCategory; + switch (dir.getName()) { + case CPUSET_TOP_APP_DIR: + cpusetCategory = FLAG_CPUSET_CATEGORY_TOP_APP; + break; + case CPUSET_BACKGROUND_DIR: + cpusetCategory = FLAG_CPUSET_CATEGORY_BACKGROUND; + break; + default: + continue; + } + File cpuCoresFile = new File(dir.getPath(), CPUS_FILE); + List<Integer> cpuCores = readCpuCores(cpuCoresFile); + if (cpuCores.isEmpty()) { + Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath()); + continue; + } + for (int j = 0; j < cpuCores.size(); j++) { + int categories = mCpusetCategoriesByCpus.get(cpuCores.get(j)); + categories |= cpusetCategory; + mCpusetCategoriesByCpus.append(cpuCores.get(j), categories); + } + } + } + + private void readMaxCpuFrequencies() { + for (int i = 0; i < mCpuFreqPolicyDirs.length; i++) { + File policyDir = mCpuFreqPolicyDirs[i]; + long maxCpuFreqKHz = readMaxCpuFrequency(policyDir); + if (maxCpuFreqKHz == 0) { + Slogf.w(TAG, "Invalid max CPU frequency read from %s", policyDir.getAbsolutePath()); + continue; + } + File cpuCoresFile = new File(policyDir, RELATED_CPUS_FILE); + List<Integer> cpuCores = readCpuCores(cpuCoresFile); + if (cpuCores.isEmpty()) { + Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath()); + continue; + } + for (int j = 0; j < cpuCores.size(); j++) { + mMaxCpuFrequenciesByCpus.append(cpuCores.get(j), maxCpuFreqKHz); + } + } + } + + private long readMaxCpuFrequency(File policyDir) { + long curCpuFreqKHz = readCpuFreqKHz(new File(policyDir, MAX_CPUFREQ_FILE)); + return curCpuFreqKHz > 0 ? curCpuFreqKHz + : readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE)); + } + + private static long readCpuFreqKHz(File file) { + if (!file.exists()) { + Slogf.e(TAG, "CPU frequency file %s doesn't exist", file.getAbsolutePath()); + return 0; + } + try { + List<String> lines = Files.readAllLines(file.toPath()); + if (!lines.isEmpty()) { + long frequency = Long.parseLong(lines.get(0).trim()); + return frequency > 0 ? frequency : 0; + } + } catch (Exception e) { + Slogf.e(TAG, e, "Failed to read integer content from file: %s", file.getAbsolutePath()); + } + return 0; + } + + /** + * Reads the list of CPU cores from the given file. + * + * Reads CPU cores represented in one of the below formats. + * <ul> + * <li> Single core id. Eg: 1 + * <li> Core id range. Eg: 1-4 + * <li> Comma separated values. Eg: 1, 3-5, 7 + * </ul> + */ + private static List<Integer> readCpuCores(File file) { + if (!file.exists()) { + Slogf.e(TAG, "Failed to read CPU cores as the file '%s' doesn't exist", + file.getAbsolutePath()); + return Collections.emptyList(); + } + try { + List<String> lines = Files.readAllLines(file.toPath()); + List<Integer> cpuCores = new ArrayList<>(); + for (int i = 0; i < lines.size(); i++) { + String[] pairs = lines.get(i).trim().split(","); + for (int j = 0; j < pairs.length; j++) { + String[] minMaxPairs = pairs[j].split("-"); + if (minMaxPairs.length >= 2) { + int min = Integer.parseInt(minMaxPairs[0]); + int max = Integer.parseInt(minMaxPairs[1]); + if (min > max) { + continue; + } + for (int id = min; id <= max; id++) { + cpuCores.add(id); + } + } else if (minMaxPairs.length == 1) { + cpuCores.add(Integer.parseInt(minMaxPairs[0])); + } else { + Slogf.w(TAG, "Invalid CPU core range format %s", pairs[j]); + } + } + } + return cpuCores; + } catch (Exception e) { + Slogf.e(TAG, e, "Failed to read CPU cores from %s", file.getAbsolutePath()); + } + return Collections.emptyList(); + } + + @Nullable + private SparseArray<CpuUsageStats> readLatestCpuUsageStats() { + SparseArray<CpuUsageStats> cumulativeCpuUsageStats = readCumulativeCpuUsageStats(); + if (cumulativeCpuUsageStats.size() == 0) { + Slogf.e(TAG, "Failed to read cumulative CPU usage stats"); + return null; + } + SparseArray<CpuUsageStats> deltaCpuUsageStats = new SparseArray(); + for (int i = 0; i < cumulativeCpuUsageStats.size(); i++) { + int cpu = cumulativeCpuUsageStats.keyAt(i); + CpuUsageStats newStats = cumulativeCpuUsageStats.valueAt(i); + CpuUsageStats oldStats = mCumulativeCpuUsageStats.get(cpu); + deltaCpuUsageStats.append(cpu, oldStats == null ? newStats : newStats.delta(oldStats)); + } + mCumulativeCpuUsageStats = cumulativeCpuUsageStats; + return deltaCpuUsageStats; + } + + private SparseArray<CpuUsageStats> readCumulativeCpuUsageStats() { + SparseArray<CpuUsageStats> cpuUsageStats = new SparseArray<>(); + try { + List<String> lines = Files.readAllLines(mProcStatFile.toPath()); + for (int i = 0; i < lines.size(); i++) { + Matcher m = PROC_STAT_PATTERN.matcher(lines.get(i).trim()); + if (!m.find()) { + continue; + } + cpuUsageStats.append(Integer.parseInt(Objects.requireNonNull(m.group("core"))), + new CpuUsageStats(jiffyStrToMillis(m.group("userClockTicks")), + jiffyStrToMillis(m.group("niceClockTicks")), + jiffyStrToMillis(m.group("sysClockTicks")), + jiffyStrToMillis(m.group("idleClockTicks")), + jiffyStrToMillis(m.group("iowaitClockTicks")), + jiffyStrToMillis(m.group("irqClockTicks")), + jiffyStrToMillis(m.group("softirqClockTicks")), + jiffyStrToMillis(m.group("stealClockTicks")), + jiffyStrToMillis(m.group("guestClockTicks")), + jiffyStrToMillis(m.group("guestNiceClockTicks")))); + } + } catch (Exception e) { + Slogf.e(TAG, e, "Failed to read cpu usage stats from %s", + mProcStatFile.getAbsolutePath()); + } + return cpuUsageStats; + } + + private static long jiffyStrToMillis(String jiffyStr) { + return Long.parseLong(Objects.requireNonNull(jiffyStr)) * MILLIS_PER_JIFFY; + } + + /** Contains information for each CPU core on the system. */ + public static final class CpuInfo { + public final int cpuCore; + public final @CpusetCategory int cpusetCategories; + public final long curCpuFreqKHz; + public final long maxCpuFreqKHz; + public final CpuUsageStats latestCpuUsageStats; + + CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, long curCpuFreqKHz, + long maxCpuFreqKHz, CpuUsageStats latestCpuUsageStats) { + this.cpuCore = cpuCore; + this.cpusetCategories = cpusetCategories; + this.curCpuFreqKHz = curCpuFreqKHz; + this.maxCpuFreqKHz = maxCpuFreqKHz; + this.latestCpuUsageStats = latestCpuUsageStats; + } + + @Override + public String toString() { + return new StringBuilder("CpuInfo{ cpuCore = ").append(cpuCore) + .append(", cpusetCategories = ").append(cpusetCategories) + .append(", curCpuFreqKHz = ").append(curCpuFreqKHz) + .append(", maxCpuFreqKHz = ").append(maxCpuFreqKHz) + .append(", latestCpuUsageStats = ").append(latestCpuUsageStats) + .append(" }").toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof CpuInfo)) { + return false; + } + CpuInfo other = (CpuInfo) obj; + return cpuCore == other.cpuCore && cpusetCategories == other.cpusetCategories + && curCpuFreqKHz == other.curCpuFreqKHz + && maxCpuFreqKHz == other.maxCpuFreqKHz + && latestCpuUsageStats.equals(other.latestCpuUsageStats); + } + + @Override + public int hashCode() { + return Objects.hash(cpuCore, cpusetCategories, curCpuFreqKHz, maxCpuFreqKHz, + latestCpuUsageStats); + } + } + + /** CPU time spent in different modes. */ + public static final class CpuUsageStats { + public final long userTimeMillis; + public final long niceTimeMillis; + public final long systemTimeMillis; + public final long idleTimeMillis; + public final long iowaitTimeMillis; + public final long irqTimeMillis; + public final long softirqTimeMillis; + public final long stealTimeMillis; + public final long guestTimeMillis; + public final long guestNiceTimeMillis; + + public CpuUsageStats(long userTimeMillis, long niceTimeMillis, long systemTimeMillis, + long idleTimeMillis, long iowaitTimeMillis, long irqTimeMillis, + long softirqTimeMillis, long stealTimeMillis, long guestTimeMillis, + long guestNiceTimeMillis) { + this.userTimeMillis = userTimeMillis; + this.niceTimeMillis = niceTimeMillis; + this.systemTimeMillis = systemTimeMillis; + this.idleTimeMillis = idleTimeMillis; + this.iowaitTimeMillis = iowaitTimeMillis; + this.irqTimeMillis = irqTimeMillis; + this.softirqTimeMillis = softirqTimeMillis; + this.stealTimeMillis = stealTimeMillis; + this.guestTimeMillis = guestTimeMillis; + this.guestNiceTimeMillis = guestNiceTimeMillis; + } + + public long getTotalTime() { + return userTimeMillis + niceTimeMillis + systemTimeMillis + idleTimeMillis + + iowaitTimeMillis + irqTimeMillis + softirqTimeMillis + stealTimeMillis + + guestTimeMillis + guestNiceTimeMillis; + } + + @Override + public String toString() { + return new StringBuilder("CpuUsageStats{ userTimeMillis = ") + .append(userTimeMillis) + .append(", niceTimeMillis = ").append(niceTimeMillis) + .append(", systemTimeMillis = ").append(systemTimeMillis) + .append(", idleTimeMillis = ").append(idleTimeMillis) + .append(", iowaitTimeMillis = ").append(iowaitTimeMillis) + .append(", irqTimeMillis = ").append(irqTimeMillis) + .append(", softirqTimeMillis = ").append(softirqTimeMillis) + .append(", stealTimeMillis = ").append(stealTimeMillis) + .append(", guestTimeMillis = ").append(guestTimeMillis) + .append(", guestNiceTimeMillis = ").append(guestNiceTimeMillis) + .append(" }").toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof CpuUsageStats)) { + return false; + } + CpuUsageStats other = (CpuUsageStats) obj; + return userTimeMillis == other.userTimeMillis && niceTimeMillis == other.niceTimeMillis + && systemTimeMillis == other.systemTimeMillis + && idleTimeMillis == other.idleTimeMillis + && iowaitTimeMillis == other.iowaitTimeMillis + && irqTimeMillis == other.irqTimeMillis + && softirqTimeMillis == other.softirqTimeMillis + && stealTimeMillis == other.stealTimeMillis + && guestTimeMillis == other.guestTimeMillis + && guestNiceTimeMillis == other.guestNiceTimeMillis; + } + + @Override + public int hashCode() { + return Objects.hash(userTimeMillis, niceTimeMillis, systemTimeMillis, idleTimeMillis, + iowaitTimeMillis, irqTimeMillis, softirqTimeMillis, stealTimeMillis, + guestTimeMillis, + guestNiceTimeMillis); + } + + CpuUsageStats delta(CpuUsageStats rhs) { + return new CpuUsageStats(diff(userTimeMillis, rhs.userTimeMillis), + diff(niceTimeMillis, rhs.niceTimeMillis), + diff(systemTimeMillis, rhs.systemTimeMillis), + diff(idleTimeMillis, rhs.idleTimeMillis), + diff(iowaitTimeMillis, rhs.iowaitTimeMillis), + diff(irqTimeMillis, rhs.irqTimeMillis), + diff(softirqTimeMillis, rhs.softirqTimeMillis), + diff(stealTimeMillis, rhs.stealTimeMillis), + diff(guestTimeMillis, rhs.guestTimeMillis), + diff(guestNiceTimeMillis, rhs.guestNiceTimeMillis)); + } + + private static long diff(long lhs, long rhs) { + return lhs > rhs ? lhs - rhs : 0; + } + } +} 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 000000000000..849a20be0cf8 --- /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 000000000000..b0dfb8467fa7 --- /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 a0cbd7fb60a0..881199964d45 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.Display; import android.view.DisplayAddress; import android.view.DisplayCutout; import android.view.DisplayEventReceiver; +import android.view.DisplayShape; import android.view.RoundedCorners; import android.view.Surface; @@ -316,6 +317,11 @@ final class DisplayDeviceInfo { 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; @@ -451,7 +457,8 @@ final class DisplayDeviceInfo { || !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; @@ -497,6 +504,7 @@ final class DisplayDeviceInfo { brightnessDefault = other.brightnessDefault; roundedCorners = other.roundedCorners; installOrientation = other.installOrientation; + displayShape = other.displayShape; } // For debugging purposes @@ -546,6 +554,9 @@ final class DisplayDeviceInfo { } sb.append(flagsToString(flags)); sb.append(", installOrientation ").append(installOrientation); + if (displayShape != null) { + sb.append(", displayShape ").append(displayShape); + } sb.append("}"); return sb.toString(); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 05cd67f2f808..1d04f2ef99c0 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.UserHandle; 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 @@ public final class DisplayManagerService extends SystemService { } }; - private final boolean mAllowNonNativeRefreshRateOverride; - private final BrightnessSynchronizer mBrightnessSynchronizer; /** @@ -506,7 +503,6 @@ public final class DisplayManagerService extends SystemService { ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces(); mWideColorSpace = colorSpaces[1]; mOverlayProperties = SurfaceControl.getOverlaySupport(); - mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride(); mSystemReady = false; } @@ -930,24 +926,20 @@ public final class DisplayManagerService extends SystemService { } } - 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) { @@ -1581,7 +1573,7 @@ public final class DisplayManagerService extends SystemService { mSyncRoot.notifyAll(); } - sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); Runnable work = updateDisplayStateLocked(device); if (work != null) { @@ -1600,7 +1592,7 @@ public final class DisplayManagerService extends SystemService { // We don't bother invalidating the display info caches here because any changes to the // display info will trigger a cache invalidation inside of LogicalDisplay before we hit // this point. - sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); scheduleTraversalLocked(false); mPersistentDataStore.saveIfNeeded(); @@ -1630,7 +1622,7 @@ public final class DisplayManagerService extends SystemService { mDisplayStates.delete(displayId); mDisplayBrightnesses.delete(displayId); DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); - sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); scheduleTraversalLocked(false); if (mDisplayWindowPolicyControllers.contains(displayId)) { @@ -1646,23 +1638,13 @@ public final class DisplayManagerService extends SystemService { } private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) { - final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); - final Runnable work = updateDisplayStateLocked(device); - if (work != null) { - mHandler.post(work); - } - final int displayId = display.getDisplayIdLocked(); + handleLogicalDisplayChangedLocked(display); + final int displayId = display.getDisplayIdLocked(); if (displayId == Display.DEFAULT_DISPLAY) { notifyDefaultDisplayDeviceUpdated(display); } - DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); - if (dpc != null) { - dpc.onDisplayChanged(); - } - mPersistentDataStore.saveIfNeeded(); mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS); - handleLogicalDisplayChangedLocked(display); } private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) { @@ -1674,7 +1656,7 @@ public final class DisplayManagerService extends SystemService { final int displayId = display.getDisplayIdLocked(); final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { - dpc.onDeviceStateTransition(); + dpc.onDisplayChanged(); } } @@ -2374,9 +2356,13 @@ public final class DisplayManagerService extends SystemService { } } - private void sendDisplayEventLocked(int displayId, @DisplayEvent int event) { - Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); - mHandler.sendMessage(msg); + private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) { + // Only send updates outside of DisplayManagerService for enabled displays + if (display.isEnabledLocked()) { + int displayId = display.getDisplayIdLocked(); + Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); + mHandler.sendMessage(msg); + } } private void sendDisplayGroupEvent(int groupId, int event) { @@ -2602,11 +2588,6 @@ public final class DisplayManagerService extends SystemService { long getDefaultDisplayDelayTimeout() { return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT; } - - boolean getAllowNonNativeRefreshRateOverride() { - return DisplayProperties - .debug_allow_non_native_refresh_rate_override().orElse(true); - } } @VisibleForTesting @@ -2666,8 +2647,7 @@ public final class DisplayManagerService extends SystemService { } private void handleBrightnessChange(LogicalDisplay display) { - sendDisplayEventLocked(display.getDisplayIdLocked(), - DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED); + sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED); } private DisplayDevice getDeviceForDisplayLocked(int displayId) { @@ -2884,12 +2864,12 @@ public final class DisplayManagerService extends SystemService { * Returns the list of all display ids. */ @Override // Binder call - public int[] getDisplayIds() { + public int[] getDisplayIds(boolean includeDisabled) { final int callingUid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { - return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid); + return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid, includeDisabled); } } finally { Binder.restoreCallingIdentity(token); @@ -3380,6 +3360,11 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked( + displayId, /* includeDisabled= */ false); + if (display == null || !display.isEnabledLocked()) { + return null; + } DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { return dpc.getBrightnessInfo(); diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 306b8cf4e0ee..40e7c5062a77 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -16,6 +16,7 @@ package com.android.server.display; +import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED; import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE; import static android.os.PowerManager.BRIGHTNESS_INVALID; @@ -1640,7 +1641,7 @@ public class DisplayModeDirector { SparseArray<Display.Mode[]> modes = new SparseArray<>(); SparseArray<Display.Mode> defaultModes = new SparseArray<>(); DisplayInfo info = new DisplayInfo(); - Display[] displays = dm.getDisplays(); + Display[] displays = dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); for (Display d : displays) { final int displayId = d.getDisplayId(); d.getDisplayInfo(info); @@ -2517,7 +2518,8 @@ public class DisplayModeDirector { sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); synchronized (mSensorObserverLock) { - for (Display d : mDisplayManager.getDisplays()) { + for (Display d : mDisplayManager.getDisplays( + DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) { mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); } } @@ -2528,7 +2530,8 @@ public class DisplayModeDirector { } private void recalculateVotesLocked() { - final Display[] displays = mDisplayManager.getDisplays(); + final Display[] displays = mDisplayManager.getDisplays( + DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); for (Display d : displays) { int displayId = d.getDisplayId(); Vote vote = null; diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index d6f0fd070f94..81245001eaf6 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -497,6 +497,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final String mSuspendBlockerIdProxNegative; private final String mSuspendBlockerIdProxDebounce; + private boolean mIsEnabled; + private boolean mIsInTransition; + /** * Creates the display power controller. */ @@ -520,6 +523,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); mDisplayStatsId = mUniqueDisplayId.hashCode(); + mIsEnabled = logicalDisplay.isEnabledLocked(); + mIsInTransition = logicalDisplay.isInTransitionLocked(); mHandler = new DisplayControllerHandler(handler.getLooper()); mLastBrightnessEvent = new BrightnessEvent(mDisplayId); mTempBrightnessEvent = new BrightnessEvent(mDisplayId); @@ -807,29 +812,36 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final DisplayDeviceConfig config = device.getDisplayDeviceConfig(); final IBinder token = device.getDisplayTokenLocked(); final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + final boolean isEnabled = mLogicalDisplay.isEnabledLocked(); + final boolean isInTransition = mLogicalDisplay.isInTransitionLocked(); mHandler.post(() -> { + boolean changed = false; if (mDisplayDevice != device) { + changed = true; mDisplayDevice = device; mUniqueDisplayId = uniqueId; mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; loadFromDisplayDeviceConfig(token, info); + + // Since the underlying display-device changed, we really don't know the + // last command that was sent to change it's state. Lets assume it is unknown so + // that we trigger a change immediately. + mPowerState.resetScreenState(); + } + if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { + changed = true; + mIsEnabled = isEnabled; + mIsInTransition = isInTransition; + } + + if (changed) { updatePowerState(); } }); } /** - * Called when the displays are preparing to transition from one device state to another. - * This process involves turning off some displays so we need updatePowerState() to run and - * calculate the new state. - */ - @Override - public void onDeviceStateTransition() { - sendUpdatePowerState(); - } - - /** * Unregisters all listeners and interrupts all running threads; halting future work. * * This method should be called when the DisplayPowerController is no longer in use; i.e. when @@ -1316,8 +1328,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mIgnoreProximityUntilChanged = false; } - if (!mLogicalDisplay.isEnabled() - || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION + if (!mIsEnabled + || mIsInTransition || mScreenOffBecauseOfProximity) { state = Display.STATE_OFF; } diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 1f58a1c40fd8..9a594e8e059e 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -422,6 +422,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private DisplayDeviceConfig mDisplayDeviceConfig; + private boolean mIsEnabled; + private boolean mIsInTransition; /** * Creates the display power controller. */ @@ -439,6 +441,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mHandler = new DisplayControllerHandler(handler.getLooper()); mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked() .getDisplayDeviceConfig(); + mIsEnabled = logicalDisplay.isEnabledLocked(); + mIsInTransition = logicalDisplay.isInTransitionLocked(); mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks); mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController( mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(), @@ -721,30 +725,37 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal final DisplayDeviceConfig config = device.getDisplayDeviceConfig(); final IBinder token = device.getDisplayTokenLocked(); final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + final boolean isEnabled = mLogicalDisplay.isEnabledLocked(); + final boolean isInTransition = mLogicalDisplay.isInTransitionLocked(); mHandler.post(() -> { + boolean changed = false; if (mDisplayDevice != device) { + changed = true; mDisplayDevice = device; mUniqueDisplayId = uniqueId; mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; loadFromDisplayDeviceConfig(token, info); mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config); + + // Since the underlying display-device changed, we really don't know the + // last command that was sent to change it's state. Lets assume it is unknown so + // that we trigger a change immediately. + mPowerState.resetScreenState(); + } + if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { + changed = true; + mIsEnabled = isEnabled; + mIsInTransition = isInTransition; + } + + if (changed) { updatePowerState(); } }); } /** - * Called when the displays are preparing to transition from one device state to another. - * This process involves turning off some displays so we need updatePowerState() to run and - * calculate the new state. - */ - @Override - public void onDeviceStateTransition() { - sendUpdatePowerState(); - } - - /** * Unregisters all listeners and interrupts all running threads; halting future work. * * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when @@ -1165,8 +1176,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mDisplayPowerProximityStateController.updateProximityState(mPowerRequest, state); - if (!mLogicalDisplay.isEnabled() - || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION + if (!mIsEnabled + || mIsInTransition || mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) { state = Display.STATE_OFF; } diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java index 6677f358557d..46f1343ceeb8 100644 --- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java +++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java @@ -45,11 +45,6 @@ public interface DisplayPowerControllerInterface { void stop(); /** - * Used to manage the displays preparing to transition from one device state to another. - */ - void onDeviceStateTransition(); - - /** * Used to update the display's BrightnessConfiguration * @param config The new BrightnessConfiguration */ diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index 2f22d33f552a..f650b118b815 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -145,7 +145,7 @@ final class DisplayPowerState { public void setScreenState(int state) { if (mScreenState != state) { if (DEBUG) { - Slog.d(TAG, "setScreenState: state=" + state); + Slog.w(TAG, "setScreenState: state=" + Display.stateToString(state)); } mScreenState = state; @@ -339,6 +339,15 @@ final class DisplayPowerState { if (mColorFade != null) mColorFade.dump(pw); } + /** + * Resets the screen state to unknown. Useful when the underlying display-device changes for the + * LogicalDisplay and we do not know the last state that was sent to it. + */ + void resetScreenState() { + mScreenState = Display.STATE_UNKNOWN; + mScreenReady = false; + } + private void scheduleScreenUpdate() { if (!mScreenUpdatePending) { mScreenUpdatePending = true; diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 5a714f59485c..4bf1e98f99a5 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.Display; 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 @@ final class LocalDisplayAdapter extends DisplayAdapter { 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 26ac528e519c..8dd169bf4bf6 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -18,7 +18,6 @@ package com.android.server.display; import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Point; @@ -68,33 +67,6 @@ import java.util.Objects; final class LogicalDisplay { private static final String TAG = "LogicalDisplay"; - /** - * Phase indicating the logical display's existence is hidden from the rest of the framework. - * This can happen if the current layout has specifically requested to keep this display - * disabled. - */ - static final int DISPLAY_PHASE_DISABLED = -1; - - /** - * Phase indicating that the logical display is going through a layout transition. - * When in this phase, other systems can choose to special case power-state handling of a - * display that might be in a transition. - */ - static final int DISPLAY_PHASE_LAYOUT_TRANSITION = 0; - - /** - * The display is exposed to the rest of the system and its power state is determined by a - * power-request from PowerManager. - */ - static final int DISPLAY_PHASE_ENABLED = 1; - - @IntDef(prefix = {"DISPLAY_PHASE" }, value = { - DISPLAY_PHASE_DISABLED, - DISPLAY_PHASE_LAYOUT_TRANSITION, - DISPLAY_PHASE_ENABLED - }) - @interface DisplayPhase {} - // The layer stack we use when the display has been blanked to prevent any // of its content from appearing. private static final int BLANK_LAYER_STACK = -1; @@ -159,14 +131,6 @@ final class LogicalDisplay { private final Rect mTempDisplayRect = new Rect(); /** - * Indicates the current phase of the display. Generally, phases supersede any - * requests from PowerManager in DPC's calculation for the display state. Only when the - * phase is ENABLED does PowerManager's request for the display take effect. - */ - @DisplayPhase - private int mPhase = DISPLAY_PHASE_ENABLED; - - /** * The UID mappings for refresh rate override */ private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides; @@ -181,12 +145,22 @@ final class LogicalDisplay { */ private final SparseArray<Float> mTempFrameRateOverride; + // Indicates the display is enabled (allowed to be ON). + private boolean mIsEnabled; + + // Indicates the display is part of a transition from one device-state ({@link + // DeviceStateManager}) to another. Being a "part" of a transition means that either + // the {@link mIsEnabled} is changing, or the underlying mPrimiaryDisplayDevice is changing. + private boolean mIsInTransition; + public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { mDisplayId = displayId; mLayerStack = layerStack; mPrimaryDisplayDevice = primaryDisplayDevice; mPendingFrameRateOverrideUids = new ArraySet<>(); mTempFrameRateOverride = new SparseArray<>(); + mIsEnabled = true; + mIsInTransition = false; } /** @@ -233,6 +207,7 @@ final class LogicalDisplay { info.displayCutout = mOverrideDisplayInfo.displayCutout; info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi; info.roundedCorners = mOverrideDisplayInfo.roundedCorners; + info.displayShape = mOverrideDisplayInfo.displayShape; } mInfo.set(info); } @@ -437,6 +412,7 @@ final class LogicalDisplay { mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault; mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners; mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation; + mBaseDisplayInfo.displayShape = deviceInfo.displayShape; mPrimaryDisplayDeviceInfo = deviceInfo; mInfo.set(null); } @@ -529,7 +505,7 @@ final class LogicalDisplay { // Prevent displays that are disabled from receiving input. // TODO(b/188914255): Remove once input can dispatch against device vs layerstack. device.setDisplayFlagsLocked(t, - (isEnabled() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE) + (isEnabledLocked() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE) ? SurfaceControl.DISPLAY_RECEIVES_INPUT : 0); @@ -771,32 +747,45 @@ final class LogicalDisplay { return old; } - public void setPhase(@DisplayPhase int phase) { - mPhase = phase; + /** + * @return {@code true} if the LogicalDisplay is enabled or {@code false} + * if disabled indicating that the display should be hidden from the rest of the apps and + * framework. + */ + public boolean isEnabledLocked() { + return mIsEnabled; + } + + /** + * Sets the display as enabled. + * + * @param enable True if enabled, false otherwise. + */ + public void setEnabledLocked(boolean enabled) { + mIsEnabled = enabled; } /** - * Returns the currently set phase for this LogicalDisplay. Phases are used when transitioning - * from one device state to another. {@see LogicalDisplayMapper}. + * @return {@code true} if the LogicalDisplay is in a transition phase. This is used to indicate + * that we are getting ready to swap the underlying display-device and the display should be + * rendered appropriately to reduce jank. */ - @DisplayPhase - public int getPhase() { - return mPhase; + public boolean isInTransitionLocked() { + return mIsInTransition; } /** - * @return {@code true} if the LogicalDisplay is enabled or {@code false} - * if disabled indicating that the display should be hidden from the rest of the apps and - * framework. + * Sets the transition phase. + * @param isInTransition True if it display is in transition. */ - public boolean isEnabled() { - // DISPLAY_PHASE_LAYOUT_TRANSITION is still considered an 'enabled' phase. - return mPhase == DISPLAY_PHASE_ENABLED || mPhase == DISPLAY_PHASE_LAYOUT_TRANSITION; + public void setIsInTransitionLocked(boolean isInTransition) { + mIsInTransition = isInTransition; } public void dumpLocked(PrintWriter pw) { pw.println("mDisplayId=" + mDisplayId); - pw.println("mPhase=" + mPhase); + pw.println("mIsEnabled=" + mIsEnabled); + pw.println("mIsInTransition=" + mIsInTransition); pw.println("mLayerStack=" + mLayerStack); pw.println("mHasContent=" + mHasContent); pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}"); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index cb97e2832854..66073c2abdec 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -40,7 +40,6 @@ import android.view.DisplayAddress; import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.display.LogicalDisplay.DisplayPhase; import com.android.server.display.layout.Layout; import java.io.PrintWriter; @@ -180,6 +179,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, @NonNull Handler handler) { + this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap()); + } + + LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, + @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, + @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) { mSyncRoot = syncRoot; mPowerManager = context.getSystemService(PowerManager.class); mInteractive = mPowerManager.isInteractive(); @@ -194,7 +199,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mDeviceStatesOnWhichToSleep = toSparseBooleanArray(context.getResources().getIntArray( com.android.internal.R.array.config_deviceStatesOnWhichToSleep)); mDisplayDeviceRepo.addListener(this); - mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(); + mDeviceStateToLayoutMap = deviceStateToLayoutMap; } @Override @@ -231,10 +236,22 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } public LogicalDisplay getDisplayLocked(int displayId) { - return mLogicalDisplays.get(displayId); + return getDisplayLocked(displayId, /* includeDisabled= */ true); + } + + public LogicalDisplay getDisplayLocked(int displayId, boolean includeDisabled) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display == null || display.isEnabledLocked() || includeDisabled) { + return display; + } + return null; } public LogicalDisplay getDisplayLocked(DisplayDevice device) { + return getDisplayLocked(device, /* includeDisabled= */ true); + } + + public LogicalDisplay getDisplayLocked(DisplayDevice device, boolean includeDisabled) { if (device == null) { return null; } @@ -242,21 +259,26 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { for (int i = 0; i < count; i++) { final LogicalDisplay display = mLogicalDisplays.valueAt(i); if (display.getPrimaryDisplayDeviceLocked() == device) { - return display; + if (display.isEnabledLocked() || includeDisabled) { + return display; + } + return null; } } return null; } - public int[] getDisplayIdsLocked(int callingUid) { + public int[] getDisplayIdsLocked(int callingUid, boolean includeDisabled) { final int count = mLogicalDisplays.size(); int[] displayIds = new int[count]; int n = 0; for (int i = 0; i < count; i++) { LogicalDisplay display = mLogicalDisplays.valueAt(i); - DisplayInfo info = display.getDisplayInfoLocked(); - if (info.hasAccess(callingUid)) { - displayIds[n++] = mLogicalDisplays.keyAt(i); + if (display.isEnabledLocked() || includeDisabled) { + DisplayInfo info = display.getDisplayInfoLocked(); + if (info.hasAccess(callingUid)) { + displayIds[n++] = mLogicalDisplays.keyAt(i); + } } } if (n != count) { @@ -390,14 +412,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { void setDeviceStateLocked(int state, boolean isOverrideActive) { Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState - + ", interactive=" + mInteractive); + + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted); // As part of a state transition, we may need to turn off some displays temporarily so that // the transition is smooth. Plus, on some devices, only one internal displays can be - // on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be + // on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be // temporarily turned off. - if (mDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) { - resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION); - } + resetLayoutLocked(mDeviceState, state, /* transitionValue= */ true); mPendingDeviceState = state; final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState, mInteractive, mBootCompleted); @@ -507,7 +527,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final int count = mLogicalDisplays.size(); for (int i = 0; i < count; i++) { final LogicalDisplay display = mLogicalDisplays.valueAt(i); - if (display.getPhase() != LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) { + if (!display.isInTransitionLocked()) { continue; } @@ -523,7 +543,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } private void transitionToPendingStateLocked() { - resetLayoutLocked(mDeviceState, mPendingDeviceState, LogicalDisplay.DISPLAY_PHASE_ENABLED); + resetLayoutLocked(mDeviceState, mPendingDeviceState, /* transitionValue= */ false); mDeviceState = mPendingDeviceState; mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; applyLayoutLocked(); @@ -838,17 +858,17 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { /** * Goes through all the displays used in the layouts for the specified {@code fromState} and - * {@code toState} and applies the specified {@code phase}. When a new layout is requested, we - * put the displays that will change into a transitional phase so that they can all be turned - * OFF. Once all are confirmed OFF, then this method gets called again to reset the phase to - * normal operation. This helps to ensure that all display-OFF requests are made before + * {@code toState} and un/marks them for transition. When a new layout is requested, we + * mark the displays that will change into a transitional phase so that they can all be turned + * OFF. Once all are confirmed OFF, then this method gets called again to reset transition + * marker. This helps to ensure that all display-OFF requests are made before * display-ON which in turn hides any resizing-jank windows might incur when switching displays. * * @param fromState The state we are switching from. * @param toState The state we are switching to. - * @param phase The new phase to apply to the displays. + * @param transitionValue The value to mark the transition state: true == transitioning. */ - private void resetLayoutLocked(int fromState, int toState, @DisplayPhase int phase) { + private void resetLayoutLocked(int fromState, int toState, boolean transitionValue) { final Layout fromLayout = mDeviceStateToLayoutMap.get(fromState); final Layout toLayout = mDeviceStateToLayoutMap.get(toState); @@ -866,12 +886,16 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // new layout. final DisplayAddress address = device.getDisplayDeviceInfoLocked().address; - // Virtual displays do not have addresses. + // Virtual displays do not have addresses, so account for nulls. final Layout.Display fromDisplay = address != null ? fromLayout.getByAddress(address) : null; final Layout.Display toDisplay = address != null ? toLayout.getByAddress(address) : null; + // If the display is in one of the layouts but not the other, then the content will + // change, so in this case we also want to blank the displays to avoid jank. + final boolean displayNotInBothLayouts = (fromDisplay == null) != (toDisplay == null); + // If a layout doesn't mention a display-device at all, then the display-device defaults // to enabled. This is why we treat null as "enabled" in the code below. final boolean wasEnabled = fromDisplay == null || fromDisplay.isEnabled(); @@ -886,16 +910,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // 3) It's enabled, but it's mapped to a new logical display ID. To the user this // would look like apps moving from one screen to another since task-stacks stay // with the logical display [ID]. + // 4) It's in one layout but not the other, so the content will change. final boolean isTransitioning = - (logicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) + logicalDisplay.isInTransitionLocked() || (wasEnabled != willBeEnabled) - || deviceHasNewLogicalDisplayId; + || deviceHasNewLogicalDisplayId + || displayNotInBothLayouts; if (isTransitioning) { - setDisplayPhase(logicalDisplay, phase); - if (phase == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) { - mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION); + if (transitionValue != logicalDisplay.isInTransitionLocked()) { + Slog.i(TAG, "Set isInTransition on display " + displayId + ": " + + transitionValue); } + // This will either mark the display as "transitioning" if we are starting to change + // the device state, or remove the transitioning marker if the state change is + // ending. + logicalDisplay.setIsInTransitionLocked(transitionValue); + mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION); } } } @@ -940,9 +971,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { newDisplay.swapDisplaysLocked(oldDisplay); } - if (!displayLayout.isEnabled()) { - setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED); - } + setEnabledLocked(newDisplay, displayLayout.isEnabled()); } } @@ -961,23 +990,25 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); display.updateLocked(mDisplayDeviceRepo); mLogicalDisplays.put(displayId, display); - setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED); return display; } - private void setDisplayPhase(LogicalDisplay display, @DisplayPhase int phase) { + private void setEnabledLocked(LogicalDisplay display, boolean isEnabled) { final int displayId = display.getDisplayIdLocked(); final DisplayInfo info = display.getDisplayInfoLocked(); final boolean disallowSecondaryDisplay = mSingleDisplayDemoMode && (info.type != Display.TYPE_INTERNAL); - if (phase != LogicalDisplay.DISPLAY_PHASE_DISABLED && disallowSecondaryDisplay) { + if (isEnabled && disallowSecondaryDisplay) { Slog.i(TAG, "Not creating a logical display for a secondary display because single" + " display demo mode is enabled: " + display.getDisplayInfoLocked()); - phase = LogicalDisplay.DISPLAY_PHASE_DISABLED; + isEnabled = false; } - display.setPhase(phase); + if (display.isEnabledLocked() != isEnabled) { + Slog.i(TAG, "SetEnabled on display " + displayId + ": " + isEnabled); + display.setEnabledLocked(isEnabled); + } } private int assignDisplayGroupIdLocked( diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index b0de844389b6..0e11b53e0257 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.text.TextUtils; 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 @@ final class OverlayDisplayAdapter extends DisplayAdapter { 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 a23a073ad447..d0e518b876dd 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -52,6 +52,7 @@ import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Slog; import android.view.Display; +import android.view.DisplayShape; import android.view.Surface; import android.view.SurfaceControl; @@ -524,6 +525,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { 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 146b003650ac..c759d98561f9 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.os.UserHandle; 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 @@ final class WifiDisplayAdapter extends DisplayAdapter { 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/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index fac001e7828f..c2157a6497de 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -79,9 +79,10 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { // (requires restart) private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 1; - private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 2; - private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 3; + private static final int MSG_UPDATE_EXISTING_DEVICES = 1; + private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2; + private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3; + private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4; private final Context mContext; private final NativeInputManagerService mNative; @@ -121,10 +122,10 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { InputManager inputManager = Objects.requireNonNull( mContext.getSystemService(InputManager.class)); inputManager.registerInputDeviceListener(this, mHandler); - // Circle through all the already added input devices - for (int deviceId : inputManager.getInputDeviceIds()) { - onInputDeviceAdded(deviceId); - } + + Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES, + inputManager.getInputDeviceIds()); + mHandler.sendMessage(msg); } @Override @@ -682,6 +683,13 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { private boolean handleMessage(Message msg) { switch (msg.what) { + case MSG_UPDATE_EXISTING_DEVICES: + // Circle through all the already added input devices + // Need to do it on handler thread and not block IMS thread + for (int deviceId : (int[]) msg.obj) { + onInputDeviceAdded(deviceId); + } + return true; case MSG_SWITCH_KEYBOARD_LAYOUT: handleSwitchKeyboardLayout(msg.arg1, msg.arg2); return true; diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index 015e5768d505..c53f1a52306d 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 @@ final class IInputMethodInvoker { @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 4d67311db150..079234c2f95c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -296,7 +296,7 @@ final class InputMethodBindingController { 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(); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8b083bd72722..080d5829f9d9 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 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @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 8d247f6a89e3..3ce51c3d1412 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.GPS_PROVIDER; 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 @@ public class LocationManagerService extends ILocationManager.Stub implements 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/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index dcdb881df12b..72ce38b72340 100644 --- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -275,6 +275,10 @@ final class MediaButtonReceiverHolder { String.valueOf(mComponentType)); } + public ComponentName getComponentName() { + return mComponentName; + } + @ComponentType private static int getComponentType(PendingIntent pendingIntent) { if (pendingIntent.isBroadcast()) { diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index f3cfa95ff86d..b8bdabe365f8 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 @@ public class AppDataHelper { 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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 78e419078bde..5d01aebab29b 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 @@ final class InstallPackageHelper { || (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, @@ -3923,13 +3923,20 @@ final class InstallPackageHelper { && !pkgSetting.getPathString().equals(parsedPackage.getPath()); final boolean newPkgVersionGreater = pkgAlreadyExists && parsedPackage.getLongVersionCode() > pkgSetting.getVersionCode(); + final boolean newSharedUserSetting = pkgAlreadyExists + && (initialScanRequest.mOldSharedUserSetting + != initialScanRequest.mSharedUserSetting); final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated - && newPkgChangedPaths && newPkgVersionGreater; + && newPkgChangedPaths && (newPkgVersionGreater || newSharedUserSetting); if (isSystemPkgBetter) { // The version of the application on /system is greater than the version on // /data. Switch back to the application on /system. // It's safe to assume the application on /system will correctly scan. If not, // there won't be a working copy of the application. + // Also, if the sharedUserSetting of the application on /system is different + // from the sharedUserSetting on /data, switch back to the application on /system. + // We should trust the sharedUserSetting on /system, even if the application + // version on /system is smaller than the version on /data. synchronized (mPm.mLock) { // just remove the loaded entries from package lists mPm.mPackages.remove(pkgSetting.getPackageName()); diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java index b27373e1d9f1..b66c6ac472b4 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 @@ public final class MovePackageHelper { 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 226a27eccc03..49f3a3c02879 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 @@ public class PackageDexOptimizer { // 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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8f8cc8a43017..9e1bffb7abd5 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 6d90593c2f48..3ec6e7dcdef6 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 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @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 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @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 a905df98e577..6572d7b27e2b 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 @@ final class ScanPackageUtils { 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 a40d40467735..4aba01637abf 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.LocalServices; 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 @@ public final class Settings implements Watchable, Snappable { 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 @@ public final class Settings implements Watchable, Snappable { // (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 1da442b3dc20..74594cce0041 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.internal.util.Preconditions; 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 @@ public class StagingManager { // 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 1027f4c03127..0f920c6a4e90 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 @@ public abstract class UserManagerInternal { /** 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 23a6b67269e8..3234e87a7125 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.text.TextUtils; 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.BundleUtils; 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { /** 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 @@ public class UserManagerService extends IUserManager.Stub { @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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { } @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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { + "%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 @@ public class UserManagerService extends IUserManager.Stub { // 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { userData.seedAccountOptions = seedAccountOptions; userData.userProperties = userProperties; userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp); + userData.mLastEnteredForegroundTimeMillis = lastEnteredForegroundTime; if (ignorePrepareStorageErrors) { userData.setIgnorePrepareStorageErrors(); } @@ -6153,6 +6246,11 @@ public class UserManagerService extends IUserManager.Stub { || 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { 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 @@ public class UserManagerService extends IUserManager.Stub { @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 @@ public class UserManagerService extends IUserManager.Stub { } 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 2650b2394cc5..9b9ca1088064 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.Handler; 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 android.view.Display; 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 @@ import java.util.concurrent.CopyOnWriteArrayList; 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 final class UserVisibilityMediator implements Dumpable { 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 @@ public final class UserVisibilityMediator implements Dumpable { } } + // 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 @@ public final class UserVisibilityMediator implements Dumpable { 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 @@ public final class UserVisibilityMediator implements Dumpable { 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 @@ public final class UserVisibilityMediator implements Dumpable { 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/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index 0bdd98038f83..046db921e245 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -320,15 +320,13 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { switch (profileType) { case ArtManager.PROFILE_APPS : - return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false); + return true; case ArtManager.PROFILE_BOOT_IMAGE: // The device config property overrides the system property version. boolean profileBootClassPath = SystemProperties.getBoolean( "persist.device_config.runtime_native_boot.profilebootclasspath", SystemProperties.getBoolean("dalvik.vm.profilebootclasspath", false)); - return (Build.IS_USERDEBUG || Build.IS_ENG) && - SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false) && - profileBootClassPath; + return (Build.IS_USERDEBUG || Build.IS_ENG) && profileBootClassPath; default: throw new IllegalArgumentException("Invalid profile type:" + profileType); } 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 a7d4ceadc37a..558202b99de2 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 @@ public class PackageInfoUtils { 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 944e4ad6eb69..876bf17c9634 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 @@ interface AndroidPackageHidden { 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 5b0cc51c8531..c76b12983656 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.dex.DexMetadataHelper; 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 @@ public class AndroidPackageUtils { 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 a43b9796dc76..ba36ab7a1f02 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 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @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 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } @Override + public UUID getStorageUuid() { + return mStorageUuid; + } + + @Override public int getTargetSandboxVersion() { return targetSandboxVersion; } @@ -2905,12 +2906,6 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } @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 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, 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 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, 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 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, 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 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, 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 d3063419bca0..aeaff6dd1458 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 @@ public interface ParsedPackage extends AndroidPackage { 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 e3dad452c9f4..84907a57c03d 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.ProviderInfo; 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.security.PublicKey; 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 @@ public interface AndroidPackage { 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 3c79cdfa5c0e..e8d0640a675c 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 @@ public interface PackageState { 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 c6ce40e39604..e552a34be70d 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 @@ public class PackageStateImpl implements PackageState { 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 @@ public class PackageStateImpl implements PackageState { 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 @@ public class PackageStateImpl implements PackageState { } @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 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated.Member + public @Nullable String getSeInfo() { + return mSeInfo; + } + + @DataClass.Generated.Member public boolean isHasSharedUser() { return mHasSharedUser; } @@ -697,10 +705,10 @@ public class PackageStateImpl implements PackageState { } @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 b22c0386c0c1..57fbfe91193b 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.NonNull; 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 @@ public class PackageStateUnserialized { @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 @@ public class PackageStateUnserialized { this.apkInUpdatedApex = other.apkInUpdatedApex; this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills; this.overrideSeInfo = other.overrideSeInfo; + this.seInfo = other.seInfo; mPackageSetting.onChanged(); } @@ -206,6 +211,13 @@ public class PackageStateUnserialized { 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 @@ public class PackageStateUnserialized { } @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/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 9281f4bf75b0..1ea0988893ad 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 @@ public final class PowerManagerService extends SystemService 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/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index 9953ca8f9b65..135841729e69 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -83,6 +83,9 @@ public class PowerStatsService extends SystemService { @Nullable @GuardedBy("this") private Looper mLooper; + @Nullable + @GuardedBy("this") + private EnergyConsumer[] mEnergyConsumers = null; @VisibleForTesting static class Injector { @@ -260,6 +263,15 @@ public class PowerStatsService extends SystemService { } } + private EnergyConsumer[] getEnergyConsumerInfo() { + synchronized (this) { + if (mEnergyConsumers == null) { + mEnergyConsumers = getPowerStatsHal().getEnergyConsumerInfo(); + } + return mEnergyConsumers; + } + } + public PowerStatsService(Context context) { this(context, new Injector()); } @@ -327,7 +339,69 @@ public class PowerStatsService extends SystemService { private void getEnergyConsumedAsync(CompletableFuture<EnergyConsumerResult[]> future, int[] energyConsumerIds) { - future.complete(getPowerStatsHal().getEnergyConsumed(energyConsumerIds)); + EnergyConsumerResult[] results = getPowerStatsHal().getEnergyConsumed(energyConsumerIds); + + // STOPSHIP(253292374): Remove once missing EnergyConsumer results issue is resolved. + EnergyConsumer[] energyConsumers = getEnergyConsumerInfo(); + if (energyConsumers != null) { + final int expectedLength; + if (energyConsumerIds.length == 0) { + // Empty request is a request for all available EnergyConsumers. + expectedLength = energyConsumers.length; + } else { + expectedLength = energyConsumerIds.length; + } + + if (results == null || expectedLength != results.length) { + // Mismatch in requested/received energy consumer data. + StringBuilder sb = new StringBuilder(); + sb.append("Requested ids:"); + if (energyConsumerIds.length == 0) { + sb.append("ALL"); + } + sb.append("["); + for (int i = 0; i < expectedLength; i++) { + final int id = energyConsumerIds[i]; + sb.append(id); + sb.append("(type:"); + sb.append(energyConsumers[id].type); + sb.append(",ord:"); + sb.append(energyConsumers[id].ordinal); + sb.append(",name:"); + sb.append(energyConsumers[id].name); + sb.append(")"); + if (i != expectedLength - 1) { + sb.append(", "); + } + } + sb.append("]"); + + sb.append(", Received result ids:"); + if (results == null) { + sb.append("null"); + } else { + sb.append("["); + final int resultLength = results.length; + for (int i = 0; i < resultLength; i++) { + final int id = results[i].id; + sb.append(id); + sb.append("(type:"); + sb.append(energyConsumers[id].type); + sb.append(",ord:"); + sb.append(energyConsumers[id].ordinal); + sb.append(",name:"); + sb.append(energyConsumers[id].name); + sb.append(")"); + if (i != resultLength - 1) { + sb.append(", "); + } + } + sb.append("]"); + } + Slog.wtf(TAG, "Missing result from getEnergyConsumedAsync call. " + sb); + } + } + future.complete(results); } private void getStateResidencyAsync(CompletableFuture<StateResidencyResult[]> future, diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java index fbb6644934f1..f17e5e73ceb0 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 class SensorManagerInternal { 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 @@ public abstract class SensorManagerInternal { */ 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 8fe2d52f7160..d8e3bddd6432 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.SystemServerInitThreadPool; 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 @@ public class SensorService extends SystemService { 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 @@ public class SensorService extends SystemService { 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 @@ public class SensorService extends SystemService { 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 0b1f6b9ba285..f971db9b5f0e 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 @@ public class TrustAgentWrapper { // 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 @@ public class TrustAgentWrapper { 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 @@ public class TrustAgentWrapper { if (mBound) { scheduleRestart(); } + if (mWithinSecurityLockdownWindow) { + mTrustManagerService.lockUser(mUserId); + } // mTrustDisabledByDpm maintains state } }; @@ -673,6 +678,22 @@ public class TrustAgentWrapper { } } + 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 class TrustAgentWrapper { 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 770cb7258319..4772bbfe97dd 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 @@ import java.util.Locale; */ 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 @@ public class EventLogger { * @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 @@ public class EventLogger { /** 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 @@ public class EventLogger { 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 e88ac63615ca..6efbd89daf4c 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 @@ public final class Slogf { } /** - * 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 @@ public final class Slogf { * 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 @@ public final class Slogf { } /** - * 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 @@ public final class Slogf { * 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 @@ public final class Slogf { } /** - * 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 @@ public final class Slogf { * 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 @@ public final class Slogf { } /** - * 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 @@ public final class Slogf { * 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 @@ public final class Slogf { } /** - * 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 @@ public final class Slogf { * 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 @@ public final class Slogf { } /** - * 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 f74956b7c846..5d084616bfea 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub 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 530fa4d6d497..e099aac8c73e 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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, @@ -8910,9 +8918,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN) - && getParent().getConfiguration().orientation == ORIENTATION_PORTRAIT - && getParent().getWindowConfiguration().getWindowingMode() - == WINDOWING_MODE_FULLSCREEN) { + && isParentFullscreenPortrait()) { // We are using the parent configuration here as this is the most recent one that gets // passed to onConfigurationChanged when a relevant change takes place return info.getMinAspectRatio(); @@ -8935,6 +8941,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return info.getMinAspectRatio(); } + private boolean isParentFullscreenPortrait() { + final WindowContainer parent = getParent(); + return parent != null + && parent.getConfiguration().orientation == ORIENTATION_PORTRAIT + && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN; + } + /** * Returns true if the activity has maximum or minimum aspect ratio. */ diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index d7c5e9373ad3..719f72c8ee52 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 @@ class ActivityStartInterceptor { * @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 5938e7f095ea..5c83c4f940d3 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -53,7 +53,9 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP; import static android.content.pm.ActivityInfo.launchModeToString; import static android.os.Process.INVALID_UID; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; @@ -78,7 +80,6 @@ import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; 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; @@ -2140,6 +2141,11 @@ class ActivityStarter { if (actuallyMoved) { // Only record if the activity actually moved. mMovedToTopActivity = act; + if (mNoAnimation) { + act.mDisplayContent.prepareAppTransition(TRANSIT_NONE); + } else { + act.mDisplayContent.prepareAppTransition(TRANSIT_TO_FRONT); + } } act.updateOptionsLocked(mOptions); deliverNewIntent(act, intentGrants); @@ -2777,11 +2783,6 @@ class ActivityStarter { 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/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index d3452277a29f..cd26e2eb9c53 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -226,6 +226,9 @@ class BLASTSyncEngine { } private void setReady(boolean ready) { + if (mReady == ready) { + return; + } ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId); mReady = ready; if (!ready) return; @@ -239,7 +242,9 @@ class BLASTSyncEngine { ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc); wc.setSyncGroup(this); wc.prepareSync(); - mWm.mWindowPlacerLocked.requestTraversal(); + if (mReady) { + mWm.mWindowPlacerLocked.requestTraversal(); + } } void onCancelSync(WindowContainer wc) { diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index bedeabeb6141..89f1bd043556 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -343,7 +343,11 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { if (childArea == null) { continue; } - pw.println(prefix + "* " + childArea.getName()); + pw.print(prefix + "* " + childArea.getName()); + if (childArea.isOrganized()) { + pw.print(" (organized)"); + } + pw.println(); if (childArea.isTaskDisplayArea()) { // TaskDisplayArea can only contain task. And it is already printed by display. continue; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f452093ebbf8..690779d8951e 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.ContentRecordingSession; 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return displayFrames.update(rotation, w, h, calculateDisplayCutoutForRotation(rotation), calculateRoundedCornersForRotation(rotation), - calculatePrivacyIndicatorBoundsForRotation(rotation)); + calculatePrivacyIndicatorBoundsForRotation(rotation), + calculateDisplayShapeForRotation(rotation)); } @Override @@ -2821,6 +2848,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp ? 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp || 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInitialPhysicalYDpi = newYDpi; mInitialDisplayCutout = newCutout; mInitialRoundedCorners = newRoundedCorners; + mInitialDisplayShape = newDisplayShape; mCurrentUniqueDisplayId = newUniqueId; reconfigureDisplayLocked(); @@ -3380,7 +3411,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } 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 */); } @@ -3485,9 +3516,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override public void dump(PrintWriter pw, String prefix, boolean dumpAll) { - super.dump(pw, prefix, dumpAll); pw.print(prefix); - pw.println("Display: mDisplayId=" + mDisplayId + " rootTasks=" + getRootTaskCount()); + pw.println("Display: mDisplayId=" + mDisplayId + (isOrganized() ? " (organized)" : "")); final String subPrefix = " " + prefix; pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x"); pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity); @@ -3518,6 +3548,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion); pw.println(); + super.dump(pw, prefix, dumpAll); pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq); pw.print(" mCurrentFocus="); pw.println(mCurrentFocus); @@ -3609,6 +3640,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp pw.println(); mInsetsStateController.dump(prefix, pw); mDwpcHelper.dump(prefix, pw); + pw.println(); } @Override diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index 33641f72b2ff..e984456161e4 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.graphics.Rect; 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 class DisplayFrames { 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 class DisplayFrames { */ 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 @@ public class DisplayFrames { 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/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 2dbccae6dde6..bb4c482118a1 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 @@ final class LetterboxUiController { 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 @@ final class LetterboxUiController { // 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 @@ final class LetterboxUiController { } 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 class LetterboxUiController { final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow); return taskbarInsetsSource != null - && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight && taskbarInsetsSource.isVisible(); } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index d53ee1e9fa51..64cca87db65a 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3407,7 +3407,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final DisplayContent display = getChildAt(i); display.dump(pw, prefix, dumpAll); } - pw.println(); } /** diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 91cb037a386e..443e3049b2ea 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { * 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { EMBEDDING_DISALLOWED_UNTRUSTED_HOST, EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION, EMBEDDING_DISALLOWED_NEW_TASK, - EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT, }) @interface EmbeddingCheckResult {} @@ -616,14 +608,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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; } @@ -1167,8 +1151,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { } next.delayedResume = false; - final TaskDisplayArea taskDisplayArea = getDisplayArea(); + // If we are currently pausing an activity, then don't do anything until that is done. + final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete(); + if (!allPausedComplete) { + ProtoLog.v(WM_DEBUG_STATES, + "resumeTopActivity: Skip resume: some activity pausing."); + return false; + } + + final TaskDisplayArea taskDisplayArea = getDisplayArea(); // If the top activity is the resumed one, nothing to do. if (mResumedActivity == next && next.isState(RESUMED) && taskDisplayArea.allResumedActivitiesComplete()) { @@ -1191,14 +1183,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { return false; } - // If we are currently pausing an activity, then don't do anything until that is done. - final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete(); - if (!allPausedComplete) { - ProtoLog.v(WM_DEBUG_STATES, - "resumeTopActivity: Skip resume: some activity pausing."); - return false; - } - // If we are sleeping, and there is no resumed activity, and the top activity is paused, // well that is the state we want. if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) { @@ -2596,6 +2580,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { return false; } + @Override + boolean canCustomizeAppTransition() { + // This is only called when the app transition is going to be played by system server. In + // this case, we should allow custom app transition for fullscreen embedded TaskFragment + // just like Activity. + return isEmbedded() && matchParentBounds(); + } + /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */ void clearLastPausedActivity() { forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null); diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index 6d149da99cb0..da73fad99eb7 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -278,31 +278,29 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { // is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is // different, we should recalcuating the bounds. boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = false; - // shouldSetAsOverrideWindowingMode is set if the task needs to retain the launchMode - // regardless of the windowing mode of the parent. - boolean shouldSetAsOverrideWindowingMode = false; - if (launchMode == WINDOWING_MODE_PINNED) { - if (DEBUG) appendLog("picture-in-picture"); - } else if (!root.isResizeable()) { - if (shouldLaunchUnresizableAppInFreeformInFreeformMode(root, suggestedDisplayArea, - options)) { - launchMode = WINDOWING_MODE_UNDEFINED; - if (outParams.mBounds.isEmpty()) { - getTaskBounds(root, suggestedDisplayArea, layout, launchMode, hasInitialBounds, - outParams.mBounds); - hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = true; + if (suggestedDisplayArea.inFreeformWindowingMode()) { + if (launchMode == WINDOWING_MODE_PINNED) { + if (DEBUG) appendLog("picture-in-picture"); + } else if (!root.isResizeable()) { + if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea, options)) { + launchMode = WINDOWING_MODE_FREEFORM; + if (outParams.mBounds.isEmpty()) { + getTaskBounds(root, suggestedDisplayArea, layout, launchMode, + hasInitialBounds, outParams.mBounds); + hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = true; + } + if (DEBUG) appendLog("unresizable-freeform"); + } else { + launchMode = WINDOWING_MODE_FULLSCREEN; + outParams.mBounds.setEmpty(); + if (DEBUG) appendLog("unresizable-forced-maximize"); } - if (DEBUG) appendLog("unresizable-freeform"); - } else { - launchMode = WINDOWING_MODE_FULLSCREEN; - outParams.mBounds.setEmpty(); - shouldSetAsOverrideWindowingMode = true; - if (DEBUG) appendLog("unresizable-forced-maximize"); } + } else { + if (DEBUG) appendLog("non-freeform-task-display-area"); } // If launch mode matches display windowing mode, let it inherit from display. outParams.mWindowingMode = launchMode == suggestedDisplayArea.getWindowingMode() - && !shouldSetAsOverrideWindowingMode ? WINDOWING_MODE_UNDEFINED : launchMode; if (phase == PHASE_WINDOWING_MODE) { @@ -669,7 +667,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { inOutBounds.offset(xOffset, yOffset); } - private boolean shouldLaunchUnresizableAppInFreeformInFreeformMode(ActivityRecord activity, + private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity, TaskDisplayArea displayArea, @Nullable ActivityOptions options) { if (options != null && options.getLaunchWindowingMode() == WINDOWING_MODE_FULLSCREEN) { // Do not launch the activity in freeform if it explicitly requested fullscreen mode. @@ -682,7 +680,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { final int displayOrientation = orientationFromBounds(displayArea.getBounds()); final int activityOrientation = resolveOrientation(activity, displayArea, displayArea.getBounds()); - if (displayOrientation != activityOrientation) { + if (displayArea.getWindowingMode() == WINDOWING_MODE_FREEFORM + && displayOrientation != activityOrientation) { return true; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index b277804ed230..2b11d54898e2 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 com.android.server.inputmethod.InputMethodManagerInternal; 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 @@ import java.util.function.Predicate; * 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 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe 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! */ @@ -158,9 +160,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe private SurfaceControl.Transaction mFinishTransaction = null; /** - * Contains change infos for both participants and all ancestors. We have to track ancestors - * because they are all promotion candidates and thus we need their start-states - * to be captured. + * Contains change infos for both participants and all remote-animatable ancestors. The + * ancestors can be the promotion candidates so their start-states need to be captured. + * @see #getAnimatableParent */ final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>(); @@ -213,10 +215,27 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mFlags = flags; mController = controller; mSyncEngine = syncEngine; + mToken = new Token(this); controller.mTransitionTracer.logState(this); } + @Nullable + static Transition fromBinder(@Nullable IBinder token) { + if (token == null) return null; + 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; } @@ -392,8 +411,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mSyncId, wc); // "snapshot" all parents (as potential promotion targets). Do this before checking // if this is already a participant in case it has since been re-parented. - for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr); - curr = curr.getParent()) { + for (WindowContainer<?> curr = getAnimatableParent(wc); + curr != null && !mChanges.containsKey(curr); + curr = getAnimatableParent(curr)) { mChanges.put(curr, new ChangeInfo(curr)); if (isReadyGroup(curr)) { mReadyTracker.addGroup(curr); @@ -726,6 +746,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe 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 +892,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */); } + cleanUpInternal(); } void abort() { @@ -909,6 +935,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe dc.getPendingTransaction().merge(transaction); mSyncId = -1; mOverrideOptions = null; + cleanUpInternal(); return; } @@ -1026,7 +1053,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe 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 +1088,17 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe 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 */ @@ -1234,6 +1273,16 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return sb.toString(); } + /** Returns the parent that the remote animator can animate or control. */ + private static WindowContainer<?> getAnimatableParent(WindowContainer<?> wc) { + WindowContainer<?> parent = wc.getParent(); + while (parent != null + && (!parent.canCreateRemoteAnimationTarget() && !parent.isOrganized())) { + parent = parent.getParent(); + } + return parent; + } + private static boolean reportIfNotTop(WindowContainer wc) { // Organized tasks need to be reported anyways because Core won't show() their surfaces // and we can't rely on onTaskAppeared because it isn't in sync. @@ -1457,7 +1506,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe intermediates.clear(); boolean foundParentInTargets = false; // Collect the intermediate parents between target and top changed parent. - for (WindowContainer<?> p = wc.getParent(); p != null; p = p.getParent()) { + for (WindowContainer<?> p = getAnimatableParent(wc); p != null; + p = getAnimatableParent(p)) { final ChangeInfo parentChange = changes.get(p); if (parentChange == null || !parentChange.hasChanged(p)) break; if (p.mRemoteToken == null) { @@ -1815,10 +1865,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe 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 +2371,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } } + + 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 25df5112e395..99527b1454c1 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 @@ class TransitionController { 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/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 6522d93d5267..3b30dd136c73 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -114,12 +114,6 @@ class WallpaperController { private boolean mShouldUpdateZoom; - /** - * Temporary storage for taking a screenshot of the wallpaper. - * @see #screenshotWallpaperLocked() - */ - private WindowState mTmpTopWallpaper; - @Nullable private Point mLargestDisplaySize = null; private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult(); @@ -965,21 +959,16 @@ class WallpaperController { } WindowState getTopVisibleWallpaper() { - mTmpTopWallpaper = null; - for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); - token.forAllWindows(w -> { - final WindowStateAnimator winAnim = w.mWinAnimator; - if (winAnim != null && winAnim.getShown() && winAnim.mLastAlpha > 0f) { - mTmpTopWallpaper = w; - return true; + for (int i = token.getChildCount() - 1; i >= 0; i--) { + final WindowState w = token.getChildAt(i); + if (w.mWinAnimator.getShown() && w.mWinAnimator.mLastAlpha > 0f) { + return w; } - return false; - }, true /* traverseTopToBottom */); + } } - - return mTmpTopWallpaper; + return null; } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4938b60346f2..f6f825f42add 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8940,14 +8940,14 @@ public class WindowManagerService extends IWindowManager.Stub } @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<>(); } @@ -8965,31 +8965,6 @@ public class WindowManagerService extends IWindowManager.Stub 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 4c35178d1b34..aa1cf563da0b 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 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub nextTransition.setAllReady(); } }); - return nextTransition; + return nextTransition.getToken(); } transition = mTransitionController.createTransition(type); } @@ -315,7 +315,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub 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 63b7dfbc2a3b..10d8b42c2979 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 @@ public: 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 @@ private: 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 @@ void NativeSensorService::unregisterProximityActiveListener() { 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 @@ void NativeSensorService::ProximityActiveListenerDelegate::onProximityActive(boo 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 @@ static void unregisterProximityActiveListenerNative(JNIEnv* env, jclass, jlong p 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 332a75ea566b..8854453a61cd 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.content.Intent; 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 @@ public final class ProviderCreateSession extends ProviderSession< 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 @@ public final class ProviderCreateSession extends ProviderSession< 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/proguard.flags b/services/proguard.flags index 27fe5056b8b4..6cdf11c3c685 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/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 1f66a1180aeb..7bf9a9ef121f 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 android.util.SparseIntArray 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 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag "getLongVersionCode", // Tested through constructor "getManifestPackageName", - // Utility methods - "getStorageUuid", // Removal not tested, irrelevant for parcelling concerns "removeUsesOptionalLibrary", "clearAdoptPermissions", @@ -85,6 +104,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag // Tested manually "getMimeGroups", "getRequestedPermissions", + "getStorageUuid", // Tested through asSplit "asSplit", "getSplits", @@ -155,7 +175,6 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag AndroidPackage::getResizeableActivity, AndroidPackage::getRestrictedAccountType, AndroidPackage::getRoundIconRes, - PackageImpl::getSeInfo, PackageImpl::getSecondaryCpuAbi, AndroidPackage::getSecondaryNativeLibraryDir, AndroidPackage::getSharedUserId, @@ -241,7 +260,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag ) 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 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag 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 51fa8517e45f..e7c384b48152 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 @@ public class RescuePartyTest { noteBoot(4); assertTrue(RescueParty.isRebootPropertySet()); + SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); noteBoot(5); assertTrue(RescueParty.isFactoryResetPropertySet()); } @@ -276,6 +277,7 @@ public class RescuePartyTest { 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 @@ public class RescuePartyTest { 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/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index 66e7ec00b6d3..c87fd26fbe82 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.annotation.NonNull; 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 @@ public class BroadcastQueueModernImplTest { } /** + * 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 dc77762795c7..a8d894511213 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,11 @@ 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_DELAY_MILLIS; +import static com.android.server.app.GameManagerService.WRITE_GAME_MODE_INTERVENTION_LIST_FILE; import static com.android.server.app.GameManagerService.WRITE_SETTINGS; import static org.junit.Assert.assertArrayEquals; @@ -58,6 +63,7 @@ import android.content.res.AssetManager; 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 +109,8 @@ public class GameManagerServiceTests { 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 +211,33 @@ public class GameManagerServiceTests { .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 +468,22 @@ public class GameManagerServiceTests { * 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 +630,21 @@ public class GameManagerServiceTests { 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 +747,14 @@ public class GameManagerServiceTests { 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 +1472,98 @@ public class GameManagerServiceTests { } @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 { @@ -1756,9 +1837,7 @@ public class GameManagerServiceTests { public void testUpdateCustomGameModeConfiguration_permissionDenied() { mockModifyGameModeDenied(); mockDeviceConfigAll(); - GameManagerService gameManagerService = - new GameManagerService(mMockContext, mTestLooper.getLooper()); - startUser(gameManagerService, USER_ID_1); + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); assertThrows(SecurityException.class, () -> { gameManagerService.updateCustomGameModeConfiguration(mPackageName, new GameModeConfiguration.Builder().setScalingFactor(0.5f).build(), @@ -1769,9 +1848,7 @@ public class GameManagerServiceTests { @Test public void testUpdateCustomGameModeConfiguration_noUserId() { mockModifyGameModeGranted(); - GameManagerService gameManagerService = - new GameManagerService(mMockContext, mTestLooper.getLooper()); - startUser(gameManagerService, USER_ID_2); + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_2); assertThrows(IllegalArgumentException.class, () -> { gameManagerService.updateCustomGameModeConfiguration(mPackageName, new GameModeConfiguration.Builder().setScalingFactor(0.5f).build(), @@ -1780,6 +1857,63 @@ public class GameManagerServiceTests { } @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 testUpdateCustomGameModeConfiguration() throws InterruptedException { + mockModifyGameModeGranted(); + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); + gameManagerService.updateCustomGameModeConfiguration(mPackageName, + new GameModeConfiguration.Builder().setScalingFactor(0.35f).setFpsOverride( + 60).build(), + USER_ID_1); + + assertTrue(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1)); + assertTrue( + gameManagerService.mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, + USER_ID_1)); + + GameManagerService.GamePackageConfiguration pkgConfig = gameManagerService.getConfig( + mPackageName, USER_ID_1); + assertNotNull(pkgConfig); + GameManagerService.GamePackageConfiguration.GameModeConfiguration modeConfig = + pkgConfig.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM); + assertNotNull(modeConfig); + assertEquals(modeConfig.getScaling(), 0.35f, 0.01f); + assertEquals(modeConfig.getFps(), 60); + // creates a new service to check that no data has been stored + mTestLooper.dispatchAll(); + gameManagerService = createServiceAndStartUser(USER_ID_1); + pkgConfig = gameManagerService.getConfig(mPackageName, USER_ID_1); + assertNull(pkgConfig); + + mTestLooper.moveTimeForward(WRITE_DELAY_MILLIS + 500); + mTestLooper.dispatchAll(); + // creates a new service to check that data is persisted after delay + gameManagerService = createServiceAndStartUser(USER_ID_1); + assertEquals(GameManager.GAME_MODE_STANDARD, + gameManagerService.getGameMode(mPackageName, USER_ID_1)); + pkgConfig = gameManagerService.getConfig(mPackageName, USER_ID_1); + assertNotNull(pkgConfig); + modeConfig = pkgConfig.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM); + assertNotNull(modeConfig); + assertEquals(modeConfig.getScaling(), 0.35f, 0.01f); + assertEquals(modeConfig.getFps(), 60); + } + + @Test public void testWritingSettingFile_onShutdown() throws InterruptedException { mockModifyGameModeGranted(); mockDeviceConfigAll(); @@ -1939,4 +2073,111 @@ public class GameManagerServiceTests { } 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 5dc12510368c..be13bad70e16 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 @@ public class AppOpsLegacyRestrictionsTest { StaticMockitoSession mSession; @Mock - AppOpsService.Constants mConstants; + AppOpsServiceImpl.Constants mConstants; @Mock Context mContext; @@ -57,7 +57,7 @@ public class AppOpsLegacyRestrictionsTest { 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 c0688d131610..7d4bc6f47ad4 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_FLAGS_ALL; 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.anyString; 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 @@ public class AppOpsServiceTest { 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 @@ public class AppOpsServiceTest { @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 @@ public class AppOpsServiceTest { @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 @@ public class AppOpsServiceTest { // 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 @@ public class AppOpsServiceTest { // 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 @@ public class AppOpsServiceTest { @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 @@ public class AppOpsServiceTest { @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 @@ public class AppOpsServiceTest { @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 98e895a86f9e..3efd5e701013 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 @@ public class AppOpsUidStateTrackerTest { 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 e08a715fbfe1..298dbf47d86d 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 @@ public class AppOpsUpgradeTest { } } - 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 @@ public class AppOpsUpgradeTest { 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 @@ public class AppOpsUpgradeTest { // 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 000000000000..7ab1363b5087 --- /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/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index 4c28c51f7e62..f2cba40685e4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -237,8 +237,8 @@ public final class DisplayPowerController2Test { when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId); when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock); when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); - when(mLogicalDisplayMock.isEnabled()).thenReturn(true); - when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED); + when(mLogicalDisplayMock.isEnabledLocked()).thenReturn(true); + when(mLogicalDisplayMock.isInTransitionLocked()).thenReturn(false); when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId); when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index 9a4bb22d5195..4f8cb8876b3f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -219,8 +219,8 @@ public final class DisplayPowerControllerTest { when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId); when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock); when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); - when(mLogicalDisplayMock.isEnabled()).thenReturn(true); - when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED); + when(mLogicalDisplayMock.isEnabledLocked()).thenReturn(true); + when(mLogicalDisplayMock.isInTransitionLocked()).thenReturn(false); when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId); when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock); @@ -233,4 +233,4 @@ public final class DisplayPowerControllerTest { }); when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); } -}
\ No newline at end of file +} 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 d41ac7021077..395e6ac1017d 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 @@ public class LocalDisplayAdapterTest { 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 a97491daa96e..ddfbf16f4833 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.Collections; 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 5c3d69547755..01674bbc6859 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.ArgumentMatchers.argThat; 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 @@ public final class BackgroundDexOptServiceUnitTest { @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 dd6c7334a45a..27d0662d118e 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 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { .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 c2038311e740..4487d136b708 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 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { 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 ae25c1bb3db8..cb59d37e46ec 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 @@ public final class SlogfTest { 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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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 @@ public final class SlogfTest { } @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/Android.bp b/services/tests/servicestests/Android.bp index 1c4309759186..80305fa7d8fb 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -114,6 +114,7 @@ android_test { ":SimpleServiceTestApp3", ":StubTestApp", ":SuspendTestApp", + ":MediaButtonReceiverHolderTestHelperApp", ], java_resources: [ diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 0be321ac5249..9eb3b92abc6f 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -110,6 +110,9 @@ <queries> <package android:name="com.android.servicestests.apps.suspendtestapp" /> + <intent> + <action android:name="android.media.browse.MediaBrowserService" /> + </intent> </queries> <!-- Uses API introduced in O (26) --> diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index 9052f58b2a8b..d9676470aca3 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -33,6 +33,7 @@ <option name="test-file-name" value="SimpleServiceTestApp1.apk" /> <option name="test-file-name" value="SimpleServiceTestApp2.apk" /> <option name="test-file-name" value="SimpleServiceTestApp3.apk" /> + <option name="test-file-name" value="MediaButtonReceiverHolderTestHelperApp.apk" /> </target_preparer> <!-- Create place to store apks --> diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java index c325778a5683..ee09074f7625 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 static com.google.common.truth.Truth.assertThat; 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 @@ public class DockObserverTest { .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 @@ public class DockObserverTest { 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 a49214f9b4f5..e8b8253f0611 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_COMPLETED_EVENT_MSG; 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.FgThread; 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { @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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { @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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { @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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { 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 @@ public class UserControllerTest { } @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/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java index 582c78bce54c..fde3422b1ff3 100644 --- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java @@ -52,6 +52,8 @@ public class GameManagerServiceSettingsTests { private static final String PACKAGE_NAME_1 = "com.android.app1"; private static final String PACKAGE_NAME_2 = "com.android.app2"; private static final String PACKAGE_NAME_3 = "com.android.app3"; + private static final String PACKAGE_NAME_4 = "com.android.app4"; + private void writeFile(File file, byte[] data) { file.mkdirs(); @@ -69,16 +71,23 @@ public class GameManagerServiceSettingsTests { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/game-manager-service.xml"), ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<packages>\n" - + " <package name=\"com.android.app1\" gameMode=\"1\">\n" - + " </package>\n" + + "<packages>" + + "\n" // app1: no package config setting + + "\n" // app2: performance mode is selected with override + " <package name=\"com.android.app2\" gameMode=\"2\">\n" + " <gameModeConfig gameMode=\"2\" scaling=\"0.99\" " + "useAngle=\"true\" fps=\"90\" loadingBoost=\"123\"></gameModeConfig>\n" + " <gameModeConfig gameMode=\"3\"></gameModeConfig>\n" - + " </package>\n" + + " </package>" + + "\n" // app3: only battery mode is selected + " <package name=\"com.android.app3\" gameMode=\"3\">\n" - + " </package>\n" + + " </package>" + + "\n" // app4: no game mode selected but custom game mode config + + " <package name=\"com.android.app4\">\n" + + " <gameModeConfig gameMode=\"4\" scaling=\"0.4\" " + + "fps=\"30\"></gameModeConfig>\n" + + " </package>" + + "\n" + "</packages>\n").getBytes()); } @@ -115,14 +124,15 @@ public class GameManagerServiceSettingsTests { assertTrue(settings.readPersistentDataLocked()); // test game modes - assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_1)); - assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2)); - assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3)); + assertEquals(GameManager.GAME_MODE_STANDARD, settings.getGameModeLocked(PACKAGE_NAME_1)); + assertEquals(GameManager.GAME_MODE_PERFORMANCE, settings.getGameModeLocked(PACKAGE_NAME_2)); + assertEquals(GameManager.GAME_MODE_BATTERY, settings.getGameModeLocked(PACKAGE_NAME_3)); + assertEquals(GameManager.GAME_MODE_STANDARD, settings.getGameModeLocked(PACKAGE_NAME_4)); // test game mode configs assertNull(settings.getConfigOverride(PACKAGE_NAME_1)); assertNull(settings.getConfigOverride(PACKAGE_NAME_3)); - final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2); + GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2); assertNotNull(config); assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_STANDARD)); @@ -141,6 +151,14 @@ public class GameManagerServiceSettingsTests { GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION); assertEquals(batteryConfig.getFpsStr(), GameModeConfiguration.DEFAULT_FPS); assertFalse(batteryConfig.getUseAngle()); + + config = settings.getConfigOverride(PACKAGE_NAME_4); + assertNotNull(config); + GameModeConfiguration customConfig = config.getGameModeConfiguration( + GameManager.GAME_MODE_CUSTOM); + assertNotNull(customConfig); + assertEquals(customConfig.getScaling(), 0.4f, 0.1f); + assertEquals(customConfig.getFps(), 30); } @Test @@ -176,16 +194,20 @@ public class GameManagerServiceSettingsTests { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/game-manager-service.xml"), ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" - + "<packages>\n" + + "<packages>" + + "\n" // missing package name + " <package gameMode=\"1\">\n" - + " </package>\n" + + " </package>" + + "\n" // app2 with unknown sub element + " <package name=\"com.android.app2\" gameMode=\"2\">\n" + " <unknown></unknown>" + " <gameModeConfig gameMode=\"3\" fps=\"90\"></gameModeConfig>\n" + " foo bar" - + " </package>\n" + + " </package>" + + "\n" // unknown package element + " <unknownTag></unknownTag>\n" - + " foo bar\n" + + " foo bar" + + "\n" // app3 after unknown element + " <package name=\"com.android.app3\" gameMode=\"3\">\n" + " </package>\n" + "</packages>\n").getBytes()); @@ -214,6 +236,8 @@ public class GameManagerServiceSettingsTests { settings.setGameModeLocked(PACKAGE_NAME_1, GameManager.GAME_MODE_BATTERY); settings.setGameModeLocked(PACKAGE_NAME_2, GameManager.GAME_MODE_PERFORMANCE); settings.setGameModeLocked(PACKAGE_NAME_3, GameManager.GAME_MODE_STANDARD); + + // set config for app2 GamePackageConfiguration config = new GamePackageConfiguration(PACKAGE_NAME_2); GameModeConfiguration performanceConfig = config.getOrAddDefaultGameModeConfiguration( GameManager.GAME_MODE_PERFORMANCE); @@ -225,18 +249,29 @@ public class GameManagerServiceSettingsTests { GameManager.GAME_MODE_BATTERY); batteryConfig.setScaling(0.77f); settings.setConfigOverride(PACKAGE_NAME_2, config); + + // set config for app4 + config = new GamePackageConfiguration(PACKAGE_NAME_4); + GameModeConfiguration customConfig = config.getOrAddDefaultGameModeConfiguration( + GameManager.GAME_MODE_CUSTOM); + customConfig.setScaling(0.4f); + customConfig.setFpsStr("30"); + settings.setConfigOverride(PACKAGE_NAME_4, config); + settings.writePersistentDataLocked(); // clear the settings in memory settings.removeGame(PACKAGE_NAME_1); settings.removeGame(PACKAGE_NAME_2); settings.removeGame(PACKAGE_NAME_3); + settings.removeGame(PACKAGE_NAME_4); // read back in and verify assertTrue(settings.readPersistentDataLocked()); assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_1)); assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2)); assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_3)); + assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_4)); config = settings.getConfigOverride(PACKAGE_NAME_1); assertNull(config); @@ -256,5 +291,14 @@ public class GameManagerServiceSettingsTests { assertEquals(performanceConfig.getLoadingBoostDuration(), 321); assertEquals(performanceConfig.getFpsStr(), "60"); assertTrue(performanceConfig.getUseAngle()); + + config = settings.getConfigOverride(PACKAGE_NAME_4); + assertNotNull(config); + customConfig = config.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM); + assertNotNull(customConfig); + assertEquals(customConfig.getScaling(), 0.4f, 0.1f); + assertEquals(customConfig.getFps(), 30); + assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)); + assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY)); } } 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 000000000000..ef8a49f95a49 --- /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 0bd6f2c2d903..3e8a07021d6b 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.VirtualDeviceManager; 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.Intent; 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 androidx.test.InstrumentationRegistry; 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 @@ public class VirtualDeviceManagerServiceTest { 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 @@ public class VirtualDeviceManagerServiceTest { @Mock private InputManagerInternal mInputManagerInternalMock; @Mock + private SensorManagerInternal mSensorManagerInternalMock; + @Mock private IVirtualDeviceActivityListener mActivityListener; @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; @@ -228,6 +236,9 @@ public class VirtualDeviceManagerServiceTest { 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 @@ public class VirtualDeviceManagerServiceTest { 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 @@ public class VirtualDeviceManagerServiceTest { .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 @@ public class VirtualDeviceManagerServiceTest { 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 @@ public class VirtualDeviceManagerServiceTest { } @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 @@ public class VirtualDeviceManagerServiceTest { } @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 036b6df92ef9..aefe4b6f4c0a 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 androidx.test.ext.junit.runners.AndroidJUnit4; 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 @@ public class VirtualDeviceParamsTest { 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 9853b7acaf13..109abd03747c 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 @@ public class DisplayManagerServiceTest { 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; @@ -335,7 +319,7 @@ public class DisplayManagerServiceTest { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); - final int displayIds[] = bs.getDisplayIds(); + final int[] displayIds = bs.getDisplayIds(/* includeDisabled= */ true); final int size = displayIds.length; assertTrue(size > 0); @@ -1113,13 +1097,32 @@ public class DisplayManagerServiceTest { } /** - * 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); } /** @@ -1147,10 +1150,7 @@ public class DisplayManagerServiceTest { @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); } /** @@ -1159,10 +1159,7 @@ public class DisplayManagerServiceTest { @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); } /** @@ -1385,10 +1382,9 @@ public class DisplayManagerServiceTest { 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); @@ -1410,46 +1406,19 @@ public class DisplayManagerServiceTest { 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, FakeDisplayDevice displayDevice) { - final int[] displayIds = displayManagerBinderService.getDisplayIds(); + final int[] displayIds = displayManagerBinderService.getDisplayIds( + /* includeDisabled= */ true); assertTrue(displayIds.length > 0); int displayId = Display.INVALID_DISPLAY; for (int i = 0; i < displayIds.length; i++) { diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index 657bda633ab5..246945c2d968 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -30,6 +30,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -52,6 +54,8 @@ import android.view.DisplayInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.display.layout.Layout; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,6 +63,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.io.InputStream; import java.io.OutputStream; @@ -83,6 +88,7 @@ public class LogicalDisplayMapperTest { @Mock Resources mResourcesMock; @Mock IPowerManager mIPowerManagerMock; @Mock IThermalService mIThermalServiceMock; + @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy = new DeviceStateToLayoutMap(); @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor; @@ -132,7 +138,8 @@ public class LogicalDisplayMapperTest { mLooper = new TestLooper(); mHandler = new Handler(mLooper.getLooper()); mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo, - mListenerMock, new DisplayManagerService.SyncRoot(), mHandler); + mListenerMock, new DisplayManagerService.SyncRoot(), mHandler, + mDeviceStateToLayoutMapSpy); } @@ -259,7 +266,8 @@ public class LogicalDisplayMapperTest { add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0)); add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0)); - int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID); + int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, + /* includeDisabled= */ true); assertEquals(3, ids.length); Arrays.sort(ids); assertEquals(DEFAULT_DISPLAY, ids[0]); @@ -503,10 +511,183 @@ public class LogicalDisplayMapperTest { /* isBootCompleted= */true)); } + @Test + public void testDeviceStateLocked() { + DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + + Layout layout = new Layout(); + layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true); + layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false); + when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); + + layout = new Layout(); + layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, false, false); + layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, true, true); + when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout); + when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout); + + LogicalDisplay display1 = add(device1); + assertEquals(info(display1).address, info(device1).address); + assertEquals(DEFAULT_DISPLAY, id(display1)); + + LogicalDisplay display2 = add(device2); + assertEquals(info(display2).address, info(device2).address); + // We can only have one default display + assertEquals(DEFAULT_DISPLAY, id(display1)); + + mLogicalDisplayMapper.setDeviceStateLocked(0, false); + advanceTime(1000); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + + mLogicalDisplayMapper.setDeviceStateLocked(1, false); + advanceTime(1000); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + + mLogicalDisplayMapper.setDeviceStateLocked(2, false); + advanceTime(1000); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); + assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); + } + + @Test + public void testEnabledAndDisabledDisplays() { + DisplayAddress displayAddressOne = new TestUtils.TestDisplayAddress(); + DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress(); + DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress(); + + TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, "one", + Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two", + Display.TYPE_INTERNAL, 200, 800, + DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three", + Display.TYPE_INTERNAL, 600, 900, + DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); + + Layout threeDevicesEnabledLayout = new Layout(); + threeDevicesEnabledLayout.createDisplayLocked( + displayAddressOne, + /* isDefault= */ true, + /* isEnabled= */ true); + threeDevicesEnabledLayout.createDisplayLocked( + displayAddressTwo, + /* isDefault= */ false, + /* isEnabled= */ true); + threeDevicesEnabledLayout.createDisplayLocked( + displayAddressThree, + /* isDefault= */ false, + /* isEnabled= */ true); + + when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT)) + .thenReturn(threeDevicesEnabledLayout); + + LogicalDisplay display1 = add(device1); + LogicalDisplay display2 = add(device2); + LogicalDisplay display3 = add(device3); + + // ensure 3 displays are returned + int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, false); + assertEquals(3, ids.length); + Arrays.sort(ids); + assertEquals(DEFAULT_DISPLAY, ids[0]); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device2, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device3, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + threeDevicesEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + threeDevicesEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + threeDevicesEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(), + /* includeDisabled= */ false)); + + Layout oneDeviceEnabledLayout = new Layout(); + oneDeviceEnabledLayout.createDisplayLocked( + displayAddressOne, + /* isDefault= */ true, + /* isEnabled= */ true); + oneDeviceEnabledLayout.createDisplayLocked( + displayAddressTwo, + /* isDefault= */ false, + /* isEnabled= */ false); + oneDeviceEnabledLayout.createDisplayLocked( + displayAddressThree, + /* isDefault= */ false, + /* isEnabled= */ false); + + when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout); + when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout); + + // 1) Set the new state + // 2) Mark the displays as STATE_OFF so that it can continue with transition + // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state + // 4) Dispatch handler events. + mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); + advanceTime(1000); + final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked( + Process.SYSTEM_UID, false); + if (allDisplayIds.length != 1) { + throw new RuntimeException("Displays: \n" + + mLogicalDisplayMapper.getDisplayLocked(device1).toString() + + "\n" + mLogicalDisplayMapper.getDisplayLocked(device2).toString() + + "\n" + mLogicalDisplayMapper.getDisplayLocked(device3).toString()); + } + // ensure only one display is returned + assertEquals(1, allDisplayIds.length); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1, + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked(device2, + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked(device3, + /* includeDisabled= */ false)); + assertNotNull(mLogicalDisplayMapper.getDisplayLocked( + oneDeviceEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked( + oneDeviceEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(), + /* includeDisabled= */ false)); + assertNull(mLogicalDisplayMapper.getDisplayLocked( + oneDeviceEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(), + /* includeDisabled= */ false)); + + // Now do it again to go back to state 1 + mLogicalDisplayMapper.setDeviceStateLocked(1, false); + mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); + advanceTime(1000); + final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked( + Process.SYSTEM_UID, false); + + // ensure all three displays are returned + assertEquals(3, threeDisplaysEnabled.length); + } + ///////////////// // Helper Methods ///////////////// + private void advanceTime(long timeMs) { + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + } + private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) { return createDisplayDevice( new TestUtils.TestDisplayAddress(), /* uniqueId */ "", type, width, height, flags); @@ -575,6 +756,7 @@ public class LogicalDisplayMapperTest { class TestDisplayDevice extends DisplayDevice { private DisplayDeviceInfo mInfo; private DisplayDeviceInfo mSentInfo; + private int mState; TestDisplayDevice() { super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java index 5a43530d44dd..1d70fc61c937 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java @@ -126,12 +126,12 @@ public class LogicalDisplayTest { verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); reset(t); - mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_DISABLED); + mLogicalDisplay.setEnabledLocked(false); mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); verify(t).setDisplayFlags(any(), eq(0)); reset(t); - mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_ENABLED); + mLogicalDisplay.setEnabledLocked(true); mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT)); diff --git a/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java new file mode 100644 index 000000000000..1c4ee691fc77 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/MediaButtonReceiverHolderTest.java @@ -0,0 +1,45 @@ +/* + * 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.media; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.google.common.truth.Truth; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class MediaButtonReceiverHolderTest { + + @Test + public void createMediaButtonReceiverHolder_resolvesNullComponentName() { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); + PendingIntent pi = PendingIntent.getBroadcast(context, /* requestCode= */ 0, intent, + PendingIntent.FLAG_IMMUTABLE); + MediaButtonReceiverHolder a = MediaButtonReceiverHolder.create(/* userId= */ 0, pi, + context.getPackageName()); + Truth.assertWithMessage("Component name must match PendingIntent creator package.").that( + a.getComponentName()).isNull(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/media/OWNERS b/services/tests/servicestests/src/com/android/server/media/OWNERS new file mode 100644 index 000000000000..55ffde223374 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 137631 +include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java index 136e79e02de6..261156611a06 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -456,9 +456,8 @@ public final class DeviceStateProviderImplTest { new DeviceState(1, "CLOSED", 0 /* flags */), new DeviceState(2, "HALF_OPENED", 0 /* flags */) }, mDeviceStateArrayCaptor.getValue()); - // onStateChanged() should be called because the provider could not find the sensor. - verify(listener).onStateChanged(mIntegerCaptor.capture()); - assertEquals(1, mIntegerCaptor.getValue().intValue()); + // onStateChanged() should not be called because the provider could not find the sensor. + verify(listener, never()).onStateChanged(mIntegerCaptor.capture()); } private static Sensor newSensor(String name, String type) throws Exception { 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 c81db9216c08..6258d6d29ae0 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 @@ public class PowerManagerServiceTest { } @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 aafc16db50da..febbffea50cd 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 @@ public class EventLoggerTest { } @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 @@ public class EventLoggerTest { // 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/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/Android.bp b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/Android.bp new file mode 100644 index 000000000000..f376b6fc9cf8 --- /dev/null +++ b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/Android.bp @@ -0,0 +1,37 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "MediaButtonReceiverHolderTestHelperApp", + + sdk_version: "current", + + srcs: ["**/*.java"], + + dex_preopt: { + enabled: false, + }, + optimize: { + enabled: false, + }, +} diff --git a/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/AndroidManifest.xml new file mode 100644 index 000000000000..3ba3dc27f10a --- /dev/null +++ b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.servicestests.apps.mediabuttonreceiverholdertesthelperapp"> + + <application> + <receiver + android:name=".FakeMediaButtonBroadcastReceiver" + android:enabled="true" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MEDIA_BUTTON" /> + </intent-filter> + </receiver> + </application> + +</manifest> diff --git a/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/OWNERS b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/OWNERS new file mode 100644 index 000000000000..55ffde223374 --- /dev/null +++ b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 137631 +include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/src/FakeMediaButtonBroadcastReceiver.java b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/src/FakeMediaButtonBroadcastReceiver.java new file mode 100644 index 000000000000..6fdd8be790cc --- /dev/null +++ b/services/tests/servicestests/test-apps/MediaButtonReceiverHolderTestHelperApp/src/FakeMediaButtonBroadcastReceiver.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package com.android.servicestests.apps.mediabuttonreceiverholdertesthelperapp; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +public class FakeMediaButtonBroadcastReceiver extends BroadcastReceiver { + + private static final String TAG = "FakeMediaButtonBroadcastReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + Log.v(TAG, "onReceive not expected"); + } +} 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 af10b9dba63f..d758e71c62a2 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 @@ import java.lang.reflect.Field; @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 3a6c0eb8fc2a..a83eb00dfa1f 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 @@ import org.mockito.Mock; @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 1ab7d7e0c81b..a410eedf4c29 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 @@ public class ActivityRecordTests extends WindowTestsBase { } } + @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 1575336600b4..8a15c30afbb1 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 com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; 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 @@ public class ActivityStartInterceptorTest { // 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 70b68c78f092..67334707db2a 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 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { 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 d99946f0a5c4..10f2270db19e 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.PixelFormat; 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 @@ public class DisplayPolicyTests extends WindowTestsBase { 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/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java index d3aa073c84d8..df7b3cdebe28 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java @@ -74,15 +74,15 @@ public class SyncEngineTests extends WindowTestsBase { int id = startSyncSet(bse, listener); bse.addToSyncSet(id, mockWC); - // Make sure a traversal is requested - verify(mWm.mWindowPlacerLocked, times(1)).requestTraversal(); + // The traversal is not requested because ready is not set. + verify(mWm.mWindowPlacerLocked, times(0)).requestTraversal(); bse.onSurfacePlacement(); verify(listener, times(0)).onTransactionReady(anyInt(), any()); bse.setReady(id); // Make sure a traversal is requested - verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal(); + verify(mWm.mWindowPlacerLocked).requestTraversal(); bse.onSurfacePlacement(); verify(listener, times(1)).onTransactionReady(eq(id), notNull()); @@ -103,14 +103,14 @@ public class SyncEngineTests extends WindowTestsBase { int id = startSyncSet(bse, listener); bse.addToSyncSet(id, mockWC); bse.setReady(id); - // Make sure traversals requested (one for add and another for setReady) - verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal(); + // Make sure traversals requested. + verify(mWm.mWindowPlacerLocked).requestTraversal(); bse.onSurfacePlacement(); verify(listener, times(0)).onTransactionReady(anyInt(), any()); mockWC.onSyncFinishedDrawing(); - // Make sure a (third) traversal is requested. - verify(mWm.mWindowPlacerLocked, times(3)).requestTraversal(); + // Make sure the second traversal is requested. + verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal(); bse.onSurfacePlacement(); verify(listener, times(1)).onTransactionReady(eq(id), notNull()); } @@ -127,8 +127,8 @@ public class SyncEngineTests extends WindowTestsBase { int id = startSyncSet(bse, listener); bse.addToSyncSet(id, mockWC); bse.setReady(id); - // Make sure traversals requested (one for add and another for setReady) - verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal(); + // Make sure traversals requested. + verify(mWm.mWindowPlacerLocked).requestTraversal(); bse.onSurfacePlacement(); verify(listener, times(0)).onTransactionReady(anyInt(), any()); 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 4e796c55dda2..8fda1917d0b5 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_LANDSCAPE; 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.never; 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 @@ public class TaskFragmentTest extends WindowTestsBase { 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/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index a4cc09a205f6..e7813ffb8e1a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -893,11 +893,10 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } @Test - public void testLaunchesPortraitUnresizableOnFreeformLandscapeDisplay() { + public void testLaunchesPortraitUnresizableOnFreeformDisplayWithFreeformSizeCompat() { mAtm.mDevEnableNonResizableMultiWindow = true; final TestDisplayContent freeformDisplay = createNewDisplayContent( WINDOWING_MODE_FREEFORM); - assertTrue(freeformDisplay.getBounds().width() > freeformDisplay.getBounds().height()); final ActivityOptions options = ActivityOptions.makeBasic(); mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea(); mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; @@ -905,42 +904,12 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setOptions(options).calculate()); - assertEquals(WINDOWING_MODE_UNDEFINED, mResult.mWindowingMode); - } - - @Test - public void testLaunchesLandscapeUnresizableOnFreeformLandscapeDisplay() { - mAtm.mDevEnableNonResizableMultiWindow = true; - final TestDisplayContent freeformDisplay = createNewDisplayContent( - WINDOWING_MODE_FREEFORM); - assertTrue(freeformDisplay.getBounds().width() > freeformDisplay.getBounds().height()); - final ActivityOptions options = ActivityOptions.makeBasic(); - mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea(); - mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; - mActivity.info.screenOrientation = SCREEN_ORIENTATION_LANDSCAPE; - assertEquals(RESULT_CONTINUE, - new CalculateRequestBuilder().setOptions(options).calculate()); - - assertEquals(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode); - } - - @Test - public void testLaunchesUndefinedUnresizableOnFreeformLandscapeDisplay() { - mAtm.mDevEnableNonResizableMultiWindow = true; - final TestDisplayContent freeformDisplay = createNewDisplayContent( + assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode, WINDOWING_MODE_FREEFORM); - assertTrue(freeformDisplay.getBounds().width() > freeformDisplay.getBounds().height()); - final ActivityOptions options = ActivityOptions.makeBasic(); - mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea(); - mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; - assertEquals(RESULT_CONTINUE, - new CalculateRequestBuilder().setOptions(options).calculate()); - - assertEquals(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode); } @Test - public void testForceMaximizingAppsOnNonFreeformDisplay() { + public void testSkipsForceMaximizingAppsOnNonFreeformDisplay() { final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM); options.setLaunchBounds(new Rect(0, 0, 200, 100)); @@ -954,9 +923,8 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setOptions(options).calculate()); - // Non-resizable apps must be launched in fullscreen in a fullscreen display regardless of - // other properties. - assertEquals(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode); + assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode, + WINDOWING_MODE_FULLSCREEN); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 35b9710f5528..59a31b105717 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -479,6 +479,8 @@ public class TransitionTests extends WindowTestsBase { wallpaperWindow.mHasSurface = true; doReturn(true).when(mDisplayContent).isAttached(); transition.collect(mDisplayContent); + assertFalse("The change of non-interesting window container should be skipped", + transition.mChanges.containsKey(mDisplayContent.getParent())); mDisplayContent.getWindowConfiguration().setRotation( (mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4); 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 9090c5550248..94b5b93e74c2 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.os.RemoteException; 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 @@ public class WallpaperControllerTests extends WindowTestsBase { 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 @@ public class WallpaperControllerTests extends WindowTestsBase { 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/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 1348770bd807..5a261bc6526b 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 @@ class WindowTestsBase extends SystemServiceTestsBase { } void startTransition() { - mOrganizer.startTransition(mLastTransit, null); + mOrganizer.startTransition(mLastTransit.getToken(), null); } void onTransactionReady(SurfaceControl.Transaction t) { @@ -1759,7 +1759,7 @@ class WindowTestsBase extends SystemServiceTestsBase { } public void finish() { - mController.finishTransition(mLastTransit); + mController.finishTransition(mLastTransit.getToken()); } } } diff --git a/services/usb/Android.bp b/services/usb/Android.bp index 52cfe25d9f26..133f924047da 100644 --- a/services/usb/Android.bp +++ b/services/usb/Android.bp @@ -33,5 +33,6 @@ java_library_static { "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 1c081c1c153d..ffdb07b27ad0 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.AdbManagerInternal; 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.UsbManager; 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.logging.nano.MetricsProto.MetricsEvent; 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.Map; 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } mControlFds.put(UsbManager.FUNCTION_PTP, ptpFd); - if (halNotPresent) { + if (mUsbGadgetHal == null) { /** * Initialze the legacy UsbHandler */ @@ -334,6 +340,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser alsaManager, permissionManager); } + mHandler.handlerInitDone(operationId); + if (nativeIsStartRequested()) { if (DEBUG) Slog.d(TAG, "accessory attached at boot"); startAccessoryMode(); @@ -455,6 +463,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser ACCESSORY_REQUEST_TIMEOUT); mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_HANDSHAKE_TIMEOUT), ACCESSORY_HANDSHAKE_TIMEOUT); - setCurrentFunctions(functions); + setCurrentFunctions(functions, operationId); } } @@ -504,6 +514,20 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } + 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, ""); } - setEnabledFunctions(mCurrentFunctions, true); + setEnabledFunctions(mCurrentFunctions, true, operationId); updateAdbNotification(false); } @@ -701,6 +763,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser @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 class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser // 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } - 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser /** * 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } + @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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } @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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser || !mCurrentFunctionsStr.equals(functions) || !mCurrentFunctionsApplied || forceRestart) { - Slog.i(TAG, "Setting USB config to " + functions); mCurrentFunctionsStr = functions; mCurrentOemFunctions = oemFunctions; mCurrentFunctionsApplied = false; @@ -1871,15 +1965,18 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false; return true; } - } - private static final class UsbHandlerHal extends UsbHandler { + @Override + public void setCurrentUsbFunctionsCb(long functions, + int status, int mRequest, long mFunctions, boolean mChargingFunctions){ + } - /** - * Proxy object for the usb gadget hal daemon. - */ - @GuardedBy("mGadgetProxyLock") - private IUsbGadget mGadgetProxy; + @Override + public void getUsbSpeedCb(int speed){ + } + } + + private final class UsbHandlerHal extends UsbHandler { private final Object mGadgetProxyLock = new Object(); @@ -1926,33 +2023,20 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser "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; - } - - 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; + 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); } - mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_2; } break; default: @@ -2101,56 +2169,31 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } - private class UsbGadgetCallback extends IUsbGadgetCallback.Stub { - int mRequest; - long mFunctions; - boolean mChargingFunctions; - - UsbGadgetCallback() { - } - - 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; - } + @Override + public void setCurrentUsbFunctionsCb(long functions, + int status, int mRequest, long mFunctions, boolean mChargingFunctions) { - 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); - } + if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT) + || (mFunctions != functions)) { + return; } - @Override - public void getCurrentUsbFunctionsCb(long functions, - int status) { - sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions, - status == Status.FUNCTIONS_APPLIED); + 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); } + } - @Override - public void getUsbSpeedCb(int speed) { - mUsbSpeed = speed; - } + @Override + public void getUsbSpeedCb(int speed) { + mUsbSpeed = speed; } - private void setUsbConfig(long config, boolean chargingFunctions) { + 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } } + + @Override + public void handlerInitDone(int operationId) { + mUsbGadgetHal.getCurrentUsbFunctions(operationId); + } } /* returns the currently attached USB accessory */ @@ -2270,6 +2319,21 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser * * @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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } 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 d821dee75d7d..d09f729bf4ea 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 @@ public class UsbService extends IUsbManager.Stub { } @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 000000000000..bdfe60ac07c1 --- /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 000000000000..267247b5b835 --- /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 000000000000..d268315a900f --- /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 000000000000..3e5ecc5eddf4 --- /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/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java index d5eea1f3ff35..76574542da4f 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamCopier.java @@ -17,16 +17,16 @@ package com.android.server.voiceinteraction; import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.service.voice.HotwordAudioStream.KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES; import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG; import android.annotation.NonNull; import android.app.AppOpsManager; -import android.media.permission.Identity; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import android.service.voice.HotwordAudioStream; import android.service.voice.HotwordDetectedResult; -import android.util.Pair; import android.util.Slog; import java.io.IOException; @@ -39,21 +39,38 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -final class HotwordAudioStreamManager { +/** + * Copies the audio streams in {@link HotwordDetectedResult}s. This allows the system to manage the + * lifetime of the {@link ParcelFileDescriptor}s and ensures that the flow of data is in the right + * direction from the {@link android.service.voice.HotwordDetectionService} to the client (i.e., the + * voice interactor). + * + * @hide + */ +final class HotwordAudioStreamCopier { - private static final String TAG = "HotwordAudioStreamManager"; + private static final String TAG = "HotwordAudioStreamCopier"; private static final String OP_MESSAGE = "Streaming hotword audio to VoiceInteractionService"; private static final String TASK_ID_PREFIX = "HotwordDetectedResult@"; private static final String THREAD_NAME_PREFIX = "Copy-"; + private static final int DEFAULT_COPY_BUFFER_LENGTH_BYTES = 2_560; + + // Corresponds to the OS pipe capacity in bytes + private static final int MAX_COPY_BUFFER_LENGTH_BYTES = 65_536; private final AppOpsManager mAppOpsManager; - private final Identity mVoiceInteractorIdentity; + private final int mVoiceInteractorUid; + private final String mVoiceInteractorPackageName; + private final String mVoiceInteractorAttributionTag; private final ExecutorService mExecutorService = Executors.newCachedThreadPool(); - HotwordAudioStreamManager(@NonNull AppOpsManager appOpsManager, - @NonNull Identity voiceInteractorIdentity) { + HotwordAudioStreamCopier(@NonNull AppOpsManager appOpsManager, + int voiceInteractorUid, @NonNull String voiceInteractorPackageName, + @NonNull String voiceInteractorAttributionTag) { mAppOpsManager = appOpsManager; - mVoiceInteractorIdentity = voiceInteractorIdentity; + mVoiceInteractorUid = voiceInteractorUid; + mVoiceInteractorPackageName = voiceInteractorPackageName; + mVoiceInteractorAttributionTag = voiceInteractorAttributionTag; } /** @@ -61,7 +78,7 @@ final class HotwordAudioStreamManager { * <p> * The returned {@link HotwordDetectedResult} is identical the one that was passed in, except * that the {@link ParcelFileDescriptor}s within {@link HotwordDetectedResult#getAudioStreams()} - * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamManager}. The + * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamCopier}. The * returned value should be passed on to the client (i.e., the voice interactor). * </p> * @@ -76,8 +93,7 @@ final class HotwordAudioStreamManager { } List<HotwordAudioStream> newAudioStreams = new ArrayList<>(audioStreams.size()); - List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> sourcesAndSinks = new ArrayList<>( - audioStreams.size()); + List<CopyTaskInfo> copyTaskInfos = new ArrayList<>(audioStreams.size()); for (HotwordAudioStream audioStream : audioStreams) { ParcelFileDescriptor[] clientPipe = ParcelFileDescriptor.createReliablePipe(); ParcelFileDescriptor clientAudioSource = clientPipe[0]; @@ -87,46 +103,69 @@ final class HotwordAudioStreamManager { clientAudioSource).build(); newAudioStreams.add(newAudioStream); + int copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES; + PersistableBundle metadata = audioStream.getMetadata(); + if (metadata.containsKey(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES)) { + copyBufferLength = metadata.getInt(KEY_AUDIO_STREAM_COPY_BUFFER_LENGTH_BYTES, -1); + if (copyBufferLength < 1 || copyBufferLength > MAX_COPY_BUFFER_LENGTH_BYTES) { + Slog.w(TAG, "Attempted to set an invalid copy buffer length (" + + copyBufferLength + ") for: " + audioStream); + copyBufferLength = DEFAULT_COPY_BUFFER_LENGTH_BYTES; + } else if (DEBUG) { + Slog.i(TAG, "Copy buffer length set to " + copyBufferLength + " for: " + + audioStream); + } + } + ParcelFileDescriptor serviceAudioSource = audioStream.getAudioStreamParcelFileDescriptor(); - sourcesAndSinks.add(new Pair<>(serviceAudioSource, clientAudioSink)); + copyTaskInfos.add(new CopyTaskInfo(serviceAudioSource, clientAudioSink, + copyBufferLength)); } String resultTaskId = TASK_ID_PREFIX + System.identityHashCode(result); - mExecutorService.execute(new HotwordDetectedResultCopyTask(resultTaskId, sourcesAndSinks)); + mExecutorService.execute(new HotwordDetectedResultCopyTask(resultTaskId, copyTaskInfos)); return result.buildUpon().setAudioStreams(newAudioStreams).build(); } + private static class CopyTaskInfo { + private final ParcelFileDescriptor mSource; + private final ParcelFileDescriptor mSink; + private final int mCopyBufferLength; + + CopyTaskInfo(ParcelFileDescriptor source, ParcelFileDescriptor sink, int copyBufferLength) { + mSource = source; + mSink = sink; + mCopyBufferLength = copyBufferLength; + } + } + private class HotwordDetectedResultCopyTask implements Runnable { private final String mResultTaskId; - private final List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> mSourcesAndSinks; + private final List<CopyTaskInfo> mCopyTaskInfos; private final ExecutorService mExecutorService = Executors.newCachedThreadPool(); - HotwordDetectedResultCopyTask(String resultTaskId, - List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> sourcesAndSinks) { + HotwordDetectedResultCopyTask(String resultTaskId, List<CopyTaskInfo> copyTaskInfos) { mResultTaskId = resultTaskId; - mSourcesAndSinks = sourcesAndSinks; + mCopyTaskInfos = copyTaskInfos; } @Override public void run() { Thread.currentThread().setName(THREAD_NAME_PREFIX + mResultTaskId); - int size = mSourcesAndSinks.size(); + int size = mCopyTaskInfos.size(); List<SingleAudioStreamCopyTask> tasks = new ArrayList<>(size); for (int i = 0; i < size; i++) { - Pair<ParcelFileDescriptor, ParcelFileDescriptor> sourceAndSink = - mSourcesAndSinks.get(i); - ParcelFileDescriptor serviceAudioSource = sourceAndSink.first; - ParcelFileDescriptor clientAudioSink = sourceAndSink.second; + CopyTaskInfo copyTaskInfo = mCopyTaskInfos.get(i); String streamTaskId = mResultTaskId + "@" + i; - tasks.add(new SingleAudioStreamCopyTask(streamTaskId, serviceAudioSource, - clientAudioSink)); + tasks.add(new SingleAudioStreamCopyTask(streamTaskId, copyTaskInfo.mSource, + copyTaskInfo.mSink, copyTaskInfo.mCopyBufferLength)); } if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, - mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, - mVoiceInteractorIdentity.attributionTag, OP_MESSAGE) == MODE_ALLOWED) { + mVoiceInteractorUid, mVoiceInteractorPackageName, + mVoiceInteractorAttributionTag, OP_MESSAGE) == MODE_ALLOWED) { try { // TODO(b/244599891): Set timeout, close after inactivity mExecutorService.invokeAll(tasks); @@ -135,25 +174,23 @@ final class HotwordAudioStreamManager { bestEffortPropagateError(e.getMessage()); } finally { mAppOpsManager.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, - mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, - mVoiceInteractorIdentity.attributionTag); + mVoiceInteractorUid, mVoiceInteractorPackageName, + mVoiceInteractorAttributionTag); } } else { bestEffortPropagateError( - "Failed to obtain RECORD_AUDIO_HOTWORD permission for " - + SoundTriggerSessionPermissionsDecorator.toString( - mVoiceInteractorIdentity)); + "Failed to obtain RECORD_AUDIO_HOTWORD permission for voice interactor with" + + " uid=" + mVoiceInteractorUid + + " packageName=" + mVoiceInteractorPackageName + + " attributionTag=" + mVoiceInteractorAttributionTag); } } private void bestEffortPropagateError(@NonNull String errorMessage) { try { - for (Pair<ParcelFileDescriptor, ParcelFileDescriptor> sourceAndSink : - mSourcesAndSinks) { - ParcelFileDescriptor serviceAudioSource = sourceAndSink.first; - ParcelFileDescriptor clientAudioSink = sourceAndSink.second; - serviceAudioSource.closeWithError(errorMessage); - clientAudioSink.closeWithError(errorMessage); + for (CopyTaskInfo copyTaskInfo : mCopyTaskInfos) { + copyTaskInfo.mSource.closeWithError(errorMessage); + copyTaskInfo.mSink.closeWithError(errorMessage); } } catch (IOException e) { Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e); @@ -162,18 +199,17 @@ final class HotwordAudioStreamManager { } private static class SingleAudioStreamCopyTask implements Callable<Void> { - // TODO: Make this buffer size customizable from updateState() - private static final int COPY_BUFFER_LENGTH = 2_560; - private final String mStreamTaskId; private final ParcelFileDescriptor mAudioSource; private final ParcelFileDescriptor mAudioSink; + private final int mCopyBufferLength; SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource, - ParcelFileDescriptor audioSink) { + ParcelFileDescriptor audioSink, int copyBufferLength) { mStreamTaskId = streamTaskId; mAudioSource = audioSource; mAudioSink = audioSink; + mCopyBufferLength = copyBufferLength; } @Override @@ -189,7 +225,7 @@ final class HotwordAudioStreamManager { try { fis = new ParcelFileDescriptor.AutoCloseInputStream(mAudioSource); fos = new ParcelFileDescriptor.AutoCloseOutputStream(mAudioSink); - byte[] buffer = new byte[COPY_BUFFER_LENGTH]; + byte[] buffer = new byte[mCopyBufferLength]; while (true) { if (Thread.interrupted()) { Slog.e(TAG, diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 415166328519..3bcba6c1af65 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -183,7 +183,7 @@ final class HotwordDetectionConnection { private final ScheduledExecutorService mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); private final AppOpsManager mAppOpsManager; - private final HotwordAudioStreamManager mHotwordAudioStreamManager; + private final HotwordAudioStreamCopier mHotwordAudioStreamCopier; @Nullable private final ScheduledFuture<?> mCancellationTaskFuture; private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false); private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied; @@ -245,8 +245,9 @@ final class HotwordDetectionConnection { mVoiceInteractionServiceUid = voiceInteractionServiceUid; mVoiceInteractorIdentity = voiceInteractorIdentity; mAppOpsManager = mContext.getSystemService(AppOpsManager.class); - mHotwordAudioStreamManager = new HotwordAudioStreamManager(mAppOpsManager, - mVoiceInteractorIdentity); + mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, + mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, + mVoiceInteractorIdentity.attributionTag); mDetectionComponentName = serviceName; mUser = userId; mCallback = callback; @@ -506,7 +507,7 @@ final class HotwordDetectionConnection { saveProximityValueToBundle(result); HotwordDetectedResult newResult; try { - newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result); + newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result); } catch (IOException e) { // TODO: Write event mSoftwareCallback.onError(); @@ -641,7 +642,7 @@ final class HotwordDetectionConnection { saveProximityValueToBundle(result); HotwordDetectedResult newResult; try { - newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result); + newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result); } catch (IOException e) { // TODO: Write event externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR); @@ -1000,7 +1001,7 @@ final class HotwordDetectionConnection { HotwordDetectedResult newResult; try { newResult = - mHotwordAudioStreamManager.startCopyingAudioStreams( + mHotwordAudioStreamCopier.startCopyingAudioStreams( triggerResult); } catch (IOException e) { // TODO: Write event diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java index b87b8f790338..eeafe910e13e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java @@ -183,7 +183,7 @@ final class TrustedHotwordDetectorSession { private final ScheduledExecutorService mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); private final AppOpsManager mAppOpsManager; - private final HotwordAudioStreamManager mHotwordAudioStreamManager; + private final HotwordAudioStreamCopier mHotwordAudioStreamCopier; @Nullable private final ScheduledFuture<?> mCancellationTaskFuture; private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false); private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied; @@ -245,8 +245,9 @@ final class TrustedHotwordDetectorSession { mVoiceInteractionServiceUid = voiceInteractionServiceUid; mVoiceInteractorIdentity = voiceInteractorIdentity; mAppOpsManager = mContext.getSystemService(AppOpsManager.class); - mHotwordAudioStreamManager = new HotwordAudioStreamManager(mAppOpsManager, - mVoiceInteractorIdentity); + mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, + mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, + mVoiceInteractorIdentity.attributionTag); mDetectionComponentName = serviceName; mUser = userId; mCallback = callback; @@ -506,7 +507,7 @@ final class TrustedHotwordDetectorSession { saveProximityValueToBundle(result); HotwordDetectedResult newResult; try { - newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result); + newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result); } catch (IOException e) { // TODO: Write event mSoftwareCallback.onError(); @@ -641,7 +642,7 @@ final class TrustedHotwordDetectorSession { saveProximityValueToBundle(result); HotwordDetectedResult newResult; try { - newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result); + newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result); } catch (IOException e) { // TODO: Write event externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR); @@ -1000,7 +1001,7 @@ final class TrustedHotwordDetectorSession { HotwordDetectedResult newResult; try { newResult = - mHotwordAudioStreamManager.startCopyingAudioStreams( + mHotwordAudioStreamCopier.startCopyingAudioStreams( triggerResult); } catch (IOException e) { // TODO: Write event diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc index 5cb0c171e06f..1d3b648175cc 100644 --- a/startop/view_compiler/apk_layout_compiler.cc +++ b/startop/view_compiler/apk_layout_compiler.cc @@ -100,56 +100,60 @@ void CompileApkAssetsLayouts(const std::unique_ptr<android::ApkAssets>& assets, 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 86b98f1cbe79..2435243f0044 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -5,6 +5,7 @@ import android.net.NetworkAgent; 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 @@ public class Annotation { 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 class Annotation { }) 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 b7bef39aa275..1dc64a9200fc 100644 --- a/telephony/java/android/telephony/CallAttributes.java +++ b/telephony/java/android/telephony/CallAttributes.java @@ -29,8 +29,10 @@ import java.util.Objects; * 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 000000000000..dd5af8e65921 --- /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 000000000000..51ecfb0a0be8 --- /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 e6d7df34f755..1ea7fdc982a5 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -78,8 +78,9 @@ public final class ImsCallProfile implements Parcelable { 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/android/telephony/ims/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl index 85191734872a..ea4480dc7958 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl @@ -65,6 +65,7 @@ interface IImsMmTelFeature { oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry, in byte[] pdu); oneway void acknowledgeSms(int token, int messageRef, int result); + oneway void acknowledgeSmsWithPdu(int token, int messageRef, int result, in byte[] pdu); oneway void acknowledgeSmsReport(int token, int messageRef, int result); String getSmsFormat(); oneway void onSmsReady(); diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index 8147759769e6..d776928965e5 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -275,6 +275,12 @@ public class MmTelFeature extends ImsFeature { } @Override + public void acknowledgeSmsWithPdu(int token, int messageRef, int result, byte[] pdu) { + executeMethodAsyncNoException(() -> MmTelFeature.this + .acknowledgeSms(token, messageRef, result, pdu), "acknowledgeSms"); + } + + @Override public void acknowledgeSmsReport(int token, int messageRef, int result) { executeMethodAsyncNoException(() -> MmTelFeature.this .acknowledgeSmsReport(token, messageRef, result), "acknowledgeSmsReport"); @@ -1087,6 +1093,11 @@ public class MmTelFeature extends ImsFeature { getSmsImplementation().acknowledgeSms(token, messageRef, result); } + private void acknowledgeSms(int token, int messageRef, + @ImsSmsImplBase.DeliverStatusResult int result, byte[] pdu) { + getSmsImplementation().acknowledgeSms(token, messageRef, result, pdu); + } + private void acknowledgeSmsReport(int token, int messageRef, @ImsSmsImplBase.StatusReportResult int result) { getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java index fb997d118419..66833d18a945 100644 --- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java @@ -18,6 +18,7 @@ package android.telephony.ims.stub; import android.annotation.IntDef; import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.RemoteException; import android.telephony.SmsManager; @@ -174,6 +175,9 @@ public class ImsSmsImplBase { * {@link #onSmsReceived(int, String, byte[])} has been called to deliver the result to the IMS * provider. * + * If the framework needs to provide the PDU used to acknowledge the SMS, + * {@link #acknowledgeSms(int, int, int, byte[])} will be called. + * * @param token token provided in {@link #onSmsReceived(int, String, byte[])} * @param messageRef the message reference, which may be 1 byte if it is in * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in @@ -186,6 +190,27 @@ public class ImsSmsImplBase { } /** + * This method will be called by the platform after + * {@link #onSmsReceived(int, String, byte[])} has been called to acknowledge an incoming SMS. + * + * This method is only called in cases where the framework needs to provide the PDU such as the + * case where we provide the Short Message Transfer Layer PDU (see 3GPP TS 23.040). Otherwise, + * {@link #acknowledgeSms(int, int, int)} will be used. + * + * @param token token provided in {@link #onSmsReceived(int, String, byte[])} + * @param messageRef the message reference, which may be 1 byte if it is in + * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in + * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B). + * @param result result of delivering the message. + * @param pdu PDU representing the contents of the message. + */ + public void acknowledgeSms(int token, @IntRange(from = 0, to = 65535) int messageRef, + @DeliverStatusResult int result, @NonNull byte[] pdu) { + Log.e(LOG_TAG, "acknowledgeSms() not implemented. acknowledgeSms(int, int, int) called."); + acknowledgeSms(token, messageRef, result); + } + + /** * This method will be triggered by the platform after * {@link #onSmsStatusReportReceived(int, int, String, byte[])} or * {@link #onSmsStatusReportReceived(int, String, byte[])} has been called to provide the diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 0c14dbaa5a3f..a1257e39f668 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -546,6 +546,8 @@ public interface RILConstants { 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 @@ public interface RILConstants { 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 8a1e1fabd131..3f6a75d74d36 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 @@ constructor( 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 000000000000..945de3363669 --- /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 b9c875ab5938..ef42766bb04e 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 @@ class CloseImeWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpe 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 1dc3ca55caba..c92fce33188e 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 @@ class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSp 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 a6bd791282b8..7d7953b0c4dc 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 @@ class OpenImeWindowAndCloseTest(testSpec: FlickerTestParameter) : BaseTest(testS 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 b43efea4c647..9919d873525d 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 @@ class OpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) { } } + @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 1973ec0a98a8..ad14d0d2b612 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 @@ class ChangeAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition @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 4faeb246037e..8e3fd4066000 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 @@ abstract class RotationTransition(testSpec: FlickerTestParameter) : BaseTest(tes } } } + + 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 a08db29fe7a4..d0d4122423fe 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 @@ open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTra @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 d133f6fbdd87..e2099e652c49 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 static org.mockito.Mockito.when; 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 @@ public class UsbManagerTestLib { 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 @@ public class UsbManagerTestLib { } 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 class UsbManagerTestLib { } 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 86bcb7290d95..4103ca7c8ca8 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 @@ public class UsbHandlerTest { } @Override - protected void setEnabledFunctions(long functions, boolean force) { + protected void setEnabledFunctions(long functions, boolean force, int operationId) { mCurrentFunctions = functions; } @@ -134,6 +134,20 @@ public class UsbHandlerTest { 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/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 15a6afc5ff7c..7c5dcf8b95f7 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -50,6 +50,7 @@ public final class FrameworksTestsFilter extends SelectTest { "android.app.servertransaction.", // all tests under the package. "android.view.CutoutSpecificationTest", "android.view.DisplayCutoutTest", + "android.view.DisplayShapeTest", "android.view.InsetsAnimationControlImplTest", "android.view.InsetsControllerTest", "android.view.InsetsFlagsTest", diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index 9b9cde2f37da..6b1fd9ff11eb 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -72,7 +72,7 @@ static ApkFormat DetermineApkFormat(io::IFileCollection* apk) { } } -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 a4aff3f8376a..4cd7eae0a5e2 100644 --- a/tools/aapt2/LoadedApk.h +++ b/tools/aapt2/LoadedApk.h @@ -45,7 +45,7 @@ class LoadedApk { 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 0b4905253d20..0b08c3276cb5 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -36,7 +36,7 @@ struct NameManglerPolicy { * 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 @@ class NameMangler { 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 @@ class NameMangler { * 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 df8c3b9956d0..cfcb2bb4f99d 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -138,11 +138,11 @@ ResourceNamedTypeRef ResourceNamedTypeWithDefaultName(ResourceType t) { 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 @@ std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::String 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 9cfaf4742ca5..7ba3277d2093 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -74,7 +74,7 @@ android::StringPiece to_string(ResourceType type); /** * 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 @@ struct ResourceNamedType { 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 @@ struct ResourceNamedTypeRef { 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 @@ struct ResourceName { 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 @@ struct ResourceNameRef { 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 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) // // 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 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNamedType& // // 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 @@ inline ResourceNamedType ResourceNamedTypeRef::ToResourceNamedType() const { 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 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNamedTypeRe // 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 ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) 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 19fd306d5a42..fa9a98f136cb 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -50,11 +50,11 @@ constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final" 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 @@ static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) { 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 @@ static uint32_t ParseFormatType(const StringPiece& piece) { 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 @@ static bool AddResourcesToTable(ResourceTable* table, android::IDiagnostics* dia 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 @@ bool ResourceParser::FlattenXmlSubtree( // 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 @@ bool ResourceParser::FlattenXmlSubtree( // 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 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { // 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 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // 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 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // 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 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, 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 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, 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 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, 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 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, 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 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, } 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 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou 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 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou } 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 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource 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 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource 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 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, 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 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, } 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 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, 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 396ce9767fe9..012a056dccf3 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -122,7 +122,7 @@ class ResourceParser { 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 fe7eb96ffe16..b59b16574c42 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -65,11 +65,11 @@ class ResourceParserTest : public ::testing::Test { 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 cb4811445ed1..a3b0b45df5c3 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -49,21 +49,21 @@ bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, } 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 @@ bool less_than_struct_with_name_and_id(const T& lhs, } 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 @@ T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Fu struct ConfigKey { const ConfigDescription* config; - const StringPiece& product; + StringPiece product; }; template <typename T> @@ -104,12 +104,12 @@ bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) { 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 @@ ResourceTableType* ResourceTablePackage::FindOrCreateType(const ResourceNamedTyp }); } -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 @@ const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescrip } 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 f49ce8147f71..bb286a8abdaa 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -71,12 +71,11 @@ struct StagedId { 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 @@ class ResourceConfigValue { // 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 @@ class ResourceEntry { // 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 @@ class ResourceEntry { 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 @@ class ResourceTableType { : 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 @@ class ResourceTablePackage { 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 @@ class ResourceTable { // 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 0cf84736a081..54b98d13aa0a 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -187,7 +187,7 @@ static StringPiece LevelToString(Visibility::Level level) { 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 41c7435b534d..5a118a902963 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -109,8 +109,7 @@ std::optional<ResourceName> ToResourceName(const android::AssetManager2::Resourc 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 @@ bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref, 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 @@ bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref, 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 @@ bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) { return false; } -bool IsAttributeReference(const StringPiece& str) { +bool IsAttributeReference(StringPiece str) { return ParseAttributeReference(str, nullptr); } @@ -247,7 +246,7 @@ bool IsAttributeReference(const StringPiece& str) { * <[*]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 @@ std::optional<Reference> ParseStyleParentReference(const StringPiece& str, std:: 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 @@ std::optional<Reference> ParseXmlAttributeName(const StringPiece& str) { 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 @@ std::unique_ptr<Reference> TryParseReference(const StringPiece& str, 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 @@ std::unique_ptr<BinaryPrimitive> MakeEmpty() { 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 @@ std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, 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 @@ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, 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 @@ static uint32_t ParseHex(char c, bool* out_error) { } } -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 @@ std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) { : 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 @@ std::optional<bool> ParseBool(const StringPiece& str) { 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 @@ std::optional<uint32_t> ParseInt(const StringPiece& str) { 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 @@ std::optional<ResourceId> ParseResourceId(const StringPiece& str) { 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 @@ std::optional<int> ParseSdkVersion(const StringPiece& str) { 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 @@ std::unique_ptr<BinaryPrimitive> MakeBool(bool val) { 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 @@ std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t val) { 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 @@ uint32_t AndroidTypeToAttributeTypeMask(uint16_t type) { } 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 @@ std::unique_ptr<Item> TryParseItemForAttribute( * 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 22cf3459809d..f30f4acfec7a 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -38,7 +38,7 @@ namespace ResourceUtils { * `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 @@ bool ParseResourceName(const android::StringPiece& str, ResourceNameRef* out_res * 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 @@ std::optional<ResourceName> ToResourceName(const android::AssetManager2::Resourc * 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 @@ std::optional<int> ParseSdkVersion(const android::StringPiece& str); * ?[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 @@ std::optional<Reference> ParseStyleParentReference(const android::StringPiece& s * * 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 @@ std::optional<Reference> ParseXmlAttributeName(const android::StringPiece& str); * 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 @@ std::unique_ptr<BinaryPrimitive> MakeEmpty(); * 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 @@ std::unique_ptr<BinaryPrimitive> MakeBool(bool val); * 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 @@ std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t value); * 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 @@ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr, * 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 c4d54be01efe..a5754e0d168f 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -206,7 +206,7 @@ void Reference::PrettyPrint(Printer* printer) const { 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 f5167a1ac8e6..6f9dccbd3bcc 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -83,8 +83,8 @@ class Value { 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 @@ struct Reference : public TransformableItem<Reference, BaseItem<Reference>> { 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 34e8edb0a47f..a7c5479b56fd 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -77,7 +77,7 @@ ApiVersion FindAttributeSdkLevel(const ResourceId& id) { 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 0bd61c04f2b2..40bcef787815 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -63,7 +63,7 @@ enum : ApiVersion { }; 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 697b110443fd..3c0831c7ec0d 100644 --- a/tools/aapt2/cmd/ApkInfo.cpp +++ b/tools/aapt2/cmd/ApkInfo.cpp @@ -64,7 +64,7 @@ int ApkInfoCommand::Action(const std::vector<std::string>& args) { 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 b1452fad0e8f..514651e92c27 100644 --- a/tools/aapt2/cmd/Command.cpp +++ b/tools/aapt2/cmd/Command.cpp @@ -33,7 +33,7 @@ using android::StringPiece; 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 @@ std::string GetSafePath(const StringPiece& arg) { 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::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool } } -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 @@ int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_err 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 8678cda59856..1416e980ed19 100644 --- a/tools/aapt2/cmd/Command.h +++ b/tools/aapt2/cmd/Command.h @@ -30,13 +30,10 @@ namespace aapt { 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 @@ class Command { 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 @@ class Command { 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 0409f7391f79..03f9715fb265 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -125,8 +125,12 @@ static std::optional<ResourcePathData> ExtractResourcePathData(const std::string 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 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, 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 @@ static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const Res 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 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, 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 52e113e0dbdc..612e3a630013 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -387,7 +387,7 @@ int ConvertCommand::Action(const std::vector<std::string>& args) { } 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 423e939398d7..5bfc73233bfe 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -78,7 +78,7 @@ class DiffContext : public IAaptContext { 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 a8d229956b73..97404fc69af2 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -126,8 +126,8 @@ class LinkContext : public IAaptContext { 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 @@ class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate { 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 @@ static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, } 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 @@ static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, 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 @@ struct R { }; 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 @@ class Linker { 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 @@ class Linker { 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 @@ int LinkCommand::Action(const std::vector<std::string>& args) { 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 042926c1943a..d7a39bfb8c0b 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -154,13 +154,22 @@ class Optimizer { 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 @@ class Optimizer { 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 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { 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 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { 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 794a87b0faa5..1879f25bc1b0 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -58,6 +58,7 @@ struct OptimizeOptions { 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 @@ struct OptimizeOptions { // 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 @@ class OptimizeCommand : public Command { 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 56e2f5243e96..92849cf02d48 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -34,8 +34,7 @@ using ::android::base::StringPrintf; 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 @@ std::optional<uint16_t> ParseTargetDensityParameter(const StringPiece& arg, 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 @@ bool ParseSplitParameter(const StringPiece& arg, android::IDiagnostics* diag, st *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 @@ std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std 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 3d4ca245ee28..169d5f92f7dd 100644 --- a/tools/aapt2/cmd/Util.h +++ b/tools/aapt2/cmd/Util.h @@ -34,13 +34,13 @@ namespace aapt { // 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 c931da48c889..4538ecc56e4c 100644 --- a/tools/aapt2/compile/NinePatch.cpp +++ b/tools/aapt2/compile/NinePatch.cpp @@ -218,11 +218,9 @@ inline static uint32_t get_alpha(uint32_t color) { 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 7f8d923edd03..a8b7dd18f12f 100644 --- a/tools/aapt2/compile/Png.h +++ b/tools/aapt2/compile/Png.h @@ -59,7 +59,7 @@ class Png { */ 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 4db2392b4eab..2e55d0c82b7b 100644 --- a/tools/aapt2/compile/PngChunkFilter.cpp +++ b/tools/aapt2/compile/PngChunkFilter.cpp @@ -70,7 +70,7 @@ static bool IsPngChunkAllowed(uint32_t type) { } } -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 3a515fad3202..463ce787dae7 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 @@ class PseudoMethodAccent : public PseudoMethodImpl { 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 @@ void Pseudolocalizer::SetMethod(Method method) { } } -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 @@ std::string Pseudolocalizer::Text(const StringPiece& text) { } 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 bool IsPossibleNormalPlaceholderEnd(const char c) { } 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 @@ std::string PseudoMethodAccent::End() { * * 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 @@ std::string PseudoMethodAccent::Text(const StringPiece& source) { 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 @@ std::string PseudoMethodBidi::Text(const StringPiece& source) { 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 @@ std::string PseudoMethodBidi::Text(const StringPiece& source) { } 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 4dedc700a8e7..2b94bcc87fc9 100644 --- a/tools/aapt2/compile/Pseudolocalizer.h +++ b/tools/aapt2/compile/Pseudolocalizer.h @@ -31,8 +31,8 @@ class PseudoMethodImpl { 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 @@ class Pseudolocalizer { 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 6bba11e26e6a..1b0325325778 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -152,7 +152,7 @@ bool CopyXmlReferences(const std::optional<std::string>& name, const Group<T>& g * 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 @@ std::optional<PostProcessingConfiguration> ExtractConfiguration(const std::strin 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 @@ std::optional<std::string> ToBaseName(std::string result, const StringPiece& apk // 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 @@ std::optional<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& 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 @@ ConfigurationParser::ConfigurationParser(std::string contents, const std::string } 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 @@ std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse( } // 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 @@ bool ArtifactFormatTagHandler(PostProcessingConfiguration* config, Element* root 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 @@ bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_eleme 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 @@ bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* 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 @@ bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_el 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 @@ bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root 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 @@ bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* 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 2c8221d5108b..d66f4ab000a3 100644 --- a/tools/aapt2/configuration/ConfigurationParser.h +++ b/tools/aapt2/configuration/ConfigurationParser.h @@ -43,7 +43,7 @@ enum class Abi { }; /** 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 @@ class ConfigurationParser { * 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 3028c3f58e4e..198f730f1e12 100644 --- a/tools/aapt2/configuration/ConfigurationParser.internal.h +++ b/tools/aapt2/configuration/ConfigurationParser.internal.h @@ -138,13 +138,12 @@ struct ConfiguredArtifact { 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 c4c002d16ebb..d60869af2846 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -1076,7 +1076,7 @@ class FeatureGroup : public ManifestExtractor::Element { /** 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 @@ class UsesPermission : public ManifestExtractor::Element { 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 @@ class UsesPermissionSdk23 : public ManifestExtractor::Element { 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 9ace82ad4af7..908b1714bd14 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 @@ bool AbiFilter::Keep(const std::string& path) { } // 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 2832711efb2c..7380f3f479ae 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 @@ class AbiFilter : public IPathFilter { 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 f932f9ccc82e..baf4791f76c8 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 @@ class IPathFilter { 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 @@ class PrefixFilter : public IPathFilter { } /** 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 @@ class FilterChain : public IPathFilter { } /** 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 80c16188aca4..e9a93d8b12ad 100644 --- a/tools/aapt2/format/Archive.cpp +++ b/tools/aapt2/format/Archive.cpp @@ -40,8 +40,8 @@ class DirectoryWriter : public IArchiveWriter { 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 @@ class DirectoryWriter : public IArchiveWriter { 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 @@ class DirectoryWriter : public IArchiveWriter { 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 @@ class ZipFileWriter : public IArchiveWriter { 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 @@ class ZipFileWriter : public IArchiveWriter { 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 @@ class ZipFileWriter : public IArchiveWriter { 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 @@ class ZipFileWriter : public IArchiveWriter { } // 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> CreateDirectoryArchiveWriter(android::IDiagnosti } 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 55b0b2f0f017..6cde753a255d 100644 --- a/tools/aapt2/format/Archive.h +++ b/tools/aapt2/format/Archive.h @@ -46,12 +46,12 @@ class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream { 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 @@ class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream { }; 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 ceed3740f37a..3c44da710d94 100644 --- a/tools/aapt2/format/Archive_test.cpp +++ b/tools/aapt2/format/Archive_test.cpp @@ -50,7 +50,7 @@ std::unique_ptr<IArchiveWriter> MakeDirectoryWriter(const std::string& output_pa } 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 82918629f1f4..75dcba581c90 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -373,7 +373,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, 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 f19223411232..8c594ba553a0 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 @@ class PackageFlattener { // 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 @@ class PackageFlattener { } 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 35254ba24e53..60605d2fb7fd 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 @@ struct TableFlattenerOptions { // // 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 @@ class TableFlattener : public IResourceTableConsumer { 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 d08b4a3e5deb..0f1168514c4a 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -84,7 +84,7 @@ class TableFlattenerTest : public ::testing::Test { 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 983e6467fab0..05f975177cd1 100644 --- a/tools/aapt2/format/binary/XmlFlattener.cpp +++ b/tools/aapt2/format/binary/XmlFlattener.cpp @@ -79,7 +79,7 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { } 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 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { // 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 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { // 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 a6d58fd38f09..0e40124aa79e 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 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table } 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 5adc5e639830..ecfdba83a2e8 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -35,7 +35,7 @@ namespace aapt { 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 @@ TEST(ProtoSerializeTest, SerializeAndDeserializePrimitives) { 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 422658a0309e..08d497def8a4 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -101,7 +101,7 @@ class IFileCollection { 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 3f071af08844..a64982a7fa5c 100644 --- a/tools/aapt2/io/FileSystem.cpp +++ b/tools/aapt2/io/FileSystem.cpp @@ -67,8 +67,8 @@ IFile* FileCollectionIterator::Next() { 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::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiec 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 @@ std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiec 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 bc03b9b4391e..0e798fc1b975 100644 --- a/tools/aapt2/io/FileSystem.h +++ b/tools/aapt2/io/FileSystem.h @@ -60,12 +60,11 @@ class FileCollection : public IFileCollection { 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 @@ class FileCollection : public IFileCollection { 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 4ca04a8c7477..9c497882b99b 100644 --- a/tools/aapt2/io/StringStream.cpp +++ b/tools/aapt2/io/StringStream.cpp @@ -21,7 +21,7 @@ using ::android::StringPiece; 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 f29890ab7ee5..f7bdecca0dee 100644 --- a/tools/aapt2/io/StringStream.h +++ b/tools/aapt2/io/StringStream.h @@ -29,7 +29,7 @@ namespace io { 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 afe54d408361..79d8d527fe8b 100644 --- a/tools/aapt2/io/Util.cpp +++ b/tools/aapt2/io/Util.cpp @@ -26,7 +26,7 @@ using ::google::protobuf::io::ZeroCopyOutputStream; 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 @@ bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std: 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 CopyFileToArchive(IAaptContext* context, io::IFile* file, const std::string } 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 @@ bool Copy(OutputStream* out, InputStream* in) { 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 1b48a288d255..685f522a2e71 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 400269c41230..4a5385d90d3b 100644 --- a/tools/aapt2/io/ZipArchive.cpp +++ b/tools/aapt2/io/ZipArchive.cpp @@ -91,8 +91,8 @@ IFile* ZipFileCollectionIterator::Next() { 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 @@ std::unique_ptr<ZipFileCollection> ZipFileCollection::Create( 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 @@ std::unique_ptr<ZipFileCollection> ZipFileCollection::Create( 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 78c9c211ab57..c263aa490d22 100644 --- a/tools/aapt2/io/ZipArchive.h +++ b/tools/aapt2/io/ZipArchive.h @@ -61,10 +61,10 @@ class ZipFileCollectionIterator : public IFileCollectionIterator { // 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 @@ class ZipFileCollection : public IFileCollection { 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 482d91aeb491..87da09a7b054 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -30,7 +30,7 @@ using ::android::StringPiece; 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 @@ static std::array<AnnotationRule, 2> sAnnotationRules = {{ }}; 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 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) { 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 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) { // 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 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) { 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::AppendNewLine() { 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 f217afb16f32..db3437e3b5b1 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -56,11 +56,11 @@ class AnnotationProcessor { // 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 3163497f0da6..98f3bd2018b0 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -27,8 +27,8 @@ void ClassMember::Print(bool /*final*/, Printer* printer, bool strip_api_annotat 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 @@ constexpr static const char* sWarningHeader = " * 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 2acdadb3c034..63c99821a836 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -59,8 +59,8 @@ class ClassMember { 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 @@ class PrimitiveMember : public ClassMember { 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 @@ using StringMember = PrimitiveMember<std::string>; 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 @@ using ResourceArrayMember = PrimitiveArrayMember<std::variant<ResourceId, FieldR 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 @@ enum class ClassQualifier { kNone, kStatic }; 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 a25ca22c288d..7665d0e8d9cb 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -57,14 +57,14 @@ static const std::set<StringPiece> sJavaIdentifiers = { "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 @@ std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) // 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 bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) { 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 @@ bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res 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 @@ bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res 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 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso } 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 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso } 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 @@ std::optional<std::string> JavaClassGenerator::UnmangleResource( 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 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate 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 @@ static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations } } -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 b45a2f12db35..234df04472ce 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -70,16 +70,16 @@ class JavaClassGenerator { // 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 @@ class JavaClassGenerator { // 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 @@ class JavaClassGenerator { // 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 d0850b800e4f..56d90758ee73 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -646,8 +646,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn 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 void FullyQualifyClassName(const StringPiece& package, const StringPiece& } } -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 @@ static bool RenameManifestPackage(const StringPiece& package_override, xml::Elem 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 8d1a647b494d..7180ae6b8bc7 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -61,12 +61,12 @@ struct ManifestFixerTest : public ::testing::Test { .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 f2a93a868bb7..9dadfb26a3f8 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -189,8 +189,7 @@ class EmptyDeclStack : public xml::IPackageDeclStack { 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 @@ struct MacroDeclStack : public xml::IPackageDeclStack { : 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 c9f0964193d2..67a48283e8b6 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -66,7 +66,7 @@ bool TableMerger::MergeImpl(const android::Source& src, ResourceTable* table, bo // 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 @@ std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile( 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 2ba212372966..37daf42f51e5 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -61,7 +61,7 @@ class TableMerger { // 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 @@ class TableMerger { // 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 @@ class TableMerger { 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 f994e27e4e5b..f01db3ddca2e 100644 --- a/tools/aapt2/optimize/MultiApkGenerator.cpp +++ b/tools/aapt2/optimize/MultiApkGenerator.cpp @@ -113,12 +113,12 @@ class ContextWrapper : public IAaptContext { }; 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 f704f26bfd29..cc21093084b5 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 @@ static const char base64_chars[] = 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 @@ int OptimalShortenedLength(int num_resources) { } } -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 @@ struct PathComparator { } }; -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 @@ bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) { 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 1ea32db12815..5ccf54383aae 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 @@ class ResourceTable; // 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 a3339d486d4a..7f57b71ddf76 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 @@ TEST(ObfuscatorTest, FileRefPathsChangedInResourceTable) { .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(ObfuscatorTest, SkipColorFileRefPaths) { 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 @@ TEST(ObfuscatorTest, KeepExtensions) { .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(ObfuscatorTest, DeterministicallyHandleCollisions) { 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 @@ TEST(ObfuscatorTest, DeterministicallyHandleCollisions) { 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, DeterministicallyHandleCollisions) { } } +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 aa0d0c095f57..18dcd6bace77 100644 --- a/tools/aapt2/optimize/VersionCollapser_test.cpp +++ b/tools/aapt2/optimize/VersionCollapser_test.cpp @@ -23,7 +23,7 @@ using android::StringPiece; 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 92b45c397eed..bca62da447b0 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -218,7 +218,7 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( 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 c17837c224ab..b09ff702ca58 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -192,7 +192,7 @@ class AssetManagerSymbolSource : public ISymbolSource { 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 30336e27b907..65f63dc68e54 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -34,61 +34,53 @@ using ::android::StringPiece; 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 @@ ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& 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 @@ ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, 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 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na 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 @@ std::unique_ptr<ResourceTable> ResourceTableBuilder::Build() { 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 @@ AttributeBuilder& AttributeBuilder::SetWeak(bool weak) { 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 @@ std::unique_ptr<Attribute> AttributeBuilder::Build() { 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 @@ std::unique_ptr<Style> StyleBuilder::Build() { 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 @@ std::unique_ptr<Styleable> StyleableBuilder::Build() { 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> BuildXmlDom(const StringPiece& str) { } 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 780bd0df8f16..f03d6fcd03f1 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -38,40 +38,35 @@ class ResourceTableBuilder { 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(android::StringPiece name, + const android::ConfigDescription& config, 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& 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 @@ class ResourceTableBuilder { 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 @@ class ValueBuilder { return *this; } - ValueBuilder& SetComment(const android::StringPiece& str) { + ValueBuilder& SetComment(android::StringPiece str) { value_->SetComment(str); return *this; } @@ -121,7 +116,7 @@ class AttributeBuilder { 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 AttributeBuilder { 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 StyleBuilder { 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 @@ class StyleableBuilder { 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 eca0c1c12bab..cdf245341844 100644 --- a/tools/aapt2/test/Common.cpp +++ b/tools/aapt2/test/Common.cpp @@ -44,10 +44,9 @@ android::IDiagnostics* GetDiagnostics() { } 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 3f28361f6c2b..83a0f3f3f652 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -39,22 +39,22 @@ namespace test { 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 @@ T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& } 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 4e4973ea39be..c5331fb87381 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -52,8 +52,8 @@ class Context : public IAaptContext { 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 @@ class ContextBuilder { 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 ContextBuilder { 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 @@ class StaticSymbolSourceBuilder { 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 dbc0e3643104..428372f31d0d 100644 --- a/tools/aapt2/test/Fixture.cpp +++ b/tools/aapt2/test/Fixture.cpp @@ -38,8 +38,8 @@ namespace aapt { 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 @@ void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& } 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 @@ bool CommandTestFixture::Link(const std::vector<std::string>& args, android::IDi 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::string CommandTestFixture::GetDefaultManifest(const char* package_name) { } 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 61403b7b0a6d..ba4a734e03bb 100644 --- a/tools/aapt2/test/Fixture.h +++ b/tools/aapt2/test/Fixture.h @@ -48,7 +48,7 @@ class TestDirectoryFixture : public ::testing::Test { // 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 @@ class CommandTestFixture : public TestDirectoryFixture { // 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 243800c9385f..8e491aca794d 100644 --- a/tools/aapt2/text/Printer.cpp +++ b/tools/aapt2/text/Printer.cpp @@ -26,7 +26,7 @@ using ::android::StringPiece; namespace aapt { namespace text { -Printer& Printer::Println(const StringPiece& str) { +Printer& Printer::Println(StringPiece str) { Print(str); return Print("\n"); } @@ -35,7 +35,7 @@ Printer& Printer::Println() { return Print("\n"); } -Printer& Printer::Print(const StringPiece& str) { +Printer& Printer::Print(StringPiece str) { if (error_) { return *this; } @@ -47,7 +47,7 @@ Printer& Printer::Print(const StringPiece& str) { 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 f399f8ea5e0f..f7ad98bfd981 100644 --- a/tools/aapt2/text/Printer.h +++ b/tools/aapt2/text/Printer.h @@ -31,8 +31,8 @@ class Printer { 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 3735b3e841e0..5e25be3e2812 100644 --- a/tools/aapt2/text/Unicode.cpp +++ b/tools/aapt2/text/Unicode.cpp @@ -77,7 +77,7 @@ bool IsWhitespace(char32_t codepoint) { (codepoint == 0x3000); } -bool IsJavaIdentifier(const StringPiece& str) { +bool IsJavaIdentifier(StringPiece str) { Utf8Iterator iter(str); // Check the first character. @@ -99,7 +99,7 @@ bool IsJavaIdentifier(const StringPiece& str) { 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 546714e9a290..ab3e82b00f08 100644 --- a/tools/aapt2/text/Unicode.h +++ b/tools/aapt2/text/Unicode.h @@ -46,11 +46,11 @@ bool IsWhitespace(char32_t codepoint); // 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 20b9073b9a26..0bd8a375a255 100644 --- a/tools/aapt2/text/Utf8Iterator.cpp +++ b/tools/aapt2/text/Utf8Iterator.cpp @@ -24,7 +24,7 @@ using ::android::StringPiece; 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 9318401876d1..2bba1984a8ce 100644 --- a/tools/aapt2/text/Utf8Iterator.h +++ b/tools/aapt2/text/Utf8Iterator.h @@ -25,7 +25,7 @@ namespace text { 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 b4b31d9daf6e..da5373936306 100644 --- a/tools/aapt2/trace/TraceBuffer.cpp +++ b/tools/aapt2/trace/TraceBuffer.cpp @@ -103,7 +103,7 @@ Trace::Trace(const std::string& tag, const std::vector<android::StringPiece>& ar s << tag; s << " "; for (auto& arg : args) { - s << arg.to_string(); + s << arg; s << " "; } tracebuffer::Add(s.str(), tracebuffer::kBegin); @@ -124,7 +124,7 @@ FlushTrace::FlushTrace(const std::string& basepath, const std::string& tag, 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 5d5b7cd7d472..93c1b61f9a57 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -139,7 +139,7 @@ bool mkdirs(const std::string& path) { 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 @@ StringPiece GetStem(const StringPiece& path) { 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 @@ StringPiece GetFilename(const StringPiece& path) { 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 @@ StringPiece GetExtension(const StringPiece& path) { return {}; } -bool IsHidden(const android::StringPiece& path) { +bool IsHidden(android::StringPiece path) { return util::StartsWith(GetFilename(path), "."); } @@ -193,16 +193,16 @@ std::string BuildPath(std::vector<const StringPiece>&& args) { 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 @@ std::optional<FileMap> MmapPath(const std::string& path, std::string* out_error) 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 @@ bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_a 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 @@ bool AppendSetArgsFromFile(const StringPiece& path, std::unordered_set<std::stri 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 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { 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::optional<std::vector<std::string>> FindFiles(const android::StringPiece& pa } 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 @@ std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& pa // 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 ee95712f157d..42eeaf2d2e2a 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -66,31 +66,31 @@ std::string BuildPath(std::vector<const android::StringPiece>&& args); 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 @@ class FileFilter { // - 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 @@ class FileFilter { // 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 9b7ebdd690ac..be877660ef72 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -43,15 +43,14 @@ namespace util { // 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 @@ static std::vector<std::string> SplitAndTransform( 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 @@ StringPiece TrimLeadingWhitespace(const StringPiece& str) { 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 @@ StringPiece TrimTrailingWhitespace(const StringPiece& str) { 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 @@ StringPiece TrimWhitespace(const StringPiece& str) { 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 @@ static int IsJavaNameImpl(const StringPiece& str) { 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 @@ static int IsAndroidNameImpl(const StringPiece& str) { 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 @@ bool IsAndroidSharedUserId(const android::StringPiece& package_name, 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 @@ static size_t ConsumeDigits(const char* start, const char* end) { 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 @@ bool VerifyJavaStringFormat(const StringPiece& str) { 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 @@ typename Tokenizer::iterator& Tokenizer::iterator::operator++() { 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 @@ typename Tokenizer::iterator& Tokenizer::iterator::operator++() { 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 @@ bool Tokenizer::iterator::operator!=(const iterator& rhs) const { 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 8d3b41315485..40ff5b633d97 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -48,44 +48,44 @@ struct Range { 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 @@ bool IsAndroidSplitName(const android::StringPiece& str); // - 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 @@ bool IsAndroidSharedUserId(const android::StringPiece& package_name, // .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 @@ template <typename Container> // 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 @@ class Tokenizer { 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 @@ class Tokenizer { bool end_; }; - Tokenizer(const android::StringPiece& str, char sep); + Tokenizer(android::StringPiece str, char sep); iterator begin() const { return begin_; @@ -252,7 +251,7 @@ class Tokenizer { 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 @@ inline Tokenizer Tokenize(const android::StringPiece& str, char sep) { // 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 4ebcb115306f..15135690d0de 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -84,6 +84,14 @@ TEST(UtilTest, TokenizeAtEnd) { 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 9bdbd22b5697..3ccbaa2a4b6c 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -84,7 +84,7 @@ bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPi 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 f51e8a47041d..8dea8ea52f92 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -169,7 +169,7 @@ static void XMLCALL CharacterDataHandler(void* user_data, const char* s, int len 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 @@ void Element::InsertChild(size_t index, std::unique_ptr<Node> child) { 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 @@ const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece 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 @@ void Element::RemoveAttribute(const StringPiece& ns, const StringPiece& name) { 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 @@ void PackageAwareVisitor::AfterVisitElement(Element* el) { } 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 5bc55b6b68a1..c253b0a1f4a9 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -96,27 +96,22 @@ class Element : public Node { 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); - - Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name); - const Element* FindChild(const android::StringPiece& ns, const 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); - - 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; + 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(android::StringPiece ns, android::StringPiece name); + const Element* FindChild(android::StringPiece ns, android::StringPiece name) const; + + Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name, + android::StringPiece attr_ns, android::StringPiece attr_name, + android::StringPiece attr_value); + + 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 @@ class PackageAwareVisitor : public Visitor, public IPackageDeclStack { 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 bfa07490b9c0..d79446bfae6f 100644 --- a/tools/aapt2/xml/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -140,8 +140,7 @@ const std::string& XmlPullParser::namespace_uri() const { 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 @@ void XMLCALL XmlPullParser::EndCdataSectionHandler(void* user_data) { 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 @@ std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, const Stri 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 ab347728ae4b..fe4cd018d808 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -120,8 +120,7 @@ class XmlPullParser : public IPackageDeclStack { * 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 @@ class XmlPullParser : public IPackageDeclStack { * 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 @@ std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser, * 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 114b5ba7ab1a..709755e69292 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -27,7 +27,7 @@ using ::android::StringPiece; 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 @@ std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& n 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 @@ std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& n 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 1ab05a93d314..ad676ca91886 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -59,8 +59,7 @@ std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& n // // 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 @@ struct IPackageDeclStack { // 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 f29d9b2a6e81..c6f6d45215fe 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 @@ class ImmutabilityProcessor : AbstractProcessor() { "java.lang.Short", "java.lang.String", "java.lang.Void", + "java.util.UUID", "android.os.Parcelable.Creator", ) |