Merge "updateAppUidRecLocked() needs to run for all ProcessRecord of a UidRecord." into rvc-dev
diff --git a/apex/Android.bp b/apex/Android.bp
index 63ec7c3..de4b24a 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -71,6 +71,11 @@
// stubs libraries.
libs: ["framework-annotations-lib"],
+ // Framework modules are not generally shared libraries, i.e. they are not
+ // intended, and must not be allowed, to be used in a <uses-library> manifest
+ // entry.
+ shared_library: false,
+
// Enable api lint. This will eventually become the default for java_sdk_library
// but it cannot yet be turned on because some usages have not been cleaned up.
// TODO(b/156126315) - Remove when no longer needed.
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 3d0eeb8..8587e145 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -160,7 +160,7 @@
static const long kPullerCacheClearIntervalSec = 1;
// Max time to do a pull.
- static const int64_t kPullMaxDelayNs = 10 * NS_PER_SEC;
+ static const int64_t kPullMaxDelayNs = 30 * NS_PER_SEC;
// Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId.
static const int kMaxNonPlatformPushedAtoms = 100;
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 2e6043d..72decf2 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -278,7 +278,7 @@
optional int64 max_num_gauge_atoms_per_bucket = 11 [default = 10];
- optional int32 max_pull_delay_sec = 13 [default = 10];
+ optional int32 max_pull_delay_sec = 13 [default = 30];
optional bool split_bucket_for_app_upgrade = 14 [default = true];
}
@@ -328,7 +328,7 @@
optional bool skip_zero_diff_output = 14 [default = true];
- optional int32 max_pull_delay_sec = 16 [default = 10];
+ optional int32 max_pull_delay_sec = 16 [default = 30];
optional bool split_bucket_for_app_upgrade = 17 [default = true];
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d086459..0d8618f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -5968,6 +5968,206 @@
}
}
+ /**
+ * Merges the signing lineage of this instance with the lineage in the provided {@code
+ * otherSigningDetails} when one has the same or an ancestor signer of the other.
+ *
+ * <p>Merging two signing lineages will result in a new {@code SigningDetails} instance
+ * containing the longest common lineage with the most restrictive capabilities. If the two
+ * lineages contain the same signers with the same capabilities then the instance on which
+ * this was invoked is returned without any changes. Similarly if neither instance has a
+ * lineage, or if neither has the same or an ancestor signer then this instance is returned.
+ *
+ * Following are some example results of this method for lineages with signers A, B, C, D:
+ * - lineage B merged with lineage A -> B returns lineage A -> B.
+ * - lineage A -> B merged with lineage B -> C returns lineage A -> B -> C
+ * - lineage A -> B with the {@code PERMISSION} capability revoked for A merged with
+ * lineage A -> B with the {@code SHARED_USER_ID} capability revoked for A returns
+ * lineage A -> B with both capabilities revoked for A.
+ * - lineage A -> B -> C merged with lineage A -> B -> D would return the original lineage
+ * A -> B -> C since the current signer of both instances is not the same or in the
+ * lineage of the other.
+ */
+ public SigningDetails mergeLineageWith(SigningDetails otherSigningDetails) {
+ if (!hasPastSigningCertificates()) {
+ return otherSigningDetails.hasPastSigningCertificates()
+ && otherSigningDetails.hasAncestorOrSelf(this) ? otherSigningDetails : this;
+ }
+ if (!otherSigningDetails.hasPastSigningCertificates()) {
+ return this;
+ }
+ // Use the utility method to determine which SigningDetails instance is the descendant
+ // and to confirm that the signing lineage does not diverge.
+ SigningDetails descendantSigningDetails = getDescendantOrSelf(otherSigningDetails);
+ if (descendantSigningDetails == null) {
+ return this;
+ }
+ return descendantSigningDetails == this ? mergeLineageWithAncestorOrSelf(
+ otherSigningDetails) : otherSigningDetails.mergeLineageWithAncestorOrSelf(this);
+ }
+
+ /**
+ * Merges the signing lineage of this instance with the lineage of the ancestor (or same)
+ * signer in the provided {@code otherSigningDetails}.
+ */
+ private SigningDetails mergeLineageWithAncestorOrSelf(SigningDetails otherSigningDetails) {
+ // This method should only be called with instances that contain lineages.
+ int index = pastSigningCertificates.length - 1;
+ int otherIndex = otherSigningDetails.pastSigningCertificates.length - 1;
+ if (index < 0 || otherIndex < 0) {
+ return this;
+ }
+
+ List<Signature> mergedSignatures = new ArrayList<>();
+ boolean capabilitiesModified = false;
+ // If this is a descendant lineage then add all of the descendant signer(s) to the
+ // merged lineage until the ancestor signer is reached.
+ while (index >= 0 && !pastSigningCertificates[index].equals(
+ otherSigningDetails.pastSigningCertificates[otherIndex])) {
+ mergedSignatures.add(new Signature(pastSigningCertificates[index--]));
+ }
+ // If the signing lineage was exhausted then the provided ancestor is not actually an
+ // ancestor of this lineage.
+ if (index < 0) {
+ return this;
+ }
+
+ do {
+ // Add the common signer to the merged lineage with the most restrictive
+ // capabilities of the two lineages.
+ Signature signature = pastSigningCertificates[index--];
+ Signature ancestorSignature =
+ otherSigningDetails.pastSigningCertificates[otherIndex--];
+ Signature mergedSignature = new Signature(signature);
+ int mergedCapabilities = signature.getFlags() & ancestorSignature.getFlags();
+ if (signature.getFlags() != mergedCapabilities) {
+ capabilitiesModified = true;
+ mergedSignature.setFlags(mergedCapabilities);
+ }
+ mergedSignatures.add(mergedSignature);
+ } while (index >= 0 && otherIndex >= 0 && pastSigningCertificates[index].equals(
+ otherSigningDetails.pastSigningCertificates[otherIndex]));
+
+ // If both lineages still have elements then their lineages have diverged; since this is
+ // not supported return the invoking instance.
+ if (index >= 0 && otherIndex >= 0) {
+ return this;
+ }
+
+ // Add any remaining elements from either lineage that is not yet exhausted to the
+ // the merged lineage.
+ while (otherIndex >= 0) {
+ mergedSignatures.add(new Signature(
+ otherSigningDetails.pastSigningCertificates[otherIndex--]));
+ }
+ while (index >= 0) {
+ mergedSignatures.add(new Signature(pastSigningCertificates[index--]));
+ }
+
+ // if this lineage already contains all the elements in the ancestor and none of the
+ // capabilities were changed then just return this instance.
+ if (mergedSignatures.size() == pastSigningCertificates.length
+ && !capabilitiesModified) {
+ return this;
+ }
+ // Since the signatures were added to the merged lineage from newest to oldest reverse
+ // the list to ensure the oldest signer is at index 0.
+ Collections.reverse(mergedSignatures);
+ try {
+ return new SigningDetails(new Signature[]{new Signature(signatures[0])},
+ signatureSchemeVersion, mergedSignatures.toArray(new Signature[0]));
+ } catch (CertificateException e) {
+ Slog.e(TAG, "Caught an exception creating the merged lineage: ", e);
+ return this;
+ }
+ }
+
+ /**
+ * Returns whether this and the provided {@code otherSigningDetails} share a common
+ * ancestor.
+ *
+ * <p>The two SigningDetails have a common ancestor if any of the following conditions are
+ * met:
+ * - If neither has a lineage and their current signer(s) are equal.
+ * - If only one has a lineage and the signer of the other is the same or in the lineage.
+ * - If both have a lineage and their current signers are the same or one is in the lineage
+ * of the other, and their lineages do not diverge to different signers.
+ */
+ public boolean hasCommonAncestor(SigningDetails otherSigningDetails) {
+ if (!hasPastSigningCertificates()) {
+ // If this instance does not have a lineage then it must either be in the ancestry
+ // of or the same signer of the otherSigningDetails.
+ return otherSigningDetails.hasAncestorOrSelf(this);
+ }
+ if (!otherSigningDetails.hasPastSigningCertificates()) {
+ return hasAncestorOrSelf(otherSigningDetails);
+ }
+ // If both have a lineage then use getDescendantOrSelf to obtain the descendant signing
+ // details; a null return from that method indicates there is no common lineage between
+ // the two or that they diverge at a point in the lineage.
+ return getDescendantOrSelf(otherSigningDetails) != null;
+ }
+
+ /**
+ * Returns the SigningDetails with a descendant (or same) signer after verifying the
+ * descendant has the same, a superset, or a subset of the lineage of the ancestor.
+ *
+ * <p>If this instance and the provided {@code otherSigningDetails} do not share an
+ * ancestry, or if their lineages diverge then null is returned to indicate there is no
+ * valid descendant SigningDetails.
+ */
+ private SigningDetails getDescendantOrSelf(SigningDetails otherSigningDetails) {
+ SigningDetails descendantSigningDetails;
+ SigningDetails ancestorSigningDetails;
+ if (hasAncestorOrSelf(otherSigningDetails)) {
+ // If the otherSigningDetails has the same signer or a signer in the lineage of this
+ // instance then treat this instance as the descendant.
+ descendantSigningDetails = this;
+ ancestorSigningDetails = otherSigningDetails;
+ } else if (otherSigningDetails.hasAncestor(this)) {
+ // The above check confirmed that the two instances do not have the same signer and
+ // the signer of otherSigningDetails is not in this instance's lineage; if this
+ // signer is in the otherSigningDetails lineage then treat this as the ancestor.
+ descendantSigningDetails = otherSigningDetails;
+ ancestorSigningDetails = this;
+ } else {
+ // The signers are not the same and neither has the current signer of the other in
+ // its lineage; return null to indicate there is no descendant signer.
+ return null;
+ }
+ // Once the descent (or same) signer is identified iterate through the ancestry until
+ // the current signer of the ancestor is found.
+ int descendantIndex = descendantSigningDetails.pastSigningCertificates.length - 1;
+ int ancestorIndex = ancestorSigningDetails.pastSigningCertificates.length - 1;
+ while (descendantIndex >= 0
+ && !descendantSigningDetails.pastSigningCertificates[descendantIndex].equals(
+ ancestorSigningDetails.pastSigningCertificates[ancestorIndex])) {
+ descendantIndex--;
+ }
+ // Since the ancestry was verified above the descendant lineage should never be
+ // exhausted, but if for some reason the ancestor signer is not found then return null.
+ if (descendantIndex < 0) {
+ return null;
+ }
+ // Once the common ancestor (or same) signer is found iterate over the lineage of both
+ // to ensure that they are either the same or one is a subset of the other.
+ do {
+ descendantIndex--;
+ ancestorIndex--;
+ } while (descendantIndex >= 0 && ancestorIndex >= 0
+ && descendantSigningDetails.pastSigningCertificates[descendantIndex].equals(
+ ancestorSigningDetails.pastSigningCertificates[ancestorIndex]));
+
+ // If both lineages still have elements then they diverge and cannot be considered a
+ // valid common lineage.
+ if (descendantIndex >= 0 && ancestorIndex >= 0) {
+ return null;
+ }
+ // Since one or both of the lineages was exhausted they are either the same or one is a
+ // subset of the other; return the valid descendant.
+ return descendantSigningDetails;
+ }
+
/** Returns true if the signing details have one or more signatures. */
public boolean hasSignatures() {
return signatures != null && signatures.length > 0;
@@ -6284,7 +6484,13 @@
if (!Arrays.equals(pastSigningCertificates, that.pastSigningCertificates)) {
return false;
}
-
+ // The capabilities for the past signing certs must match as well.
+ for (int i = 0; i < pastSigningCertificates.length; i++) {
+ if (pastSigningCertificates[i].getFlags()
+ != that.pastSigningCertificates[i].getFlags()) {
+ return false;
+ }
+ }
return true;
}
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index 3b84ae7..b2875e9 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -123,6 +123,21 @@
}
/**
+ * Copy constructor that creates a new instance from the provided {@code other} Signature.
+ *
+ * @hide
+ */
+ public Signature(Signature other) {
+ mSignature = other.mSignature.clone();
+ Certificate[] otherCertificateChain = other.mCertificateChain;
+ if (otherCertificateChain != null && otherCertificateChain.length > 1) {
+ mCertificateChain = Arrays.copyOfRange(otherCertificateChain, 1,
+ otherCertificateChain.length);
+ }
+ mFlags = other.mFlags;
+ }
+
+ /**
* Sets the flags representing the capabilities of the past signing certificate.
* @hide
*/
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ebf4cc5..bf105ce 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2216,6 +2216,20 @@
}
};
+ // Uses IS_USER_UNLOCKED_PROPERTY for invalidation as the APIs have the same dependencies.
+ private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockingOrUnlockedCache =
+ new PropertyInvalidatedCache<Integer, Boolean>(
+ 32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
+ @Override
+ protected Boolean recompute(Integer query) {
+ try {
+ return mService.isUserUnlockingOrUnlocked(query);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ };
+
/** {@hide} */
@UnsupportedAppUsage
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
@@ -2227,6 +2241,7 @@
/** {@hide} */
public void disableIsUserUnlockedCache() {
mIsUserUnlockedCache.disableLocal();
+ mIsUserUnlockingOrUnlockedCache.disableLocal();
}
/** {@hide} */
@@ -2263,11 +2278,7 @@
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
- try {
- return mService.isUserUnlockingOrUnlocked(userId);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ return mIsUserUnlockingOrUnlockedCache.query(userId);
}
/**
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 389e33ae..8b0dd8a 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -101,6 +101,7 @@
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
@@ -2147,8 +2148,7 @@
extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
ChooserTarget chooserTarget = new ChooserTarget(
- shortcutInfo.getLongLabel() != null ? shortcutInfo.getLongLabel()
- : shortcutInfo.getShortLabel(),
+ shortcutInfo.getLabel(),
null, // Icon will be loaded later if this target is selected to be shown.
score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
@@ -2778,6 +2778,7 @@
@Override
public void onListRebuilt(ResolverListAdapter listAdapter) {
setupScrollListener();
+ maybeSetupGlobalLayoutListener();
ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
if (chooserListAdapter.getUserHandle()
@@ -2859,6 +2860,28 @@
});
}
+ private void maybeSetupGlobalLayoutListener() {
+ if (shouldShowTabs()) {
+ return;
+ }
+ final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
+ recyclerView.getViewTreeObserver()
+ .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ // Fixes an issue were the accessibility border disappears on list creation.
+ recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ final TextView titleView = findViewById(R.id.title);
+ if (titleView != null) {
+ titleView.setFocusable(true);
+ titleView.setFocusableInTouchMode(true);
+ titleView.requestFocus();
+ titleView.requestAccessibilityFocus();
+ }
+ }
+ });
+ }
+
@Override // ChooserListCommunicator
public boolean isSendAction(Intent targetIntent) {
if (targetIntent == null) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a205fcf..2dc3996 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4451,4 +4451,11 @@
<bool name="config_pdp_reject_enable_retry">false</bool>
<!-- pdp data reject retry delay in ms -->
<integer name="config_pdp_reject_retry_delay_ms">-1</integer>
+
+ <!-- Package name that is recognized as an actor for the packages listed in
+ @array/config_overlayableConfiguratorTargets. If an overlay targeting one of the listed
+ targets is signed with the same signature as the configurator, the overlay will be granted
+ the "actor" policy. -->
+ <string name="config_overlayableConfigurator" translatable="false" />
+ <string-array name="config_overlayableConfiguratorTargets" translatable="false" />
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 758a4f7..6ce25d4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4025,4 +4025,6 @@
<java-symbol type="string" name="config_pdp_reject_service_not_subscribed" />
<java-symbol type="string" name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" />
+ <java-symbol type="string" name="config_overlayableConfigurator" />
+ <java-symbol type="array" name="config_overlayableConfiguratorTargets" />
</resources>
diff --git a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
index 51af048..24f45a5 100644
--- a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
+++ b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
@@ -15,13 +15,19 @@
*/
package android.content.pm;
+import static android.content.pm.PackageParser.SigningDetails.CertCapabilities.AUTH;
+import static android.content.pm.PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA;
+import static android.content.pm.PackageParser.SigningDetails.CertCapabilities.PERMISSION;
+import static android.content.pm.PackageParser.SigningDetails.CertCapabilities.ROLLBACK;
+import static android.content.pm.PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID;
import static android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.content.pm.PackageParser.SigningDetails;
-import android.util.ArraySet;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -29,14 +35,70 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.security.PublicKey;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SigningDetailsTest {
- private static final String FIRST_SIGNATURE = "1234";
- private static final String SECOND_SIGNATURE = "5678";
- private static final String THIRD_SIGNATURE = "9abc";
+ private static final int DEFAULT_CAPABILITIES =
+ INSTALLED_DATA | SHARED_USER_ID | PERMISSION | AUTH;
+
+ // Some of the tests in this class require valid certificate encodings from which to pull the
+ // public key for the SigningDetails; the following are all DER encoded EC X.509 certificates.
+ private static final String FIRST_SIGNATURE =
+ "3082016c30820111a003020102020900ca0fb64dfb66e772300a06082a86"
+ + "48ce3d04030230123110300e06035504030c0765632d70323536301e170d"
+ + "3136303333313134353830365a170d3433303831373134353830365a3012"
+ + "3110300e06035504030c0765632d703235363059301306072a8648ce3d02"
+ + "0106082a8648ce3d03010703420004a65f113d22cb4913908307ac31ee2b"
+ + "a0e9138b785fac6536d14ea2ce90d2b4bfe194b50cdc8e169f54a73a991e"
+ + "f0fa76329825be078cc782740703da44b4d7eba350304e301d0603551d0e"
+ + "04160414d4133568b95b30158b322071ea8c43ff5b05ccc8301f0603551d"
+ + "23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc8300c06"
+ + "03551d13040530030101ff300a06082a8648ce3d04030203490030460221"
+ + "00f504a0866caef029f417142c5cb71354c79ffcd1d640618dfca4f19e16"
+ + "db78d6022100f8eea4829799c06cad08c6d3d2d2ec05e0574154e747ea0f"
+ + "dbb8042cb655aadd";
+ private static final String SECOND_SIGNATURE =
+ "3082016d30820113a0030201020209008855bd1dd2b2b225300a06082a86"
+ + "48ce3d04030230123110300e06035504030c0765632d70323536301e170d"
+ + "3138303731333137343135315a170d3238303731303137343135315a3014"
+ + "3112301006035504030c0965632d703235365f323059301306072a8648ce"
+ + "3d020106082a8648ce3d030107034200041d4cca0472ad97ee3cecef0da9"
+ + "3d62b450c6788333b36e7553cde9f74ab5df00bbba6ba950e68461d70bbc"
+ + "271b62151dad2de2bf6203cd2076801c7a9d4422e1a350304e301d060355"
+ + "1d0e041604147991d92b0208fc448bf506d4efc9fff428cb5e5f301f0603"
+ + "551d23041830168014d4133568b95b30158b322071ea8c43ff5b05ccc830"
+ + "0c0603551d13040530030101ff300a06082a8648ce3d0403020348003045"
+ + "02202769abb1b49fc2f53479c4ae92a6631dabfd522c9acb0bba2b43ebeb"
+ + "99c63011022100d260fb1d1f176cf9b7fa60098bfd24319f4905a3e5fda1"
+ + "00a6fe1a2ab19ff09e";
+ private static final String THIRD_SIGNATURE =
+ "3082016e30820115a0030201020209008394f5cad16a89a7300a06082a86"
+ + "48ce3d04030230143112301006035504030c0965632d703235365f32301e"
+ + "170d3138303731343030303532365a170d3238303731313030303532365a"
+ + "30143112301006035504030c0965632d703235365f333059301306072a86"
+ + "48ce3d020106082a8648ce3d03010703420004f31e62430e9db6fc5928d9"
+ + "75fc4e47419bacfcb2e07c89299e6cd7e344dd21adfd308d58cb49a1a2a3"
+ + "fecacceea4862069f30be1643bcc255040d8089dfb3743a350304e301d06"
+ + "03551d0e041604146f8d0828b13efaf577fc86b0e99fa3e54bcbcff0301f"
+ + "0603551d230418301680147991d92b0208fc448bf506d4efc9fff428cb5e"
+ + "5f300c0603551d13040530030101ff300a06082a8648ce3d040302034700"
+ + "30440220256bdaa2784c273e4cc291a595a46779dee9de9044dc9f7ab820"
+ + "309567df9fe902201a4ad8c69891b5a8c47434fe9540ed1f4979b5fad348"
+ + "3f3fa04d5677355a579e";
+ private static final String FOURTH_SIGNATURE =
+ "3082017b30820120a00302010202146c8cb8a818433c1e6431fb16fb3ae0"
+ + "fb5ad60aa7300a06082a8648ce3d04030230143112301006035504030c09"
+ + "65632d703235365f33301e170d3230303531333139313532385a170d3330"
+ + "303531313139313532385a30143112301006035504030c0965632d703235"
+ + "365f343059301306072a8648ce3d020106082a8648ce3d03010703420004"
+ + "db4a60031e79ad49cb759007d6855d4469b91c8bab065434f2fba971ade7"
+ + "e4d19599a0f67b5e708cfda7543e5630c3769d37e093640d7c768a15144c"
+ + "d0e5dcf4a350304e301d0603551d0e041604146e78970332554336b6ee89"
+ + "24eaa70230e393f678301f0603551d230418301680146f8d0828b13efaf5"
+ + "77fc86b0e99fa3e54bcbcff0300c0603551d13040530030101ff300a0608"
+ + "2a8648ce3d0403020349003046022100ce786e79ec7547446082e9caf910"
+ + "614ff80758f9819fb0f148695067abe0fcd4022100a4881e332ddec2116a"
+ + "d2b59cf891d0f331ff7e27e77b7c6206c7988d9b539330";
@Test
public void hasAncestor_multipleSignersInLineageWithAncestor_returnsTrue() throws Exception {
@@ -121,27 +183,456 @@
assertFalse(result2);
}
- private SigningDetails createSigningDetailsWithLineage(String... signers) {
+ @Test
+ public void mergeLineageWith_neitherHasLineage_returnsOriginal() throws Exception {
+ // When attempting to merge two instances of SigningDetails that do not have a lineage the
+ // initial object should be returned to indicate no changes were made.
+ SigningDetails noLineageDetails = createSigningDetails(FIRST_SIGNATURE);
+ SigningDetails otherNoLineageDetails = createSigningDetails(FIRST_SIGNATURE);
+
+ SigningDetails result1 = noLineageDetails.mergeLineageWith(otherNoLineageDetails);
+ SigningDetails result2 = otherNoLineageDetails.mergeLineageWith(noLineageDetails);
+
+ assertTrue(result1 == noLineageDetails);
+ assertTrue(result2 == otherNoLineageDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_oneHasNoLineage_returnsOther() throws Exception {
+ // When attempting to merge a SigningDetails with no lineage with another that has a
+ // lineage and is a descendant the descendant SigningDetails with lineage should be returned
+ SigningDetails noLineageDetails = createSigningDetails(FIRST_SIGNATURE);
+ SigningDetails lineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+
+ SigningDetails result1 = noLineageDetails.mergeLineageWith(lineageDetails);
+ SigningDetails result2 = lineageDetails.mergeLineageWith(noLineageDetails);
+
+ assertTrue(result1 == lineageDetails);
+ assertTrue(result2 == lineageDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_bothHaveSameLineage_returnsOriginal() throws Exception {
+ // If twoSigningDetails instances have the exact same lineage with the same capabilities
+ // then the original instance should be returned without modification.
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+
+ SigningDetails result1 = firstLineageDetails.mergeLineageWith(secondLineageDetails);
+ SigningDetails result2 = secondLineageDetails.mergeLineageWith(firstLineageDetails);
+
+ assertTrue(result1 == firstLineageDetails);
+ assertTrue(result2 == secondLineageDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_oneIsAncestorWithoutLineage_returnsDescendant() throws Exception {
+ // If one instance without a lineage is an ancestor of the other then the descendant should
+ // be returned.
+ SigningDetails ancestorDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE);
+ SigningDetails descendantDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+
+ SigningDetails result1 = ancestorDetails.mergeLineageWith(descendantDetails);
+ SigningDetails result2 = descendantDetails.mergeLineageWith(ancestorDetails);
+
+ assertEquals(descendantDetails, result1);
+ assertTrue(result2 == descendantDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_oneIsAncestorWithLineage_returnsDescendant() throws Exception {
+ // Similar to the above test if one instance with a lineage is an ancestor of the other then
+ // the descendant should be returned.
+ SigningDetails ancestorDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+ SigningDetails descendantDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ SigningDetails result1 = ancestorDetails.mergeLineageWith(descendantDetails);
+ SigningDetails result2 = descendantDetails.mergeLineageWith(ancestorDetails);
+
+ assertEquals(descendantDetails, result1);
+ assertTrue(result2 == descendantDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_singleSignerInMiddleOfLineage_returnsFullLineage()
+ throws Exception {
+ // If one instance without a lineage is an ancestor in the middle of the lineage for the
+ // descendant the descendant should be returned.
+ SigningDetails singleSignerDetails = createSigningDetails(SECOND_SIGNATURE);
+ SigningDetails fullLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ SigningDetails result1 = singleSignerDetails.mergeLineageWith(fullLineageDetails);
+ SigningDetails result2 = fullLineageDetails.mergeLineageWith(singleSignerDetails);
+
+ assertTrue(result1 == fullLineageDetails);
+ assertTrue(result2 == fullLineageDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_noCommonLineage_returnsOriginal() throws Exception {
+ // While a call should never be made to merge two lineages without a common ancestor if it
+ // is attempted the original lineage should be returned.
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage(THIRD_SIGNATURE,
+ FOURTH_SIGNATURE);
+
+ SigningDetails result1 = firstLineageDetails.mergeLineageWith(secondLineageDetails);
+ SigningDetails result2 = secondLineageDetails.mergeLineageWith(firstLineageDetails);
+
+ assertTrue(result1 == firstLineageDetails);
+ assertTrue(result2 == secondLineageDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_bothPartialLineages_returnsFullLineage() throws Exception {
+ // This test verifies the following scenario:
+ // - One package is signed with a rotated key B and linage A -> B
+ // - The other package is signed with a rotated key C and lineage B -> C
+ // Merging the lineage of these two should return the full lineage A -> B -> C
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage(SECOND_SIGNATURE,
+ THIRD_SIGNATURE);
+ SigningDetails expectedDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ SigningDetails result1 = firstLineageDetails.mergeLineageWith(secondLineageDetails);
+ SigningDetails result2 = secondLineageDetails.mergeLineageWith(firstLineageDetails);
+
+ assertEquals(expectedDetails, result1);
+ assertEquals(expectedDetails, result2);
+ }
+
+ @Test
+ public void mergeLineageWith_oneSubsetLineage_returnsFullLineage() throws Exception {
+ // This test verifies when one lineage is a subset of the other the full lineage is
+ // returned.
+ SigningDetails subsetLineageDetails = createSigningDetailsWithLineage(SECOND_SIGNATURE,
+ THIRD_SIGNATURE);
+ SigningDetails fullLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, THIRD_SIGNATURE, FOURTH_SIGNATURE);
+
+ SigningDetails result1 = subsetLineageDetails.mergeLineageWith(fullLineageDetails);
+ SigningDetails result2 = fullLineageDetails.mergeLineageWith(subsetLineageDetails);
+
+ assertEquals(fullLineageDetails, result1);
+ assertTrue(result2 == fullLineageDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_differentRootsOfTrust_returnsOriginal() throws Exception {
+ // If two SigningDetails share a common lineage but diverge at one of the ancestors then the
+ // merge should return the invoking instance since this is not supported.
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineage("1234",
+ FIRST_SIGNATURE, SECOND_SIGNATURE);
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage("5678",
+ FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ SigningDetails result1 = firstLineageDetails.mergeLineageWith(secondLineageDetails);
+ SigningDetails result2 = secondLineageDetails.mergeLineageWith(firstLineageDetails);
+
+ assertTrue(result1 == firstLineageDetails);
+ assertTrue(result2 == secondLineageDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_divergedSignerInLineage_returnsOriginal() throws Exception {
+ // Similar to the test above if two lineages diverge at any point then the merge should
+ // return the original since the signers in a sharedUserId must always be either the same,
+ // a subset, or a superset of the existing lineage.
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ "1234", SECOND_SIGNATURE, THIRD_SIGNATURE);
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ "5678", SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ SigningDetails result1 = firstLineageDetails.mergeLineageWith(secondLineageDetails);
+ SigningDetails result2 = secondLineageDetails.mergeLineageWith(firstLineageDetails);
+
+ assertTrue(result1 == firstLineageDetails);
+ assertTrue(result2 == secondLineageDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_sameLineageDifferentCaps_returnsLineageWithModifiedCaps()
+ throws Exception {
+ // This test verifies when two lineages consist of the same signers but have different
+ // capabilities the more restrictive capabilities are returned.
+ SigningDetails defaultCapabilitiesDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, THIRD_SIGNATURE);
+ SigningDetails modifiedCapabilitiesDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+ new int[]{INSTALLED_DATA, INSTALLED_DATA, INSTALLED_DATA});
+
+ SigningDetails result1 = defaultCapabilitiesDetails.mergeLineageWith(
+ modifiedCapabilitiesDetails);
+ SigningDetails result2 = modifiedCapabilitiesDetails.mergeLineageWith(
+ defaultCapabilitiesDetails);
+
+ assertEquals(modifiedCapabilitiesDetails, result1);
+ assertTrue(result2 == modifiedCapabilitiesDetails);
+ }
+
+ @Test
+ public void mergeLineageWith_overlappingLineageDiffCaps_returnsFullLineageWithModifiedCaps()
+ throws Exception {
+ // This test verifies the following scenario:
+ // - First lineage has signers A -> B with modified capabilities for A and B
+ // - Second lineage has signers B -> C with modified capabilities for B and C
+ // The merged lineage should be A -> B -> C with the most restrictive capabilities for B
+ // since it is in both lineages.
+ int[] firstCapabilities =
+ new int[]{INSTALLED_DATA | AUTH, INSTALLED_DATA | SHARED_USER_ID | PERMISSION};
+ int[] secondCapabilities = new int[]{INSTALLED_DATA | SHARED_USER_ID | AUTH,
+ INSTALLED_DATA | SHARED_USER_ID | AUTH};
+ int[] expectedCapabilities =
+ new int[]{firstCapabilities[0], firstCapabilities[1] & secondCapabilities[0],
+ secondCapabilities[1]};
+ SigningDetails firstDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE}, firstCapabilities);
+ SigningDetails secondDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{SECOND_SIGNATURE, THIRD_SIGNATURE}, secondCapabilities);
+ SigningDetails expectedDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+ expectedCapabilities);
+
+ SigningDetails result1 = firstDetails.mergeLineageWith(secondDetails);
+ SigningDetails result2 = secondDetails.mergeLineageWith(firstDetails);
+
+ assertEquals(expectedDetails, result1);
+ assertEquals(expectedDetails, result2);
+ }
+
+ @Test
+ public void mergeLineageWith_subLineageModifiedCaps_returnsFullLineageWithModifiedCaps()
+ throws Exception {
+ // This test verifies the following scenario:
+ // - First lineage has signers B -> C with modified capabilities
+ // - Second lineage has signers A -> B -> C -> D with modified capabilities
+ // The merged lineage should be A -> B -> C -> D with the most restrictive capabilities for
+ // B and C since they are in both lineages.
+ int[] subCapabilities = new int[]{INSTALLED_DATA | SHARED_USER_ID | PERMISSION,
+ DEFAULT_CAPABILITIES | ROLLBACK};
+ int[] fullCapabilities =
+ new int[]{0, SHARED_USER_ID, DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES};
+ int[] expectedCapabilities =
+ new int[]{fullCapabilities[0], subCapabilities[0] & fullCapabilities[1],
+ subCapabilities[1] & fullCapabilities[2], fullCapabilities[3]};
+ SigningDetails subLineageDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{SECOND_SIGNATURE, THIRD_SIGNATURE}, subCapabilities);
+ SigningDetails fullLineageDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE, FOURTH_SIGNATURE},
+ fullCapabilities);
+ SigningDetails expectedDetails = createSigningDetailsWithLineageAndCapabilities(
+ new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE, FOURTH_SIGNATURE},
+ expectedCapabilities);
+
+ SigningDetails result1 = subLineageDetails.mergeLineageWith(fullLineageDetails);
+ SigningDetails result2 = fullLineageDetails.mergeLineageWith(subLineageDetails);
+
+ assertEquals(expectedDetails, result1);
+ assertEquals(expectedDetails, result2);
+ }
+
+ @Test
+ public void mergeLineageWith_commonLineageDivergedSigners_returnsOriginal() throws Exception {
+ // When mergeWithLineage is invoked with SigningDetails instances that have a common lineage
+ // but diverged signers the calling instance should be returned since the current signer
+ // is not in the ancestry of the other's lineage.
+ SigningDetails firstLineageDetails = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE,
+ THIRD_SIGNATURE);
+ SigningDetails secondLineageDetails = createSigningDetails(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, FOURTH_SIGNATURE);
+
+ SigningDetails result1 = firstLineageDetails.mergeLineageWith(secondLineageDetails);
+ SigningDetails result2 = secondLineageDetails.mergeLineageWith(firstLineageDetails);
+
+ assertTrue(result1 == firstLineageDetails);
+ assertTrue(result2 == secondLineageDetails);
+ }
+
+ @Test
+ public void hasCommonAncestor_noLineageSameSingleSigner_returnsTrue() throws Exception {
+ // If neither SigningDetails have a lineage but they have the same single signer then
+ // hasCommonAncestor should return true.
+ SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE);
+ SigningDetails secondDetails = createSigningDetails(FIRST_SIGNATURE);
+
+ assertTrue(firstDetails.hasCommonAncestor(secondDetails));
+ assertTrue(secondDetails.hasCommonAncestor(firstDetails));
+ }
+
+ @Test
+ public void hasCommonAncestor_noLineageSameMultipleSigners_returnsTrue() throws Exception {
+ // Similar to above if neither SigningDetails have a lineage but they have the same multiple
+ // signers then hasCommonAncestor should return true.
+ SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE, FIRST_SIGNATURE);
+
+ assertTrue(firstDetails.hasCommonAncestor(secondDetails));
+ assertTrue(secondDetails.hasCommonAncestor(firstDetails));
+ }
+
+ @Test
+ public void hasCommonAncestor_noLineageDifferentSigners_returnsFalse() throws Exception {
+ // If neither SigningDetails have a lineage and they have different signers then
+ // hasCommonAncestor should return false.
+ SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE);
+ SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE);
+ SigningDetails thirdDetails = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+ SigningDetails fourthDetails = createSigningDetails(SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ assertFalse(firstDetails.hasCommonAncestor(secondDetails));
+ assertFalse(firstDetails.hasCommonAncestor(thirdDetails));
+ assertFalse(firstDetails.hasCommonAncestor(fourthDetails));
+ assertFalse(secondDetails.hasCommonAncestor(firstDetails));
+ assertFalse(secondDetails.hasCommonAncestor(thirdDetails));
+ assertFalse(secondDetails.hasCommonAncestor(fourthDetails));
+ assertFalse(thirdDetails.hasCommonAncestor(firstDetails));
+ assertFalse(thirdDetails.hasCommonAncestor(secondDetails));
+ assertFalse(thirdDetails.hasCommonAncestor(fourthDetails));
+ assertFalse(fourthDetails.hasCommonAncestor(firstDetails));
+ assertFalse(fourthDetails.hasCommonAncestor(secondDetails));
+ assertFalse(fourthDetails.hasCommonAncestor(thirdDetails));
+ }
+
+ @Test
+ public void hasCommonAncestor_oneWithOthersSignerInLineage_returnsTrue() throws Exception {
+ // If only one of the SigningDetails has a lineage and the current signer of the other is in
+ // the lineage then hasCommonAncestor should return true.
+ SigningDetails noLineageDetails = createSigningDetails(FIRST_SIGNATURE);
+ SigningDetails lineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+
+ assertTrue(noLineageDetails.hasCommonAncestor(lineageDetails));
+ assertTrue(lineageDetails.hasCommonAncestor(noLineageDetails));
+ }
+
+ @Test
+ public void hasCommonAncestor_oneWithSameSignerWithoutLineage_returnsTrue() throws Exception {
+ // If only one of the SigningDetails has a lineage and both have the same current signer
+ // then hasCommonAncestor should return true.
+ SigningDetails noLineageDetails = createSigningDetails(SECOND_SIGNATURE);
+ SigningDetails lineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+
+ assertTrue(noLineageDetails.hasCommonAncestor(lineageDetails));
+ assertTrue(lineageDetails.hasCommonAncestor(noLineageDetails));
+ }
+
+ @Test
+ public void hasCommonAncestor_bothHaveSameLineage_returnsTrue() throws Exception {
+ // If both SigningDetails have the exact same lineage then hasCommonAncestor should return
+ // true.
+ SigningDetails firstDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+ SigningDetails secondDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+
+ assertTrue(firstDetails.hasCommonAncestor(secondDetails));
+ assertTrue(secondDetails.hasCommonAncestor(firstDetails));
+ }
+
+ @Test
+ public void hasCommonAncestor_oneLineageIsAncestor_returnsTrue() throws Exception {
+ // If one SigningDetails has a lineage that is an ancestor of the other then
+ // hasCommonAncestor should return true.
+ SigningDetails ancestorDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+ SigningDetails descendantDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ assertTrue(ancestorDetails.hasCommonAncestor(descendantDetails));
+ assertTrue(descendantDetails.hasCommonAncestor(ancestorDetails));
+ }
+
+ @Test
+ public void hasCommonAncestor_oneLineageIsSubset_returnsTrue() throws Exception {
+ // If one SigningDetails has a lineage that is a subset of the other then hasCommonAncestor
+ // should return true.
+ SigningDetails subsetDetails = createSigningDetailsWithLineage(SECOND_SIGNATURE,
+ THIRD_SIGNATURE);
+ SigningDetails fullDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE, THIRD_SIGNATURE, FOURTH_SIGNATURE);
+
+ assertTrue(subsetDetails.hasCommonAncestor(fullDetails));
+ assertTrue(fullDetails.hasCommonAncestor(subsetDetails));
+ }
+
+ @Test
+ public void hasCommonAncestor_differentRootOfTrustInLineage_returnsFalse() throws Exception {
+ // if the two SigningDetails have a different root of trust then hasCommonAncestor should
+ // return false.
+ SigningDetails firstDetails = createSigningDetailsWithLineage(THIRD_SIGNATURE,
+ FIRST_SIGNATURE, SECOND_SIGNATURE);
+ SigningDetails secondDetails = createSigningDetailsWithLineage(FOURTH_SIGNATURE,
+ FIRST_SIGNATURE, SECOND_SIGNATURE);
+
+ assertFalse(firstDetails.hasCommonAncestor(secondDetails));
+ assertFalse(secondDetails.hasCommonAncestor(firstDetails));
+ }
+
+ @Test
+ public void hasCommonAncestor_differentSignerInMiddleOfLineage_returnsFalse() throws Exception {
+ // if the two SigningDetails have a different signer in the middle of a common lineage then
+ // hasCommonAncestor should return false.
+ SigningDetails firstDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE, "1234",
+ SECOND_SIGNATURE, THIRD_SIGNATURE);
+ SigningDetails secondDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE, "5678",
+ SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+ assertFalse(firstDetails.hasCommonAncestor(secondDetails));
+ assertFalse(secondDetails.hasCommonAncestor(firstDetails));
+ }
+
+ @Test
+ public void hasCommonAncestor_overlappingLineages_returnsTrue() throws Exception {
+ // if the two SigningDetails have overlapping lineages then hasCommonAncestor should return
+ // true.
+ SigningDetails firstLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+ SECOND_SIGNATURE);
+ SigningDetails secondLineageDetails = createSigningDetailsWithLineage(SECOND_SIGNATURE,
+ THIRD_SIGNATURE);
+
+ assertTrue(firstLineageDetails.hasCommonAncestor(secondLineageDetails));
+ assertTrue(secondLineageDetails.hasCommonAncestor(firstLineageDetails));
+ }
+
+ private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception {
+ int[] capabilities = new int[signers.length];
+ for (int i = 0; i < capabilities.length; i++) {
+ capabilities[i] = DEFAULT_CAPABILITIES;
+ }
+ return createSigningDetailsWithLineageAndCapabilities(signers, capabilities);
+ }
+
+ private SigningDetails createSigningDetailsWithLineageAndCapabilities(String[] signers,
+ int[] capabilities) throws Exception {
+ if (capabilities.length != signers.length) {
+ fail("The capabilities array must contain the same number of elements as the signers "
+ + "array");
+ }
Signature[] signingHistory = new Signature[signers.length];
for (int i = 0; i < signers.length; i++) {
signingHistory[i] = new Signature(signers[i]);
+ signingHistory[i].setFlags(capabilities[i]);
}
Signature[] currentSignature = new Signature[]{signingHistory[signers.length - 1]};
- // TODO: Since the PublicKey ArraySet is not used by any of the tests a generic empty Set
- // works for now, but if this is needed in the future consider creating mock PublicKeys that
- // can respond as required for the method under test.
- ArraySet<PublicKey> publicKeys = new ArraySet<>();
- return new SigningDetails(currentSignature, SIGNING_BLOCK_V3, publicKeys, signingHistory);
+ return new SigningDetails(currentSignature, SIGNING_BLOCK_V3, signingHistory);
}
- private SigningDetails createSigningDetails(String... signers) {
+ private SigningDetails createSigningDetails(String... signers) throws Exception {
Signature[] currentSignatures = new Signature[signers.length];
for (int i = 0; i < signers.length; i++) {
currentSignatures[i] = new Signature(signers[i]);
}
- // TODO: Similar to above when tests are added that require this it should be updated to use
- // mocked PublicKeys.
- ArraySet<PublicKey> publicKeys = new ArraySet<>();
- return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, publicKeys, null);
+ return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
}
}
diff --git a/packages/CarSystemUI/AndroidManifest.xml b/packages/CarSystemUI/AndroidManifest.xml
index 1dd0291..c0482e1 100644
--- a/packages/CarSystemUI/AndroidManifest.xml
+++ b/packages/CarSystemUI/AndroidManifest.xml
@@ -25,6 +25,11 @@
<uses-permission android:name="android.car.permission.CAR_ENROLL_TRUST"/>
<!-- This permission is required to get bluetooth broadcast. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <!-- These permissions are required to implement icons based on role holders. -->
+ <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/>
+ <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS"/>
+ <!-- This permission is required to access app information from other users. -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<!-- This permission is required to check the foreground user id. -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
</manifest>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 1418bf8..2a715d0 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -125,6 +125,7 @@
android:id="@+id/assist"
style="@style/NavigationBarButton"
systemui:icon="@drawable/ic_mic_white"
+ systemui:useDefaultAppIconForRole="true"
/>
</LinearLayout>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_button.xml b/packages/CarSystemUI/res/layout/car_navigation_button.xml
index 837252b..ca4e76e 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_button.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_button.xml
@@ -29,12 +29,14 @@
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/car_nav_button_icon_image"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/car_navigation_button_icon_height"
android:layout_width="match_parent"
android:layout_gravity="center"
android:animateLayoutChanges="true"
android:background="@android:color/transparent"
android:scaleType="fitCenter"
+ android:tintMode="src_in"
+ android:tint="@color/car_nav_icon_fill_color"
android:clickable="false"
/>
@@ -48,6 +50,7 @@
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:clickable="false"
+ android:visibility="gone"
/>
<ImageView
diff --git a/packages/CarSystemUI/res/values/attrs.xml b/packages/CarSystemUI/res/values/attrs.xml
index a586763..7883764 100644
--- a/packages/CarSystemUI/res/values/attrs.xml
+++ b/packages/CarSystemUI/res/values/attrs.xml
@@ -65,6 +65,10 @@
<attr name="showMoreWhenSelected" format="boolean" />
<!-- whether to highlight the button when selected. Defaults false -->
<attr name="highlightWhenSelected" format="boolean" />
+ <!-- whether to show the icon of the app currently associated this button's role. Only
+ relevant for buttons associated to specific roles (e.g.: AssistantButton).
+ Defaults false -->
+ <attr name="useDefaultAppIconForRole" format="boolean"/>
</declare-styleable>
<!-- Custom attributes to configure hvac values -->
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index 0e84d51..d20ab49 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -21,7 +21,7 @@
<color name="car_user_switcher_name_text_color">@*android:color/car_body1_light</color>
<color name="car_user_switcher_add_user_background_color">#131313</color>
<color name="car_user_switcher_add_user_add_sign_color">@*android:color/car_body1_light</color>
- <color name="car_nav_icon_fill_color">#8Fffffff</color>
+ <color name="car_nav_icon_fill_color">#8F8F8F</color>
<color name="car_nav_icon_fill_color_selected">#ffffff</color>
<!-- colors for seekbar -->
<color name="car_seekbar_track_background">#131315</color>
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index ed0b485..cb321cd 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -175,6 +175,7 @@
<dimen name="car_user_switcher_margin_top">@*android:dimen/car_padding_4</dimen>
<dimen name="car_navigation_button_width">64dp</dimen>
+ <dimen name="car_navigation_button_icon_height">44dp</dimen>
<dimen name="car_navigation_bar_width">760dp</dimen>
<dimen name="car_left_navigation_bar_width">96dp</dimen>
<dimen name="car_right_navigation_bar_width">96dp</dimen>
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/AssitantButton.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/AssitantButton.java
index 69ec78e..ede4696 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/AssitantButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/AssitantButton.java
@@ -18,6 +18,7 @@
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE;
+import android.app.role.RoleManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -31,7 +32,6 @@
* AssitantButton is a ui component that will trigger the Voice Interaction Service.
*/
public class AssitantButton extends CarNavigationButton {
-
private static final String TAG = "AssistantButton";
private final AssistUtils mAssistUtils;
private IVoiceInteractionSessionShowCallback mShowCallback =
@@ -50,9 +50,7 @@
public AssitantButton(Context context, AttributeSet attrs) {
super(context, attrs);
mAssistUtils = new AssistUtils(context);
- setOnClickListener(v -> {
- showAssistant();
- });
+ setOnClickListener(v -> showAssistant());
}
private void showAssistant() {
@@ -65,4 +63,9 @@
protected void setUpIntents(TypedArray typedArray) {
// left blank because for the assistant button Intent will not be passed from the layout.
}
+
+ @Override
+ protected String getRoleName() {
+ return RoleManager.ROLE_ASSISTANT;
+ }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java
new file mode 100644
index 0000000..5c83c02
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.navigationbar;
+
+import android.annotation.Nullable;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.car.CarDeviceProvisionedController;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Some CarNavigationButtons can be associated to a {@link RoleManager} role. When they are, it is
+ * possible to have them display the icon of the default application (role holder) for the given
+ * role.
+ *
+ * This class monitors the current role holders for each role type and updates the button icon for
+ * this buttons with have this feature enabled.
+ */
+@Singleton
+public class ButtonRoleHolderController {
+ private static final String TAG = "ButtonRoleHolderController";
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final RoleManager mRoleManager;
+ private final CarDeviceProvisionedController mDeviceController;
+ private final Map<String, CarNavigationButton> mButtonMap = new HashMap<>();
+ private final OnRoleHoldersChangedListener mListener = this::onRoleChanged;
+ private boolean mRegistered;
+
+ @Inject
+ public ButtonRoleHolderController(Context context, PackageManager packageManager,
+ RoleManager roleManager, CarDeviceProvisionedController deviceController) {
+ mContext = context;
+ mPackageManager = packageManager;
+ mRoleManager = roleManager;
+ mDeviceController = deviceController;
+ }
+
+ /**
+ * Iterate through a view looking for CarNavigationButton and add it to this controller if it
+ * opted to be associated with a {@link RoleManager} role type.
+ *
+ * @param v the View that may contain CarFacetButtons
+ */
+ void addAllButtonsWithRoleName(View v) {
+ if (v instanceof CarNavigationButton) {
+ CarNavigationButton button = (CarNavigationButton) v;
+ String roleName = button.getRoleName();
+ if (roleName != null && button.isDefaultAppIconForRoleEnabled()) {
+ addButtonWithRoleName(button, roleName);
+ }
+ } else if (v instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) v;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ addAllButtonsWithRoleName(viewGroup.getChildAt(i));
+ }
+ }
+ }
+
+ private void addButtonWithRoleName(CarNavigationButton button, String roleName) {
+ mButtonMap.put(roleName, button);
+ updateIcon(roleName);
+ if (!mRegistered) {
+ mRoleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
+ mListener, UserHandle.ALL);
+ mRegistered = true;
+ }
+ }
+
+ void removeAll() {
+ mButtonMap.clear();
+ if (mRegistered) {
+ mRoleManager.removeOnRoleHoldersChangedListenerAsUser(mListener, UserHandle.ALL);
+ mRegistered = false;
+ }
+ }
+
+ @VisibleForTesting
+ void onRoleChanged(String roleName, UserHandle user) {
+ if (RoleManager.ROLE_ASSISTANT.equals(roleName)
+ && user.getIdentifier() == mDeviceController.getCurrentUser()) {
+ updateIcon(roleName);
+ }
+ }
+
+ private void updateIcon(String roleName) {
+ CarNavigationButton button = mButtonMap.get(roleName);
+ if (button == null) {
+ return;
+ }
+ List<String> holders = mRoleManager.getRoleHoldersAsUser(button.getRoleName(),
+ UserHandle.of(mDeviceController.getCurrentUser()));
+ if (holders == null || holders.isEmpty()) {
+ button.setAppIcon(null);
+ } else {
+ button.setAppIcon(loadIcon(holders.get(0)));
+ }
+ }
+
+ @Nullable
+ private Drawable loadIcon(String packageName) {
+ try {
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName,
+ PackageManager.MATCH_ANY_USER);
+ return appInfo.loadIcon(mPackageManager);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(ButtonRoleHolderController.TAG, "Package not found: " + packageName, e);
+ return null;
+ }
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
index 5c6472e..4c720ab 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
@@ -87,7 +87,6 @@
private final Executor mUiBgExecutor;
private final IStatusBarService mBarService;
private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
- private final ButtonSelectionStateController mButtonSelectionStateController;
private final Lazy<PhoneStatusBarPolicy> mIconPolicyLazy;
private final Lazy<StatusBarIconController> mIconControllerLazy;
@@ -139,7 +138,6 @@
@UiBackground Executor uiBgExecutor,
IStatusBarService barService,
Lazy<KeyguardStateController> keyguardStateControllerLazy,
- ButtonSelectionStateController buttonSelectionStateController,
Lazy<PhoneStatusBarPolicy> iconPolicyLazy,
Lazy<StatusBarIconController> iconControllerLazy
) {
@@ -156,7 +154,6 @@
mUiBgExecutor = uiBgExecutor;
mBarService = barService;
mKeyguardStateControllerLazy = keyguardStateControllerLazy;
- mButtonSelectionStateController = buttonSelectionStateController;
mIconPolicyLazy = iconPolicyLazy;
mIconControllerLazy = iconControllerLazy;
@@ -280,10 +277,9 @@
* before and after the device is provisioned. . Also for change of density and font size.
*/
private void restartNavBars() {
- // remove and reattach all hvac components such that we don't keep a reference to unused
- // ui elements
- mCarNavigationBarController.removeAllFromHvac();
- mButtonSelectionStateController.removeAll();
+ // remove and reattach all components such that we don't keep a reference to unused ui
+ // elements
+ mCarNavigationBarController.removeAll();
if (mTopNavigationBarWindow != null) {
mTopNavigationBarWindow.removeAllViews();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
index 288e5cf..ca780ae 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
@@ -37,6 +37,7 @@
private final Context mContext;
private final NavigationBarViewFactory mNavigationBarViewFactory;
private final ButtonSelectionStateController mButtonSelectionStateController;
+ private final ButtonRoleHolderController mButtonRoleHolderController;
private final Lazy<HvacController> mHvacControllerLazy;
private boolean mShowTop;
@@ -59,11 +60,13 @@
public CarNavigationBarController(Context context,
NavigationBarViewFactory navigationBarViewFactory,
ButtonSelectionStateController buttonSelectionStateController,
- Lazy<HvacController> hvacControllerLazy) {
+ Lazy<HvacController> hvacControllerLazy,
+ ButtonRoleHolderController buttonRoleHolderController) {
mContext = context;
mNavigationBarViewFactory = navigationBarViewFactory;
mButtonSelectionStateController = buttonSelectionStateController;
mHvacControllerLazy = hvacControllerLazy;
+ mButtonRoleHolderController = buttonRoleHolderController;
// Read configuration.
mShowTop = mContext.getResources().getBoolean(R.bool.config_enableTopNavigationBar);
@@ -101,9 +104,11 @@
mHvacControllerLazy.get().connectToCarService();
}
- /** Clean up hvac. */
- public void removeAllFromHvac() {
+ /** Clean up */
+ public void removeAll() {
mHvacControllerLazy.get().removeAllComponents();
+ mButtonSelectionStateController.removeAll();
+ mButtonRoleHolderController.removeAll();
}
/** Gets the top window if configured to do so. */
@@ -211,6 +216,7 @@
view.setStatusBarWindowTouchListener(statusBarTouchListener);
view.setNotificationsPanelController(notifShadeController);
mButtonSelectionStateController.addAllButtonsWithSelectionState(view);
+ mButtonRoleHolderController.addAllButtonsWithRoleName(view);
mHvacControllerLazy.get().addTemperatureViewToController(view);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
index 5f4ac2d..5e113d6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
@@ -17,9 +17,11 @@
package com.android.systemui.car.navigationbar;
import android.app.ActivityOptions;
+import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
import android.util.AttributeSet;
@@ -29,6 +31,7 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
@@ -62,6 +65,8 @@
private float mUnselectedAlpha;
private int mSelectedIconResourceId;
private int mIconResourceId;
+ private Drawable mAppIcon;
+ private boolean mIsDefaultAppIconForRoleEnabled;
private String[] mComponentNames;
/** App categories that are to be used with this widget */
private String[] mButtonCategories;
@@ -92,7 +97,9 @@
super.setSelected(selected);
mSelected = selected;
if (mHighlightWhenSelected) {
- setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
+ // Always apply selected alpha if the button does not toggle alpha based on selection
+ // state.
+ setAlpha(!mHighlightWhenSelected || mSelected ? mSelectedAlpha : mUnselectedAlpha);
}
if (mShowMoreWhenSelected && mMoreIcon != null) {
mMoreIcon.setVisibility(selected ? VISIBLE : GONE);
@@ -108,6 +115,20 @@
updateImage();
}
+ /**
+ * Sets the current icon of the default application associated with this button.
+ */
+ public void setAppIcon(Drawable appIcon) {
+ mAppIcon = appIcon;
+ updateImage();
+ }
+
+ /** Gets the icon of the app currently associated to the role of this button. */
+ @VisibleForTesting
+ protected Drawable getAppIcon() {
+ return mAppIcon;
+ }
+
/** Gets whether the icon is in an unseen state. */
public boolean getUnseen() {
return mHasUnseen;
@@ -144,6 +165,22 @@
}
/**
+ * Subclasses should override this method to return the {@link RoleManager} role associated
+ * with this button.
+ */
+ protected String getRoleName() {
+ return null;
+ }
+
+ /**
+ * @return true if this button should show the icon of the default application for the
+ * role returned by {@link #getRoleName()}.
+ */
+ protected boolean isDefaultAppIconForRoleEnabled() {
+ return mIsDefaultAppIconForRoleEnabled;
+ }
+
+ /**
* @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
* a display.
*/
@@ -240,7 +277,6 @@
};
}
-
/**
* Initializes view-related aspects of the button.
*/
@@ -256,28 +292,27 @@
R.styleable.CarNavigationButton_showMoreWhenSelected,
mShowMoreWhenSelected);
- mSelectedIconResourceId = typedArray.getResourceId(
- R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
mIconResourceId = typedArray.getResourceId(
R.styleable.CarNavigationButton_icon, 0);
-
+ mSelectedIconResourceId = typedArray.getResourceId(
+ R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
+ mIsDefaultAppIconForRoleEnabled = typedArray.getBoolean(
+ R.styleable.CarNavigationButton_useDefaultAppIconForRole, false);
mIcon = findViewById(R.id.car_nav_button_icon_image);
- mIcon.setScaleType(ImageView.ScaleType.CENTER);
// Always apply selected alpha if the button does not toggle alpha based on selection state.
mIcon.setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha);
- mIcon.setImageResource(mIconResourceId);
-
mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
mMoreIcon.setAlpha(mSelectedAlpha);
- mMoreIcon.setVisibility(GONE);
-
mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon);
-
- mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
+ updateImage();
}
private void updateImage() {
- mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+ if (mIsDefaultAppIconForRoleEnabled && mAppIcon != null) {
+ mIcon.setImageDrawable(mAppIcon);
+ } else {
+ mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+ }
mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
}
diff --git a/packages/CarSystemUI/tests/res/layout/button_role_holder_controller_test.xml b/packages/CarSystemUI/tests/res/layout/button_role_holder_controller_test.xml
new file mode 100644
index 0000000..25ec2c1
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/layout/button_role_holder_controller_test.xml
@@ -0,0 +1,41 @@
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:gravity="center">
+
+ <com.android.systemui.car.navigationbar.AssitantButton
+ android:id="@+id/assistant_role_button"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:useDefaultAppIconForRole="true"
+ />
+
+ <com.android.systemui.car.navigationbar.AssitantButton
+ android:id="@+id/assistant_role_disabled_button"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_overview"
+ />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml b/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml
index f0e0216..a8e83d6 100644
--- a/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml
+++ b/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml
@@ -25,7 +25,7 @@
android:paddingEnd="20dp"
android:gravity="center">
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/detectable_by_component_name"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -34,7 +34,7 @@
systemui:highlightWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/detectable_by_category"
style="@style/NavigationBarButton"
systemui:categories="android.intent.category.APP_MAPS"
@@ -43,7 +43,7 @@
systemui:highlightWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/detectable_by_package"
style="@style/NavigationBarButton"
systemui:icon="@drawable/car_ic_phone"
diff --git a/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml b/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml
index b0ca8dc..94edc4b 100644
--- a/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml
+++ b/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.navigationbar.car.CarNavigationBarView
+<com.android.systemui.car.navigationbar.CarNavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -32,7 +32,7 @@
android:paddingEnd="20dp"
android:gravity="center">
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/home"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -55,4 +55,4 @@
android:visibility="gone"
/>
-</com.android.systemui.navigationbar.car.CarNavigationBarView>
+</com.android.systemui.car.navigationbar.CarNavigationBarView>
diff --git a/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml b/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml
index 576928c..44f8340 100644
--- a/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml
+++ b/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml
@@ -25,7 +25,7 @@
android:paddingEnd="20dp"
android:gravity="center">
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/default_no_selection_state"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -34,7 +34,7 @@
systemui:selectedIcon="@drawable/car_ic_overview_selected"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/app_grid_activity"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
@@ -44,7 +44,7 @@
systemui:highlightWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/long_click_app_grid_activity"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
@@ -54,7 +54,7 @@
systemui:highlightWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/broadcast"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -63,7 +63,7 @@
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/selected_icon_undefined"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -71,7 +71,7 @@
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/highlightable_no_more_button"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -81,7 +81,7 @@
systemui:highlightWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/not_highlightable_more_button"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -91,7 +91,7 @@
systemui:showMoreWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/highlightable_more_button"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -102,7 +102,7 @@
systemui:showMoreWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/broadcast"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -112,4 +112,13 @@
systemui:broadcast="true"
/>
+ <com.android.systemui.car.navigationbar.AssitantButton
+ android:id="@+id/role_based_button"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:useDefaultAppIconForRole="true"
+ systemui:highlightWhenSelected="true"
+ />
+
</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java
new file mode 100644
index 0000000..a57736b
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.navigationbar;
+
+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.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+import android.app.role.RoleManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.tests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class ButtonRoleHolderControllerTest extends SysuiTestCase {
+ private static final String TEST_VALID_PACKAGE_NAME = "foo";
+ private static final String TEST_INVALID_PACKAGE_NAME = "bar";
+ private static final UserHandle TEST_CURRENT_USER = UserHandle.of(100);
+ private static final UserHandle TEST_NON_CURRENT_USER = UserHandle.of(101);
+
+ private LinearLayout mTestView;
+ private CarNavigationButton mNavButtonDefaultAppIconForRoleWithEnabled;
+ private CarNavigationButton mNavButtonDefaultAppIconForRoleWithDisabled;
+ private ButtonRoleHolderController mControllerUnderTest;
+ private Drawable mAppIcon;
+
+ @Mock
+ private RoleManager mRoleManager;
+ @Mock
+ private CarDeviceProvisionedController mDeviceProvisionedController;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+
+ mTestView = (LinearLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.button_role_holder_controller_test, /* root= */ null);
+ mNavButtonDefaultAppIconForRoleWithEnabled = mTestView
+ .findViewById(R.id.assistant_role_button);
+ mNavButtonDefaultAppIconForRoleWithDisabled = mTestView
+ .findViewById(R.id.assistant_role_disabled_button);
+ mAppIcon = mContext.getDrawable(R.drawable.car_ic_apps);
+ when(mApplicationInfo.loadIcon(any())).thenReturn(mAppIcon);
+ doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+ .getApplicationInfo(any(), anyInt());
+ doReturn(mApplicationInfo).when(mPackageManager)
+ .getApplicationInfo(eq(TEST_VALID_PACKAGE_NAME), anyInt());
+ when(mDeviceProvisionedController
+ .getCurrentUser())
+ .thenReturn(TEST_CURRENT_USER.getIdentifier());
+ mControllerUnderTest = new ButtonRoleHolderController(mContext,
+ mPackageManager, mRoleManager, mDeviceProvisionedController);
+ }
+
+ @Test
+ public void addAllButtonsWithRoleName_roleAssigned_appIconEnabled_useAssignedAppIcon() {
+ when(mRoleManager.getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_VALID_PACKAGE_NAME));
+
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon()).isEqualTo(mAppIcon);
+ }
+
+ @Test
+ public void addAllButtonsWithRoleName_roleUnassigned_appIconEnabled_useDefaultIcon() {
+ when(mRoleManager.getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(null);
+
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon()).isNull();
+ }
+
+ @Test
+ public void onRoleChanged_currentUser_appIconEnabled_useAssignedAppIcon() {
+ when(mRoleManager.getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(null);
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_VALID_PACKAGE_NAME));
+
+ mControllerUnderTest.onRoleChanged(RoleManager.ROLE_ASSISTANT, TEST_CURRENT_USER);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon()).isEqualTo(mAppIcon);
+ }
+
+ @Test
+ public void onRoleChanged_nonCurrentUser_appIconEnabled_iconIsNotUpdated() {
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(null);
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+ Drawable beforeIcon = mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon();
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_VALID_PACKAGE_NAME));
+
+ mControllerUnderTest.onRoleChanged(RoleManager.ROLE_ASSISTANT, TEST_NON_CURRENT_USER);
+
+ Drawable afterIcon = mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon();
+ assertThat(afterIcon).isEqualTo(beforeIcon);
+ }
+
+ @Test
+ public void onRoleChanged_invalidPackage_useDefaultIcon() {
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_INVALID_PACKAGE_NAME));
+
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon()).isNull();
+ }
+
+ @Test
+ public void addAllButtonsWithRoleName_appIconDisabled_useDefaultIcon() {
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_VALID_PACKAGE_NAME));
+
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithDisabled.getAppIcon()).isNull();
+ }
+
+ @Test
+ public void onRoleChanged_roleAssigned_appIconDisabled_useDefaultIcon() {
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(null);
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+ assertThat(mNavButtonDefaultAppIconForRoleWithDisabled.getAppIcon()).isNull();
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_VALID_PACKAGE_NAME));
+
+ mControllerUnderTest.onRoleChanged(RoleManager.ROLE_ASSISTANT, TEST_CURRENT_USER);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithDisabled.getAppIcon()).isNull();
+ }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
index 911f624..e84e42c 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
@@ -53,6 +53,8 @@
@Mock
private ButtonSelectionStateController mButtonSelectionStateController;
@Mock
+ private ButtonRoleHolderController mButtonRoleHolderController;
+ @Mock
private HvacController mHvacController;
@Before
@@ -66,10 +68,15 @@
mDependency.injectMockDependency(StatusBarIconController.class);
}
+ private CarNavigationBarController createNavigationBarController() {
+ return new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+ mButtonSelectionStateController, () -> mHvacController,
+ mButtonRoleHolderController);
+ }
+
@Test
public void testConnectToHvac_callsConnect() {
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
mCarNavigationBar.connectToHvac();
@@ -77,20 +84,37 @@
}
@Test
- public void testRemoveAllFromHvac_callsRemoveAll() {
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ public void testRemoveAll_callsHvacControllerRemoveAllComponents() {
+ mCarNavigationBar = createNavigationBarController();
- mCarNavigationBar.removeAllFromHvac();
+ mCarNavigationBar.removeAll();
verify(mHvacController).removeAllComponents();
}
+
+ @Test
+ public void testRemoveAll_callsButtonRoleHolderControllerRemoveAll() {
+ mCarNavigationBar = createNavigationBarController();
+
+ mCarNavigationBar.removeAll();
+
+ verify(mButtonRoleHolderController).removeAll();
+ }
+
+ @Test
+ public void testRemoveAll_callsButtonSelectionStateControllerRemoveAll() {
+ mCarNavigationBar = createNavigationBarController();
+
+ mCarNavigationBar.removeAll();
+
+ verify(mButtonSelectionStateController).removeAll();
+ }
+
@Test
public void testGetTopWindow_topDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableTopNavigationBar, false);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getTopWindow();
@@ -100,8 +124,7 @@
@Test
public void testGetTopWindow_topEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableTopNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getTopWindow();
@@ -111,8 +134,7 @@
@Test
public void testGetTopWindow_topEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableTopNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window1 = mCarNavigationBar.getTopWindow();
ViewGroup window2 = mCarNavigationBar.getTopWindow();
@@ -123,8 +145,7 @@
@Test
public void testGetBottomWindow_bottomDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, false);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -134,8 +155,7 @@
@Test
public void testGetBottomWindow_bottomEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -145,8 +165,7 @@
@Test
public void testGetBottomWindow_bottomEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window1 = mCarNavigationBar.getBottomWindow();
ViewGroup window2 = mCarNavigationBar.getBottomWindow();
@@ -157,8 +176,7 @@
@Test
public void testGetLeftWindow_leftDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, false);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getLeftWindow();
assertThat(window).isNull();
}
@@ -166,8 +184,7 @@
@Test
public void testGetLeftWindow_leftEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getLeftWindow();
@@ -177,8 +194,7 @@
@Test
public void testGetLeftWindow_leftEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window1 = mCarNavigationBar.getLeftWindow();
ViewGroup window2 = mCarNavigationBar.getLeftWindow();
@@ -189,8 +205,7 @@
@Test
public void testGetRightWindow_rightDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, false);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getRightWindow();
@@ -200,8 +215,7 @@
@Test
public void testGetRightWindow_rightEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getRightWindow();
@@ -211,8 +225,7 @@
@Test
public void testGetRightWindow_rightEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window1 = mCarNavigationBar.getRightWindow();
ViewGroup window2 = mCarNavigationBar.getRightWindow();
@@ -223,8 +236,7 @@
@Test
public void testSetBottomWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getBottomWindow();
mCarNavigationBar.setBottomWindowVisibility(View.VISIBLE);
@@ -235,8 +247,7 @@
@Test
public void testSetBottomWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getBottomWindow();
mCarNavigationBar.setBottomWindowVisibility(View.GONE);
@@ -247,8 +258,7 @@
@Test
public void testSetLeftWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getLeftWindow();
mCarNavigationBar.setLeftWindowVisibility(View.VISIBLE);
@@ -259,8 +269,7 @@
@Test
public void testSetLeftWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getLeftWindow();
mCarNavigationBar.setLeftWindowVisibility(View.GONE);
@@ -271,8 +280,7 @@
@Test
public void testSetRightWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getRightWindow();
mCarNavigationBar.setRightWindowVisibility(View.VISIBLE);
@@ -283,8 +291,7 @@
@Test
public void testSetRightWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getRightWindow();
mCarNavigationBar.setRightWindowVisibility(View.GONE);
@@ -295,8 +302,7 @@
@Test
public void testRegisterBottomBarTouchListener_createViewFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View.OnTouchListener controller = bottomBar.getStatusBarWindowTouchListener();
@@ -310,8 +316,7 @@
@Test
public void testRegisterBottomBarTouchListener_registerFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
mCarNavigationBar.registerBottomBarTouchListener(mock(View.OnTouchListener.class));
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
@@ -323,8 +328,7 @@
@Test
public void testRegisterNotificationController_createViewFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationBarController.NotificationsShadeController controller =
@@ -340,8 +344,7 @@
@Test
public void testRegisterNotificationController_registerFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
mCarNavigationBar.registerNotificationController(
mock(CarNavigationBarController.NotificationsShadeController.class));
@@ -355,8 +358,7 @@
@Test
public void testShowAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
@@ -368,8 +370,7 @@
@Test
public void testShowAllKeyguardButtons_bottomEnabled_bottomNavButtonsGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
@@ -381,8 +382,7 @@
@Test
public void testHideAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
@@ -396,8 +396,7 @@
@Test
public void testHideAllKeyguardButtons_bottomEnabled_bottomNavButtonsVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
@@ -411,8 +410,7 @@
@Test
public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_hasUnseen_setCorrectly() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
@@ -426,8 +424,7 @@
@Test
public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_noUnseen_setCorrectly() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
index 04e0a73..0caa86f 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
@@ -89,6 +89,8 @@
@Mock
private ButtonSelectionStateListener mButtonSelectionStateListener;
@Mock
+ private ButtonRoleHolderController mButtonRoleHolderController;
+ @Mock
private IStatusBarService mBarService;
@Mock
private KeyguardStateController mKeyguardStateController;
@@ -137,8 +139,8 @@
mCarNavigationBarController, mLightBarController, mStatusBarIconController,
mWindowManager, mDeviceProvisionedController, new CommandQueue(mContext),
mAutoHideController, mButtonSelectionStateListener, mHandler, mUiBgExecutor,
- mBarService, () -> mKeyguardStateController, mButtonSelectionStateController,
- () -> mIconPolicy, () -> mIconController);
+ mBarService, () -> mKeyguardStateController, () -> mIconPolicy,
+ () -> mIconController);
}
@Test
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
index 11f2fa4..54282d3 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
@@ -180,6 +180,56 @@
}
@Test
+ public void onUnselected_withAppIcon_showsAppIcon() {
+ CarNavigationButton roleBasedButton = mTestView.findViewById(R.id.role_based_button);
+ Drawable appIcon = getContext().getDrawable(R.drawable.ic_android);
+
+ roleBasedButton.setSelected(false);
+ roleBasedButton.setAppIcon(appIcon);
+
+ Drawable currentDrawable = ((AlphaOptimizedImageButton) roleBasedButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+ assertThat(currentDrawable).isEqualTo(appIcon);
+ }
+
+ @Test
+ public void onUnselected_withAppIcon_applyUnselectedAlpha() {
+ CarNavigationButton roleBasedButton = mTestView.findViewById(R.id.role_based_button);
+
+ roleBasedButton.setSelected(false);
+ roleBasedButton.setAppIcon(getContext().getDrawable(R.drawable.ic_android));
+
+ assertThat(roleBasedButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_UNSELECTED_ALPHA);
+ }
+
+ @Test
+ public void onSelected_withAppIcon_showsAppIconWithSelectedAlpha() {
+ CarNavigationButton roleBasedButton = mTestView.findViewById(R.id.role_based_button);
+ Drawable appIcon = getContext().getDrawable(R.drawable.ic_android);
+
+ roleBasedButton.setSelected(true);
+ roleBasedButton.setAppIcon(appIcon);
+
+ Drawable currentDrawable = ((AlphaOptimizedImageButton) roleBasedButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+ assertThat(currentDrawable).isEqualTo(appIcon);
+ }
+
+ @Test
+ public void onSelected_withAppIcon_applySelectedAlpha() {
+ CarNavigationButton roleBasedButton = mTestView.findViewById(R.id.role_based_button);
+
+ roleBasedButton.setSelected(true);
+ roleBasedButton.setAppIcon(getContext().getDrawable(R.drawable.ic_android));
+
+ assertThat(roleBasedButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_SELECTED_ALPHA);
+ }
+
+ @Test
public void onClick_launchesIntentActivity() {
mDefaultButton.performClick();
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 66787c2..35e2295 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Geaktiveer"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Jou toestel moet herselflaai om hierdie verandering toe te pas. Herselflaai nou of kanselleer."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Werk-<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Bedraade oorfoon"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index ead0ebf..25cea4d 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ነቅቷል"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"የእርስዎን መሣሪያ ይህ ለው ለማመልከት እንደገና መነሣት አለበት። አሁን እንደገና ያስነሡ ወይም ይተዉት።"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"የስራ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ባለገመድ ጆሮ ማዳመጫ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index c1f4fd9..bd94c40 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -558,6 +558,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"مفعّل"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"يجب إعادة تشغيل جهازك ليتم تطبيق هذا التغيير. يمكنك إعادة التشغيل الآن أو إلغاء التغيير."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> المخصّص للعمل"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"سمّاعة رأس سلكية"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index b5d642a..5572994 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"সক্ষম কৰা আছে"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"এই সলনিটো কার্যকৰী হ’বলৈ আপোনাৰ ডিভাইচটো ৰিবুট কৰিবই লাগিব। এতিয়াই ৰিবুট কৰক অথবা বাতিল কৰক।"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"কৰ্মস্থান <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"তাঁৰযুক্ত হেডফ\'ন"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index aecaf70..39ab078 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiv"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bu dəyişikliyin tətbiq edilməsi üçün cihaz yenidən başladılmalıdır. İndi yenidən başladın və ya ləğv edin."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"İş <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Simli qulaqlıq"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 092bd42..78fd0bf 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Morate da restartujete uređaj da bi se ova promena primenila. Restartujte ga odmah ili otkažite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> za posao"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Žičane slušalice"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index d610973..30a9e0e 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Уключана"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Перазагрузіце прыладу, каб прымяніць гэта змяненне. Перазагрузіце ці скасуйце."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (праца)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Правадныя навушнікі"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 5c3fe75..c8dd82f 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Активирано"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"За да бъде приложена тази промяна, устройството ви трябва да бъде рестартирано. Рестартирайте сега или анулирайте."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> за работа"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Слушалки с кабел"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 27d3707..02ca679 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"চালু করা আছে"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"এই পরিবর্তনটি প্রয়োগ করার জন্য আপনার ডিভাইসটি অবশ্যই রিবুট করতে হবে। এখন রিবুট করুন বা বাতিল করুন।"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"অফিস <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"তার যুক্ত হেডফোন"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index a9d9e70..785460f 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Morate ponovo pokrenuti uređaj da se ova promjena primijeni. Ponovo pokrenite odmah ili otkažite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Poslovna aplikacija <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Žičane slušalice"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 5ed0f1b..50c0374 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Has de reiniciar el teu dispositiu perquè s\'apliquin els canvis. Reinicia\'l ara o cancel·la."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de la feina"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auriculars amb cable"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index d4d3735..9fb63ea 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Zapnuto"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Aby se tato změna projevila, je třeba zařízení restartovat. Restartujte zařízení nebo zrušte akci."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Pracovní <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Kabelová sluchátka"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 89dbf25..dc4f873 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiveret"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Din enhed skal genstartes for at denne enhed bliver anvendt. Genstart nu, eller annuller."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> – arbejde"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Høretelefoner med ledning"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index c2030b1..bee2d79 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiviert"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Damit diese Änderung übernommen wird, musst du dein Gerät neu starten. Du kannst es jetzt neu starten oder den Vorgang abbrechen."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (geschäftlich)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Kabelgebundene Kopfhörer"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index bfc183e..b1d07ac 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ενεργή"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Για να εφαρμοστεί αυτή η αλλαγή, θα πρέπει να επανεκκινήσετε τη συσκευή σας. Επανεκκίνηση τώρα ή ακύρωση."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Εργασία <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Ενσύρματα ακουστικά"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 9d869ba..e51456a 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 9d869ba..e51456a 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 9d869ba..e51456a 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 9d869ba..e51456a 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 7765824..34ff568 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 6a76beb..fb5e9d7 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -153,7 +153,7 @@
<string name="unknown" msgid="3544487229740637809">"Desconocido"</string>
<string name="running_process_item_user_label" msgid="3988506293099805796">"Usuario: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="launch_defaults_some" msgid="3631650616557252926">"Configuraciones predeterminadas establecidas"</string>
- <string name="launch_defaults_none" msgid="8049374306261262709">"No se establecieron configuraciones predeterminadas"</string>
+ <string name="launch_defaults_none" msgid="8049374306261262709">"Sin configuraciones predeterminadas"</string>
<string name="tts_settings" msgid="8130616705989351312">"Configuración de texto a voz"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"Salida de texto a voz"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"Velocidad de voz"</string>
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Habilitado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Debes reiniciar el dispositivo para que se aplique el cambio. Reinícialo ahora o cancela la acción."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de trabajo"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auriculares con cable"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 010b85b..a6a89dd 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Habilitado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Es necesario reiniciar tu dispositivo para que se apliquen los cambios. Reiniciar ahora o cancelar."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de trabajo"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auriculares con cable"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index ad2a561..65f1043 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Lubatud"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Selle muudatuse rakendamiseks tuleb seade taaskäivitada. Taaskäivitage kohe või tühistage."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Töö: <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Juhtmega kõrvaklapid"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 1ceb738..6864db1 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Gaituta"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Aldaketa aplikatzeko, berrabiarazi egin behar da gailua. Berrabiaraz ezazu orain, edo utzi bertan behera."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Laneko <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Entzungailu kableduna"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index d3f1c56..5e13c1b 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"فعال"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"برای اعمال این تغییر، دستگاهتان باید راهاندازی مجدد شود. اکنون راهاندازی مجدد کنید یا لغو کنید."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> محل کار"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"هدفون سیمی"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 5ab01c4b..1ec71c7 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Käytössä"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Laitteesi on käynnistettävä uudelleen, jotta muutos tulee voimaan. Käynnistä uudelleen nyt tai peruuta."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (työ)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Langalliset kuulokkeet"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 26e5042..0a32ca5 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activé"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Votre appareil doit être redémarré pour que ce changement prenne effet. Redémarrez-le maintenant ou annulez la modification."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (travail)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Écouteurs filaires"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index b8fc50d..bb069b5 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activé"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Vous devez redémarrer l\'appareil pour que cette modification soit appliquée. Redémarrez maintenant ou annulez l\'opération."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (travail)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Casque filaire"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 8dc58ef..a27f2c4 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necesario reiniciar o teu dispositivo para aplicar este cambio. Reiníciao agora ou cancela o cambio."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Aplicación <xliff:g id="APP_NAME">%s</xliff:g> do traballo"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auriculares con cable"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 9f69a2c..cf1e5ee 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ચાલુ છે"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"આ ફેરફારને લાગુ કરવા માટે તમારા ડિવાઇસને રીબૂટ કરવાની જરૂર છે. હમણાં જ રીબૂટ કરો કે રદ કરો."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ઑફિસ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"વાયરવાળો હૅડફોન"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index dc8e28b..89b7f67 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"चालू है"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"बदली गई सेटिंग को लागू करने के लिए, अपने डिवाइस को फिर से चालू करें. डिवाइस को फिर से चालू करें या रद्द करें."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ऑफ़िस वाला <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"वायर वाला हेडफ़ोन"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index f887839..f57a302 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Uređaj se mora ponovno pokrenuti da bi se ta promjena primijenila. Ponovo pokrenite uređaj odmah ili odustanite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> za posao"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Žičane slušalice"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index d7d2694..e8699dc 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Engedélyezve"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Az eszközt újra kell indítani, hogy a módosítás megtörténjen. Indítsa újra most, vagy vesse el a módosítást."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Munkahelyi <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Vezetékes fejhallgató"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index a5ef6b5..3f2b414 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Միացված է"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Սարքն անհրաժեշտ է վերագործարկել, որպեսզի փոփոխությունը կիրառվի։ Վերագործարկեք հիմա կամ չեղարկեք փոփոխությունը։"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Աշխատանքային <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Լարով ականջակալ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 04ca2f6..dd1f999 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktif"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Perangkat Anda harus di-reboot agar perubahan ini diterapkan. Reboot sekarang atau batalkan."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> kerja"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Headphone berkabel"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index ce60eb5..5dad63d 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Virkt"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Endurræsa þarf tækið til að þessi breyting taki gildi. Endurræstu núna eða hættu við."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> í vinnu"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Heyrnartól með snúru"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index bef644b..02b8533 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Attivo"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Devi riavviare il dispositivo per applicare questa modifica. Riavvia ora o annulla."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"App <xliff:g id="APP_NAME">%s</xliff:g> di lavoro"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Cuffie con cavo"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index ed79632..af6a870 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"מופעל"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"צריך להפעיל מחדש את המכשיר כדי להחיל את השינוי. יש להפעיל מחדש עכשיו או לבטל."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> של עבודה"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"אוזניות עם חוט"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 3f08a6a..080c1a9 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"有効"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"この変更を適用するには、デバイスの再起動が必要です。今すぐ再起動してください。キャンセルすることもできます。"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"仕事の<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"有線ヘッドフォン"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 2ebc3ee..23de5b2 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ჩართული"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ამ ცვლილების ასამოქმედებლად თქვენი მოწყობილობა უნდა გადაიტვირთოს. გადატვირთეთ ახლავე ან გააუქმეთ."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"სამსახურის <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"სადენიანი ყურსასმენი"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 91e5488..821c566 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Қосулы"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Бұл өзгеріс күшіне енуі үшін, құрылғыны қайта жүктеу керек. Қазір қайта жүктеңіз не бас тартыңыз."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (жұмыс)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Сымды құлақаспап"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 143f5f9..f93ab25 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"បានបើក"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ត្រូវតែចាប់ផ្ដើមឧបករណ៍របស់អ្នកឡើងវិញ ទើបការផ្លាស់ប្ដូរនេះត្រូវបានអនុវត្ត។ ចាប់ផ្ដើមឡើងវិញឥឡូវនេះ ឬបោះបង់។"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> សម្រាប់ការងារ"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"កាសមានខ្សែ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 1df141c..e418779 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ಈ ಬದಲಾವಣೆ ಅನ್ವಯವಾಗಲು ನಿಮ್ಮ ಸಾಧನವನ್ನು ರೀಬೂಟ್ ಮಾಡಬೇಕು. ಇದೀಗ ರೀಬೂಟ್ ಮಾಡಿ ಅಥವಾ ರದ್ದುಗೊಳಿಸಿ."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ಉದ್ಯೋಗ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ವೈಯರ್ ಹೊಂದಿರುವ ಹೆಡ್ಫೋನ್"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 86e1650..6b273a3 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"사용 설정됨"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"변경사항을 적용하려면 기기를 재부팅해야 합니다. 지금 재부팅하거나 취소하세요."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"직장용 <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"유선 헤드폰"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 925f4a7..d035362 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Күйүк"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Бул өзгөртүүнү колдонуу үчүн түзмөктү өчүрүп күйгүзүңүз. Азыр өчүрүп күйгүзүңүз же жокко чыгарыңыз."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Жумуш <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Зымдуу гарнитура"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 3bf1996..e188f3d 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ເປີດການນຳໃຊ້ແລ້ວ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ທ່ານຕ້ອງປິດເປີດອຸປະກອນຄືນໃໝ່ເພື່ອນຳໃຊ້ການປ່ຽນແປງນີ້. ປິດເປີດໃໝ່ດຽວນີ້ ຫຼື ຍົກເລີກ."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ບ່ອນເຮັດວຽກ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ຫູຟັງແບບມີສາຍ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 153c995..075032c 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Įgalinta"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Kad pakeitimas būtų pritaikytas, įrenginį reikia paleisti iš naujo. Dabar paleiskite iš naujo arba atšaukite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Darbo „<xliff:g id="APP_NAME">%s</xliff:g>“"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Laidinės ausinės"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index f6e7f35..c37811f 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Iespējots"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Lai šīs izmaiņas tiktu piemērotas, nepieciešama ierīces atkārtota palaišana. Atkārtoti palaidiet to tūlīt vai atceliet izmaiņas."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Darbā: <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Vadu austiņas"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index d55f1af..1f5908c 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Овозможено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"За да се примени променава, уредот мора да се рестартира. Рестартирајте сега или откажете."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Работна <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Жичени слушалки"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index c5267d6..6ee9777 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"പ്രവർത്തനക്ഷമമാക്കി"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ഈ മാറ്റം ബാധകമാകുന്നതിന് നിങ്ങളുടെ ഉപകരണം റീബൂട്ട് ചെയ്യേണ്ടതുണ്ട്. ഇപ്പോൾ റീബൂട്ട് ചെയ്യുകയോ റദ്ദാക്കുകയോ ചെയ്യുക."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ഔദ്യോഗികം <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"വയർ മുഖേന ബന്ധിപ്പിച്ച ഹെഡ്ഫോൺ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 0a01f84..29647df 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Идэвхжүүлсэн"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Энэ өөрчлөлтийг хэрэгжүүлэхийн тулд таны төхөөрөмжийг дахин асаах ёстой. Одоо дахин асаах эсвэл болино уу."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Ажлын <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Утастай чихэвч"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 075c748..99bf1e8 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"सुरू केले आहे"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"हा बदल लागू करण्यासाठी तुमचे डिव्हाइस रीबूट करणे आवश्यक आहे. आता रीबूट करा किंवा रद्द करा."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"कार्य <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"वायर असलेला हेडफोन"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index cdf32a7..bdecf64 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Didayakan"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Peranti anda mesti dibut semula supaya perubahan ini berlaku. But semula sekarang atau batalkan."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Kerja <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Fon kepala berwayar"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 2bd3b45..1519380 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ဖွင့်ထားသည်"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ဤအပြောင်းအလဲ ထည့်သွင်းရန် သင့်စက်ကို ပြန်လည်စတင်ရမည်။ ယခု ပြန်လည်စတင်ပါ သို့မဟုတ် ပယ်ဖျက်ပါ။"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"အလုပ် <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ကြိုးတပ်နားကြပ်"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index de5bed6..a2f862e 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Slått på"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Enheten din må startes på nytt for at denne endringen skal tre i kraft. Start på nytt nå eller avbryt."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Jobb-<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Hodetelefoner med kabel"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index bfe295a..159050e 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"सक्षम पारिएको छ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"यो परिवर्तन लागू गर्न तपाईंको यन्त्र अनिवार्य रूपमा रिबुट गर्नु पर्छ। अहिले रिबुट गर्नुहोस् वा रद्द गर्नुहोस्।"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"कार्यालयको प्रोफाइल <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"तारसहितको हेडफोन"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index a822719..7e3005b 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ingeschakeld"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Je apparaat moet opnieuw worden opgestart om deze wijziging toe te passen. Start nu opnieuw op of annuleer de wijziging."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> voor werk"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Bedrade hoofdtelefoon"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index ab9fbc3..19e250b 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ସକ୍ଷମ କରାଯାଇଛି"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ଏହି ପରିବର୍ତ୍ତନ ଲାଗୁ କରିବା ପାଇଁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ନିଶ୍ଚିତ ରୂପେ ରିବୁଟ୍ କରାଯିବା ଆବଶ୍ୟକ। ବର୍ତ୍ତମାନ ରିବୁଟ୍ କରନ୍ତୁ କିମ୍ବା ବାତିଲ୍ କରନ୍ତୁ।"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ୱାର୍କ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ତାରଯୁକ୍ତ ହେଡଫୋନ୍"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 35d8cba..184f4cd 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ਇਸ ਤਬਦੀਲੀ ਨੂੰ ਲਾਗੂ ਕਰਨ ਲਈ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨੂੰ ਰੀਬੂਟ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ। ਹੁਣੇ ਰੀਬੂਟ ਕਰੋ ਜਾਂ ਰੱਦ ਕਰੋ।"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ਕੰਮ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ਤਾਰ ਵਾਲੇ ਹੈੱਡਫ਼ੋਨ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 54ed131..eebea5f 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Włączono"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Wprowadzenie zmiany wymaga ponownego uruchomienia urządzenia. Uruchom ponownie teraz lub anuluj."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (do pracy)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Słuchawki przewodowe"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index c6dc1d3..142ef47 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necessário reinicializar o dispositivo para que a mudança seja aplicada. Faça isso agora ou cancele."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"App <xliff:g id="APP_NAME">%s</xliff:g> de trabalho"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Fones de ouvido com fio"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 999e684..0c6f488 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativada"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necessário reiniciar o dispositivo para aplicar esta alteração. Reinicie agora ou cancele."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de trabalho"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auscultadores com fios"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index c6dc1d3..142ef47 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necessário reinicializar o dispositivo para que a mudança seja aplicada. Faça isso agora ou cancele."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"App <xliff:g id="APP_NAME">%s</xliff:g> de trabalho"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Fones de ouvido com fio"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index ba2f36f..036cf83 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Pentru ca modificarea să se aplice, trebuie să reporniți dispozitivul. Reporniți-l acum sau anulați."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de serviciu"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Căști cu fir"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 8ec3875..6b39754 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Включено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Чтобы изменение вступило в силу, необходимо перезапустить устройство. Вы можете сделать это сейчас или позже."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Рабочее приложение \"<xliff:g id="APP_NAME">%s</xliff:g>\""</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Проводные наушники"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 01c3634..9074c63 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"සබලයි"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"මෙම වෙනස යෙදීමට ඔබේ උපාංගය නැවත පණ ගැන්විය යුතුය. දැන් නැවත පණ ගන්වන්න හෝ අවලංගු කරන්න."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"කාර්යාල <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"රැහැන්ගත කළ හෙඩ්ෆෝන්"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index b5cbf43..cd3109f 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Zapnuté"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Táto zmena sa uplatní až po reštartovaní zariadenia. Zariadenie reštartujte alebo zmenu zrušte."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Pracovná aplikácia <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Slúchadlá s káblom"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 86cccb8..0e492c7 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogočeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Napravo je treba znova zagnati, da bo ta sprememba uveljavljena. Znova zaženite zdaj ali prekličite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> za delo"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Žične slušalke"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index bd3353c..4a80bcd 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiv"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Pajisja jote duhet të riniset që ky ndryshim të zbatohet. Rinise tani ose anuloje."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> për punën"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Kufje me tela"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 29f23b4..321645e 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Омогућено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Морате да рестартујете уређај да би се ова промена применила. Рестартујте га одмах или откажите."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> за посао"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Жичане слушалице"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index a40100e..cd28c86b 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiverat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Enheten måste startas om för att ändringen ska börja gälla. Starta om nu eller avbryt."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> för arbetet"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Hörlurar med sladd"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index fedf1c8..2bc038e 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Imewashwa"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Ni lazima uwashe tena kifaa chako ili mabadiliko haya yatekelezwe. Washa tena sasa au ughairi."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Ya kazini <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Vipokea sauti vyenye waya vinavyobanwa kichwani"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 345f0a7..a28fdce 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"இயக்கப்பட்டது"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"இந்த மாற்றங்கள் செயல்படுத்தப்பட உங்கள் சாதனத்தை மறுபடி தொடங்க வேண்டும். இப்போதே மறுபடி தொடங்கவும் அல்லது ரத்துசெய்யவும்."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"பணியிடம் <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"வயருள்ள ஹெட்ஃபோன்"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index c4fda01..b7bb6e8 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ఎనేబుల్ చేయబడింది"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ఈ మార్పును వర్తింపజేయాలంటే మీరు మీ పరికరాన్ని తప్పనిసరిగా రీబూట్ చేయాలి. ఇప్పుడే రీబూట్ చేయండి లేదా రద్దు చేయండి."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ఆఫీసు <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"వైర్ ఉన్న హెడ్ఫోన్"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index a9032eb..2f9ac46 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"เปิดใช้"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"คุณต้องรีบูตอุปกรณ์เพื่อให้การเปลี่ยนแปลงนี้มีผล รีบูตเลยหรือยกเลิก"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> ในโปรไฟล์งาน"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"หูฟังแบบมีสาย"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 712c06b..5064c1e 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Na-enable"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Dapat i-reboot ang iyong device para mailapat ang pagbabagong ito. Mag-reboot ngayon o kanselahin."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> sa Trabaho"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired na headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 8818498..454ca3b 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Etkin"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bu değişikliğin geçerli olması için cihazının yeniden başlatılması gerekir. Şimdi yeniden başlatın veya iptal edin."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (İş)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Kablolu kulaklık"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index f6568e3..7e969f7 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Увімкнено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Щоб застосувати ці зміни, перезапустіть пристрій. Перезапустіть пристрій або скасуйте зміни."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Робочий додаток <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Дротові навушники"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index f13f8fb..e3b28c7 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"فعال"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"اس تبدیلی کو لاگو کرنے کے ليے آپ کے آلہ کو ریبوٹ کرنا ضروری ہے۔ ابھی ریبوٹ کریں یا منسوخ کریں۔"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"دفتر <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"وائرڈ ہیڈ فون"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 7b8f627..04707c5 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Yoniq"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Oʻzgarishlar qurilma oʻchib yonganda bajariladi. Hoziroq oʻchib yoqish yoki bekor qilish."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Ish <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Simli quloqlik"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 35cecc1..e7fbf46 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Đã bật"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bạn phải khởi động lại thiết bị để áp dụng sự thay đổi này. Hãy khởi động lại ngay hoặc hủy."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> dành cho công việc"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Tai nghe có dây"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index c69ea2d..bda7d8b 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已启用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"设备必须重新启动才能应用此更改。您可以立即重新启动或取消。"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"工作资料中的<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"有线耳机"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index b0324d9..841de63 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已啟用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"您的裝置必須重新開機,才能套用此變更。請立即重新開機或取消。"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"工作設定檔入面嘅「<xliff:g id="APP_NAME">%s</xliff:g>」"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"有線耳機"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 0574c7e..a6c1647 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已啟用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"裝置必須重新啟動才能套用這項變更。請立即重新啟動或取消變更。"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"工作資料夾中的<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"有線耳機"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index b60553a..c437580 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Inikwe amandla"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Kufanele idivayisi yakho iqaliswe ukuze lolu shintsho lusebenze. Qalisa manje noma khansela."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Umsebenzi we-<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Ama-headphone anentambo"</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index d3f9cd4..9d1b3cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -7,6 +7,7 @@
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore;
@@ -132,6 +133,44 @@
*/
public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context,
CachedBluetoothDevice cachedDevice) {
+ final Resources resources = context.getResources();
+ final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context,
+ cachedDevice);
+
+ if (pair.first instanceof BitmapDrawable) {
+ return new Pair<>(new AdaptiveOutlineDrawable(
+ resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second);
+ }
+
+ return new Pair<>(buildBtRainbowDrawable(context,
+ pair.first, cachedDevice.getAddress().hashCode()), pair.second);
+ }
+
+ /**
+ * Build Bluetooth device icon with rainbow
+ */
+ public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
+ int hashCode) {
+ final Resources resources = context.getResources();
+
+ // Deal with normal headset
+ final int[] iconFgColors = resources.getIntArray(R.array.bt_icon_fg_colors);
+ final int[] iconBgColors = resources.getIntArray(R.array.bt_icon_bg_colors);
+
+ // get color index based on mac address
+ final int index = Math.abs(hashCode % iconBgColors.length);
+ drawable.setTint(iconFgColors[index]);
+ final Drawable adaptiveIcon = new AdaptiveIcon(context, drawable);
+ ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(iconBgColors[index]);
+
+ return adaptiveIcon;
+ }
+
+ /**
+ * Get bluetooth icon with description
+ */
+ public static Pair<Drawable, String> getBtDrawableWithDescription(Context context,
+ CachedBluetoothDevice cachedDevice) {
final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
context, cachedDevice);
final BluetoothDevice bluetoothDevice = cachedDevice.getDevice();
@@ -159,9 +198,8 @@
final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
iconSize, false);
bitmap.recycle();
- final AdaptiveOutlineDrawable drawable = new AdaptiveOutlineDrawable(
- resources, resizedBitmap);
- return new Pair<>(drawable, pair.second);
+ return new Pair<>(new BitmapDrawable(resources,
+ resizedBitmap), pair.second);
}
} catch (IOException e) {
Log.e(TAG, "Failed to get drawable for: " + iconUri, e);
@@ -171,28 +209,7 @@
}
}
- return new Pair<>(buildBtRainbowDrawable(context,
- pair.first, cachedDevice.getAddress().hashCode()), pair.second);
- }
-
- /**
- * Build Bluetooth device icon with rainbow
- */
- public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
- int hashCode) {
- final Resources resources = context.getResources();
-
- // Deal with normal headset
- final int[] iconFgColors = resources.getIntArray(R.array.bt_icon_fg_colors);
- final int[] iconBgColors = resources.getIntArray(R.array.bt_icon_bg_colors);
-
- // get color index based on mac address
- final int index = Math.abs(hashCode % iconBgColors.length);
- drawable.setTint(iconFgColors[index]);
- final Drawable adaptiveIcon = new AdaptiveIcon(context, drawable);
- ((AdaptiveIcon) adaptiveIcon).setBackgroundColor(iconBgColors[index]);
-
- return adaptiveIcon;
+ return new Pair<>(pair.first, pair.second);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 74a5939..a2f77e2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -63,6 +63,11 @@
}
@Override
+ public Drawable getIconWithoutBackground() {
+ return BluetoothUtils.getBtDrawableWithDescription(mContext, mCachedDevice).first;
+ }
+
+ @Override
public String getId() {
return MediaDeviceUtils.getId(mCachedDevice);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 83a9671..22dc906 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -60,6 +60,11 @@
mContext.getDrawable(getDrawableResId()), getId().hashCode());
}
+ @Override
+ public Drawable getIconWithoutBackground() {
+ return mContext.getDrawable(getDrawableResId());
+ }
+
@VisibleForTesting
int getDrawableResId() {
int resId;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 008a433..959c42fe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -56,7 +56,7 @@
public class InfoMediaManager extends MediaManager {
private static final String TAG = "InfoMediaManager";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);;
@VisibleForTesting
final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback();
@VisibleForTesting
@@ -364,8 +364,8 @@
private void buildAvailableRoutes() {
for (MediaRoute2Info route : mRouterManager.getAvailableRoutes(mPackageName)) {
if (DEBUG) {
- Log.d(TAG, "buildAvailableRoutes() route : " + route.getName()
- + ", type : " + route.getType());
+ Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : "
+ + route.getVolume() + ", type : " + route.getType());
}
addMediaDevice(route);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 7d95f19..f34903c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -394,7 +394,9 @@
return mPackageName;
}
- private MediaDevice updateCurrentConnectedDevice() {
+ @VisibleForTesting
+ MediaDevice updateCurrentConnectedDevice() {
+ MediaDevice connectedDevice = null;
synchronized (mMediaDevicesLock) {
for (MediaDevice device : mMediaDevices) {
if (device instanceof BluetoothMediaDevice) {
@@ -402,12 +404,12 @@
return device;
}
} else if (device instanceof PhoneMediaDevice) {
- return device;
+ connectedDevice = device;
}
}
}
- Log.w(TAG, "updateCurrentConnectedDevice() can't found current connected device");
- return null;
+
+ return connectedDevice;
}
private boolean isActiveDevice(CachedBluetoothDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 8a178a9..317077b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -153,6 +153,13 @@
public abstract Drawable getIcon();
/**
+ * Get icon of MediaDevice without background.
+ *
+ * @return drawable of icon
+ */
+ public abstract Drawable getIconWithoutBackground();
+
+ /**
* Get unique ID that represent MediaDevice
* @return unique id of MediaDevice
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 9a3ae1b..c43a512 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -89,6 +89,11 @@
mContext.getDrawable(getDrawableResId()), getId().hashCode());
}
+ @Override
+ public Drawable getIconWithoutBackground() {
+ return mContext.getDrawable(getDrawableResId());
+ }
+
@VisibleForTesting
int getDrawableResId() {
int resId;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 734866f..c514671 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -590,7 +590,7 @@
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
- mInfoMediaManager.mMediaRouterCallback.onTransferred(null, null);
+ mInfoMediaManager.mMediaRouterCallback.onTransferred(sessionInfo, sessionInfo);
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -602,6 +602,7 @@
@Test
public void onTransferred_buildAllRoutes_shouldAddMediaDevice() {
final MediaRoute2Info info = mock(MediaRoute2Info.class);
+ final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
mInfoMediaManager.registerCallback(mCallback);
when(info.getId()).thenReturn(TEST_ID);
@@ -616,7 +617,7 @@
assertThat(mediaDevice).isNull();
mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onTransferred(null, null);
+ mInfoMediaManager.mMediaRouterCallback.onTransferred(sessionInfo, sessionInfo);
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 517071b..009f75a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -31,6 +31,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
@@ -725,4 +726,44 @@
verify(mInfoMediaManager).adjustSessionVolume(info, 10);
}
+
+ @Test
+ public void updateCurrentConnectedDevice_bluetoothDeviceIsActive_returnBluetoothDevice() {
+ final BluetoothMediaDevice device1 = mock(BluetoothMediaDevice.class);
+ final BluetoothMediaDevice device2 = mock(BluetoothMediaDevice.class);
+ final PhoneMediaDevice phoneDevice = mock(PhoneMediaDevice.class);
+ final CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class);
+ final CachedBluetoothDevice cachedDevice2 = mock(CachedBluetoothDevice.class);
+
+ when(device1.getCachedDevice()).thenReturn(cachedDevice1);
+ when(device2.getCachedDevice()).thenReturn(cachedDevice2);
+ when(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(false);
+ when(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(true);
+
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(phoneDevice);
+ mLocalMediaManager.mMediaDevices.add(device2);
+
+ assertThat(mLocalMediaManager.updateCurrentConnectedDevice()).isEqualTo(device2);
+ }
+
+ @Test
+ public void updateCurrentConnectedDevice_phoneDeviceIsActive_returnPhoneDevice() {
+ final BluetoothMediaDevice device1 = mock(BluetoothMediaDevice.class);
+ final BluetoothMediaDevice device2 = mock(BluetoothMediaDevice.class);
+ final PhoneMediaDevice phoneDevice = mock(PhoneMediaDevice.class);
+ final CachedBluetoothDevice cachedDevice1 = mock(CachedBluetoothDevice.class);
+ final CachedBluetoothDevice cachedDevice2 = mock(CachedBluetoothDevice.class);
+
+ when(device1.getCachedDevice()).thenReturn(cachedDevice1);
+ when(device2.getCachedDevice()).thenReturn(cachedDevice2);
+ when(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(false);
+ when(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).thenReturn(false);
+
+ mLocalMediaManager.mMediaDevices.add(device1);
+ mLocalMediaManager.mMediaDevices.add(phoneDevice);
+ mLocalMediaManager.mMediaDevices.add(device2);
+
+ assertThat(mLocalMediaManager.updateCurrentConnectedDevice()).isEqualTo(phoneDevice);
+ }
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 055b2be..985269b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -607,7 +607,7 @@
android:excludeFromRecents="true"
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
- android:theme="@android:style/Theme.Black.NoTitleBar">
+ android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL_WITH_USER" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 2587369..eb4ba6f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -776,6 +776,10 @@
}
}
+ boolean inLandscape() {
+ return mOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
/**
* Set a listener to be notified of bubble expand events.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 7697400..c4b4f43 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -226,8 +226,12 @@
public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ updateDimensions();
+ }
+
+ void updateDimensions() {
mDisplaySize = new Point();
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
// Get the real size -- this includes screen decorations (notches, statusbar, navbar).
mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
Resources res = getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
index f4eb580..af6e66a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
@@ -61,9 +61,7 @@
}
void setUpOverflow(ViewGroup parentViewGroup, BubbleStackView stackView) {
- mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
- mIconBitmapSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_overflow_icon_bitmap_size);
+ updateDimensions();
mExpandedView = (BubbleExpandedView) mInflater.inflate(
R.layout.bubble_expanded_view, parentViewGroup /* root */,
@@ -74,6 +72,15 @@
updateIcon(mContext, parentViewGroup);
}
+ void updateDimensions() {
+ mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
+ mIconBitmapSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_overflow_icon_bitmap_size);
+ if (mExpandedView != null) {
+ mExpandedView.updateDimensions();
+ }
+ }
+
void updateIcon(Context context, ViewGroup parentViewGroup) {
mContext = context;
mInflater = LayoutInflater.from(context);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 08ec789..8fec338 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -70,6 +70,9 @@
}
@Override
public boolean canScrollVertically() {
+ if (mBubbleController.inLandscape()) {
+ return super.canScrollVertically();
+ }
return false;
}
}
@@ -89,6 +92,14 @@
mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
+ updateDimensions();
+ onDataChanged(mBubbleController.getOverflowBubbles());
+ mBubbleController.setOverflowCallback(() -> {
+ onDataChanged(mBubbleController.getOverflowBubbles());
+ });
+ }
+
+ void updateDimensions() {
Resources res = getResources();
final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
mRecyclerView.setLayoutManager(
@@ -96,8 +107,9 @@
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
- final int recyclerViewWidth = (displayMetrics.widthPixels
- - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding));
+
+ final int overflowPadding = res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
+ final int recyclerViewWidth = displayMetrics.widthPixels - (overflowPadding * 2);
final int viewWidth = recyclerViewWidth / columns;
final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow);
@@ -109,17 +121,12 @@
mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
mRecyclerView.setAdapter(mAdapter);
- onDataChanged(mBubbleController.getOverflowBubbles());
- mBubbleController.setOverflowCallback(() -> {
- onDataChanged(mBubbleController.getOverflowBubbles());
- });
- onThemeChanged();
}
/**
* Handle theme changes.
*/
- void onThemeChanged() {
+ void updateTheme() {
final int mode =
getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (mode) {
@@ -178,7 +185,8 @@
@Override
public void onResume() {
super.onResume();
- onThemeChanged();
+ updateDimensions();
+ updateTheme();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index ec45f93..bdfcb35 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -32,6 +32,7 @@
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Notification;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -49,6 +50,7 @@
import android.graphics.Region;
import android.os.Bundle;
import android.os.Vibrator;
+import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.Choreographer;
@@ -763,9 +765,13 @@
targetView.setTranslationY(
getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height));
+ final ContentResolver contentResolver = getContext().getContentResolver();
+ final int dismissRadius = Settings.Secure.getInt(
+ contentResolver, "bubble_dismiss_radius", mBubbleSize * 2 /* default */);
+
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
// MagnetizedObjects.
- mMagneticTarget = new MagnetizedObject.MagneticTarget(targetView, mBubbleSize * 2);
+ mMagneticTarget = new MagnetizedObject.MagneticTarget(targetView, dismissRadius);
mExpandedViewXAnim =
new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
@@ -1027,6 +1033,7 @@
mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
} else {
mBubbleContainer.removeView(mBubbleOverflow.getBtn());
+ mBubbleOverflow.updateDimensions();
mBubbleOverflow.updateIcon(mContext,this);
overflowBtnIndex = mBubbleContainer.getChildCount();
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index d52c35b..fd1f879 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -16,10 +16,12 @@
package com.android.systemui.bubbles.animation;
+import android.content.ContentResolver;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.WindowInsets;
@@ -83,8 +85,7 @@
* screen, we want less friction horizontally so that the stack has a better chance of making it
* to the side without needing a spring.
*/
- private static final float FLING_FRICTION_X = 2.2f;
- private static final float FLING_FRICTION_Y = 2.2f;
+ private static final float FLING_FRICTION = 2.2f;
/**
* Values to use for the stack spring animation used to spring the stack to its final position
@@ -363,18 +364,26 @@
return destinationRelativeX;
}
+ final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
+ final float stiffness = Settings.Secure.getFloat(contentResolver, "bubble_stiffness",
+ SPRING_AFTER_FLING_STIFFNESS /* default */);
+ final float dampingRatio = Settings.Secure.getFloat(contentResolver, "bubble_damping",
+ SPRING_AFTER_FLING_DAMPING_RATIO);
+ final float friction = Settings.Secure.getFloat(contentResolver, "bubble_friction",
+ FLING_FRICTION);
+
// Minimum velocity required for the stack to make it to the targeted side of the screen,
// taking friction into account (4.2f is the number that friction scalars are multiplied by
// in DynamicAnimation.DragForce). This is an estimate - it could possibly be slightly off,
// but the SpringAnimation at the end will ensure that it reaches the destination X
// regardless.
final float minimumVelocityToReachEdge =
- (destinationRelativeX - x) * (FLING_FRICTION_X * 4.2f);
+ (destinationRelativeX - x) * (friction * 4.2f);
final float estimatedY = PhysicsAnimator.estimateFlingEndValue(
mStackPosition.y, velY,
new PhysicsAnimator.FlingConfig(
- FLING_FRICTION_Y, stackBounds.top, stackBounds.bottom));
+ friction, stackBounds.top, stackBounds.bottom));
notifyFloatingCoordinatorStackAnimatingTo(destinationRelativeX, estimatedY);
@@ -384,22 +393,24 @@
? Math.min(minimumVelocityToReachEdge, velX)
: Math.max(minimumVelocityToReachEdge, velX);
+
+
flingThenSpringFirstBubbleWithStackFollowing(
DynamicAnimation.TRANSLATION_X,
startXVelocity,
- FLING_FRICTION_X,
+ friction,
new SpringForce()
- .setStiffness(SPRING_AFTER_FLING_STIFFNESS)
- .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
+ .setStiffness(stiffness)
+ .setDampingRatio(dampingRatio),
destinationRelativeX);
flingThenSpringFirstBubbleWithStackFollowing(
DynamicAnimation.TRANSLATION_Y,
velY,
- FLING_FRICTION_Y,
+ friction,
new SpringForce()
- .setStiffness(SPRING_AFTER_FLING_STIFFNESS)
- .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
+ .setStiffness(stiffness)
+ .setDampingRatio(dampingRatio),
/* destination */ null);
// If we're flinging now, there's no more touch event to catch up to.
@@ -761,9 +772,15 @@
@Override
SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+ final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
+ final float stiffness = Settings.Secure.getFloat(contentResolver, "bubble_stiffness",
+ mIsMovingFromFlinging ? FLING_FOLLOW_STIFFNESS : DEFAULT_STIFFNESS /* default */);
+ final float dampingRatio = Settings.Secure.getFloat(contentResolver, "bubble_damping",
+ DEFAULT_BOUNCINESS);
+
return new SpringForce()
- .setDampingRatio(DEFAULT_BOUNCINESS)
- .setStiffness(mIsMovingFromFlinging ? FLING_FOLLOW_STIFFNESS : DEFAULT_STIFFNESS);
+ .setDampingRatio(dampingRatio)
+ .setStiffness(stiffness);
}
@Override
@@ -1011,6 +1028,21 @@
mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
}
+ final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
+ final float minVelocity = Settings.Secure.getFloat(contentResolver,
+ "bubble_dismiss_fling_min_velocity",
+ mMagnetizedStack.getFlingToTargetMinVelocity() /* default */);
+ final float maxVelocity = Settings.Secure.getFloat(contentResolver,
+ "bubble_dismiss_stick_max_velocity",
+ mMagnetizedStack.getStickToTargetMaxVelocity() /* default */);
+ final float targetWidth = Settings.Secure.getFloat(contentResolver,
+ "bubble_dismiss_target_width_percent",
+ mMagnetizedStack.getFlingToTargetWidthPercent() /* default */);
+
+ mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity);
+ mMagnetizedStack.setStickToTargetMaxVelocity(maxVelocity);
+ mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth);
+
return mMagnetizedStack;
}
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 181170b..f8f913e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -72,6 +72,9 @@
private const val USER_CHANGE_RETRY_DELAY = 500L // ms
private const val DEFAULT_ENABLED = 1
private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
+
+ private fun isAvailable(userId: Int, cr: ContentResolver) = Settings.Secure.getIntForUser(
+ cr, CONTROLS_AVAILABLE, DEFAULT_ENABLED, userId) != 0
}
private var userChanging: Boolean = true
@@ -85,8 +88,7 @@
private val contentResolver: ContentResolver
get() = context.contentResolver
- override var available = Settings.Secure.getIntForUser(
- contentResolver, CONTROLS_AVAILABLE, DEFAULT_ENABLED, currentUserId) != 0
+ override var available = isAvailable(currentUserId, contentResolver)
private set
private val persistenceWrapper: ControlsFavoritePersistenceWrapper
@@ -119,8 +121,7 @@
BackupManager(userStructure.userContext)
)
auxiliaryPersistenceWrapper.changeFile(userStructure.auxiliaryFile)
- available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE,
- DEFAULT_ENABLED, newUser.identifier) != 0
+ available = isAvailable(newUser.identifier, contentResolver)
resetFavorites(available)
bindingController.changeUser(newUser)
listingController.changeUser(newUser)
@@ -131,7 +132,6 @@
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_USER_SWITCHED) {
userChanging = true
- listingController.removeCallback(listingCallback)
val newUser =
UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId))
if (currentUser == newUser) {
@@ -151,7 +151,6 @@
executor.execute {
Log.d(TAG, "Restore finished, storing auxiliary favorites")
auxiliaryPersistenceWrapper.initialize()
- listingController.removeCallback(listingCallback)
persistenceWrapper.storeFavorites(auxiliaryPersistenceWrapper.favorites)
resetFavorites(available)
}
@@ -172,8 +171,7 @@
if (userChanging || userId != currentUserId) {
return
}
- available = Settings.Secure.getIntForUser(contentResolver, CONTROLS_AVAILABLE,
- DEFAULT_ENABLED, currentUserId) != 0
+ available = isAvailable(currentUserId, contentResolver)
resetFavorites(available)
}
}
@@ -244,6 +242,7 @@
null
)
contentResolver.registerContentObserver(URI, false, settingObserver, UserHandle.USER_ALL)
+ listingController.addCallback(listingCallback)
}
fun destroy() {
@@ -258,7 +257,6 @@
if (shouldLoad) {
Favorites.load(persistenceWrapper.readFavorites())
- listingController.addCallback(listingCallback)
}
}
@@ -569,12 +567,12 @@
val userContext = context.createContextAsUser(user, 0)
val file = Environment.buildPath(
- context.filesDir,
+ userContext.filesDir,
ControlsFavoritePersistenceWrapper.FILE_NAME
)
val auxiliaryFile = Environment.buildPath(
- context.filesDir,
+ userContext.filesDir,
AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index a2adcf9..1cd9712 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -29,6 +29,7 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dagger.qualifiers.Background
import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
import javax.inject.Singleton
@@ -75,6 +76,7 @@
private var availableComponents = emptySet<ComponentName>()
private var availableServices = emptyList<ServiceInfo>()
+ private var userChangeInProgress = AtomicInteger(0)
override var currentUserId = ActivityManager.getCurrentUser()
private set
@@ -85,6 +87,7 @@
newServices.mapTo(mutableSetOf<ComponentName>(), { s -> s.getComponentName() })
backgroundExecutor.execute {
+ if (userChangeInProgress.get() > 0) return@execute
if (!newComponents.equals(availableComponents)) {
Log.d(TAG, "ServiceConfig reloaded, count: ${newComponents.size}")
availableComponents = newComponents
@@ -105,22 +108,18 @@
}
override fun changeUser(newUser: UserHandle) {
+ userChangeInProgress.incrementAndGet()
+ serviceListing.setListening(false)
+
backgroundExecutor.execute {
- serviceListing.setListening(false)
-
- // Notify all callbacks in order to clear their existing state prior to attaching
- // a new listener
- availableServices = emptyList()
- callbacks.forEach {
- it.onServicesUpdated(emptyList())
+ if (userChangeInProgress.decrementAndGet() == 0) {
+ currentUserId = newUser.identifier
+ val contextForUser = context.createContextAsUser(newUser, 0)
+ serviceListing = serviceListingBuilder(contextForUser)
+ serviceListing.addCallback(serviceListingCallback)
+ serviceListing.setListening(true)
+ serviceListing.reload()
}
-
- currentUserId = newUser.identifier
- val contextForUser = context.createContextAsUser(newUser, 0)
- serviceListing = serviceListingBuilder(contextForUser)
- serviceListing.addCallback(serviceListingCallback)
- serviceListing.setListening(true)
- serviceListing.reload()
}
}
@@ -134,10 +133,16 @@
*/
override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
backgroundExecutor.execute {
- val services = getCurrentServices()
- Log.d(TAG, "Subscribing callback, service count: ${services.size}")
- callbacks.add(listener)
- listener.onServicesUpdated(services)
+ if (userChangeInProgress.get() > 0) {
+ // repost this event, as callers may rely on the initial callback from
+ // onServicesUpdated
+ addCallback(listener)
+ } else {
+ val services = getCurrentServices()
+ Log.d(TAG, "Subscribing callback, service count: ${services.size}")
+ callbacks.add(listener)
+ listener.onServicesUpdated(services)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 3a8212c..58aad86 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -28,6 +28,7 @@
import android.app.NotificationManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
import android.app.trust.TrustManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -314,4 +315,9 @@
return context.getSystemService(WindowManager.class);
}
+ @Provides
+ @Singleton
+ static RoleManager provideRoleManager(Context context) {
+ return context.getSystemService(RoleManager.class);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index ac289cb..61c9a96 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -129,6 +129,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
+import com.android.systemui.settings.CurrentUserContextTracker;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -238,10 +239,10 @@
private final Executor mBackgroundExecutor;
private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
private ControlsController mControlsController;
- private SharedPreferences mControlsPreferences;
private final RingerModeTracker mRingerModeTracker;
private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
private Handler mMainHandler;
+ private CurrentUserContextTracker mCurrentUserContextTracker;
@VisibleForTesting
boolean mShowLockScreenCardsAndControls = false;
@@ -301,7 +302,8 @@
@Background Executor backgroundExecutor,
ControlsListingController controlsListingController,
ControlsController controlsController, UiEventLogger uiEventLogger,
- RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
+ RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
+ CurrentUserContextTracker currentUserContextTracker) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -330,6 +332,7 @@
mControlsController = controlsController;
mSysUiState = sysUiState;
mMainHandler = handler;
+ mCurrentUserContextTracker = currentUserContextTracker;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -382,12 +385,6 @@
controlsListingController.addCallback(list -> mControlsServiceInfos = list);
- // Need to be user-specific with the context to make sure we read the correct prefs
- Context userContext = context.createContextAsUser(
- new UserHandle(mUserManager.getUserHandle()), 0);
- mControlsPreferences = userContext.getSharedPreferences(PREFS_CONTROLS_FILE,
- Context.MODE_PRIVATE);
-
// Listen for changes to show controls on the power menu while locked
onPowerMenuLockScreenSettingsChanged();
mContext.getContentResolver().registerContentObserver(
@@ -403,8 +400,14 @@
private void seedFavorites() {
if (mControlsServiceInfos.isEmpty()
- || mControlsController.getFavorites().size() > 0
- || mControlsPreferences.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false)) {
+ || mControlsController.getFavorites().size() > 0) {
+ return;
+ }
+
+ // Need to be user-specific with the context to make sure we read the correct prefs
+ SharedPreferences prefs = mCurrentUserContextTracker.getCurrentUserContext()
+ .getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE);
+ if (prefs.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false)) {
return;
}
@@ -426,7 +429,7 @@
if (preferredComponent == null) {
Log.i(TAG, "Controls seeding: No preferred component has been set, will not seed");
- mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply();
+ prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply();
return;
}
@@ -434,8 +437,7 @@
preferredComponent,
(accepted) -> {
Log.i(TAG, "Controls seeded: " + accepted);
- mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED,
- accepted).apply();
+ prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, accepted).apply();
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index b9b8a25..a10972e 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -36,10 +36,11 @@
import android.util.TypedValue;
import android.view.DisplayInfo;
import android.view.Gravity;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
import android.window.WindowContainerTransaction;
+import com.android.systemui.wm.DisplayController;
+import com.android.systemui.wm.DisplayLayout;
+
import java.io.PrintWriter;
import javax.inject.Inject;
@@ -56,10 +57,10 @@
private static final float INVALID_SNAP_FRACTION = -1f;
private final Context mContext;
- private final IWindowManager mWindowManager;
private final PipSnapAlgorithm mSnapAlgorithm;
private final DisplayInfo mDisplayInfo = new DisplayInfo();
- private final Rect mTmpInsets = new Rect();
+ private final DisplayController mDisplayController;
+ private final DisplayLayout mDisplayLayout;
private ComponentName mLastPipComponentName;
private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
@@ -80,11 +81,24 @@
private boolean mIsShelfShowing;
private int mShelfHeight;
+ private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (displayId == mContext.getDisplayId()) {
+ mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId));
+ }
+ }
+ };
+
@Inject
- public PipBoundsHandler(Context context, PipSnapAlgorithm pipSnapAlgorithm) {
+ public PipBoundsHandler(Context context, PipSnapAlgorithm pipSnapAlgorithm,
+ DisplayController displayController) {
mContext = context;
mSnapAlgorithm = pipSnapAlgorithm;
- mWindowManager = WindowManagerGlobal.getWindowManagerService();
+ mDisplayLayout = new DisplayLayout();
+ mDisplayController = displayController;
+ mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
reloadResources();
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -272,8 +286,8 @@
*
* @return {@code true} if internal {@link DisplayInfo} is rotated, {@code false} otherwise.
*/
- public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, int displayId,
- int fromRotation, int toRotation, WindowContainerTransaction t) {
+ public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, Rect outInsetBounds,
+ int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) {
// Bail early if the event is not sent to current {@link #mDisplayInfo}
if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) {
return false;
@@ -294,6 +308,9 @@
final Rect postChangeStackBounds = new Rect(oldBounds);
final float snapFraction = getSnapFraction(postChangeStackBounds);
+ // Update the display layout
+ mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
+
// Populate the new {@link #mDisplayInfo}.
// The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
// therefore, the width/height may require a swap first.
@@ -308,6 +325,7 @@
mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
snapFraction);
+ getInsetBounds(outInsetBounds);
outBounds.set(postChangeStackBounds);
t.setBounds(pinnedStackInfo.stackToken, outBounds);
return true;
@@ -425,15 +443,11 @@
* Populates the bounds on the screen that the PIP can be visible in.
*/
protected void getInsetBounds(Rect outRect) {
- try {
- mWindowManager.getStableInsets(mContext.getDisplayId(), mTmpInsets);
- outRect.set(mTmpInsets.left + mScreenEdgeInsets.x,
- mTmpInsets.top + mScreenEdgeInsets.y,
- mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
- mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get stable insets from WM", e);
- }
+ Rect insets = mDisplayLayout.stableInsets();
+ outRect.set(insets.left + mScreenEdgeInsets.x,
+ insets.top + mScreenEdgeInsets.y,
+ mDisplayInfo.logicalWidth - insets.right - mScreenEdgeInsets.x,
+ mDisplayInfo.logicalHeight - insets.bottom - mScreenEdgeInsets.y);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 64df2ff..02bf745 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -95,8 +95,21 @@
private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mTmpNormalBounds,
- mPipTaskOrganizer.getLastReportedBounds(), displayId, fromRotation, toRotation, t);
+ mPipTaskOrganizer.getLastReportedBounds(), mTmpInsetBounds, displayId, fromRotation,
+ toRotation, t);
if (changed) {
+ // If the pip was in the offset zone earlier, adjust the new bounds to the bottom of the
+ // movement bounds
+ mTouchHandler.adjustBoundsForRotation(mTmpNormalBounds,
+ mPipTaskOrganizer.getLastReportedBounds(), mTmpInsetBounds);
+
+ // The bounds are being applied to a specific snap fraction, so reset any known offsets
+ // for the previous orientation before updating the movement bounds
+ mPipBoundsHandler.setShelfHeight(false , 0);
+ mPipBoundsHandler.onImeVisibilityChanged(false, 0);
+ mTouchHandler.onShelfVisibilityChanged(false, 0);
+ mTouchHandler.onImeVisibilityChanged(false, 0);
+
updateMovementBounds(mTmpNormalBounds, true /* fromRotation */,
false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
}
@@ -290,9 +303,10 @@
@Override
public void setShelfHeight(boolean visible, int height) {
mHandler.post(() -> {
- final boolean changed = mPipBoundsHandler.setShelfHeight(visible, height);
+ final int shelfHeight = visible ? height : 0;
+ final boolean changed = mPipBoundsHandler.setShelfHeight(visible, shelfHeight);
if (changed) {
- mTouchHandler.onShelfVisibilityChanged(visible, height);
+ mTouchHandler.onShelfVisibilityChanged(visible, shelfHeight);
updateMovementBounds(mPipTaskOrganizer.getLastReportedBounds(),
false /* fromRotation */, false /* fromImeAdjustment */,
true /* fromShelfAdjustment */);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index c408caa..c274ee9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -397,6 +397,15 @@
mShelfHeight = shelfHeight;
}
+ public void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
+ final Rect toMovementBounds = new Rect();
+ mSnapAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
+ final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
+ if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
+ outBounds.offsetTo(outBounds.left, toMovementBounds.bottom);
+ }
+ }
+
public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
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 fefad53..da31fe0 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
@@ -206,6 +206,13 @@
return false;
}
+ if (isSnoozedPackage(sbn)) {
+ if (DEBUG_HEADS_UP) {
+ Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
+ }
+ return false;
+ }
+
boolean inShade = mStatusBarStateController.getState() == SHADE;
if (entry.isBubble() && inShade) {
if (DEBUG_HEADS_UP) {
@@ -365,14 +372,6 @@
return false;
}
}
-
- if (isSnoozedPackage(sbn)) {
- if (DEBUG_HEADS_UP) {
- Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
- }
- return false;
- }
-
return 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 e3f25c6..b388887 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
@@ -797,24 +797,6 @@
}
@Test
- fun testListingCallbackNotListeningWhileReadingFavorites() {
- 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)
-
- val inOrder = inOrder(persistenceWrapper, listingController)
-
- inOrder.verify(listingController).removeCallback(listingCallbackCaptor.value)
- inOrder.verify(persistenceWrapper).readFavorites()
- inOrder.verify(listingController).addCallback(listingCallbackCaptor.value)
- }
-
- @Test
fun testSeedFavoritesForComponent() {
var succeeded = false
val control = statelessBuilderFromInfo(TEST_CONTROL_INFO, TEST_STRUCTURE_INFO.structure)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index 128a7e8..841b255 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -65,7 +65,10 @@
@Mock
private lateinit var serviceInfo: ServiceInfo
@Mock
- private lateinit var componentName: ComponentName
+ private lateinit var serviceInfo2: ServiceInfo
+
+ private var componentName = ComponentName("pkg1", "class1")
+ private var componentName2 = ComponentName("pkg2", "class2")
private val executor = FakeExecutor(FakeSystemClock())
@@ -82,6 +85,7 @@
MockitoAnnotations.initMocks(this)
`when`(serviceInfo.componentName).thenReturn(componentName)
+ `when`(serviceInfo2.componentName).thenReturn(componentName2)
val wrapper = object : ContextWrapper(mContext) {
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
@@ -179,7 +183,7 @@
}
@Test
- fun testChangeUserResetsExistingCallbackServices() {
+ fun testChangeUserSendsCorrectServiceUpdate() {
val list = listOf(serviceInfo)
controller.addCallback(mockCallback)
@@ -197,10 +201,21 @@
assertEquals(1, captor.value.size)
reset(mockCallback)
+ reset(mockSL)
+
+ val updatedList = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(updatedList)
controller.changeUser(UserHandle.of(otherUser))
executor.runAllReady()
assertEquals(otherUser, controller.currentUserId)
+ // this event should was triggered just before the user change, and should
+ // be ignored
+ verify(mockCallback, never()).onServicesUpdated(any())
+
+ serviceListingCallbackCaptor.value.onServicesReloaded(emptyList<ServiceInfo>())
+ executor.runAllReady()
+
verify(mockCallback).onServicesUpdated(capture(captor))
assertEquals(0, captor.value.size)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 8db57cd..487452b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -65,6 +65,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
+import com.android.systemui.settings.CurrentUserContextTracker;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -119,6 +120,7 @@
@Mock GlobalActionsPanelPlugin mWalletPlugin;
@Mock GlobalActionsPanelPlugin.PanelViewController mWalletController;
@Mock private Handler mHandler;
+ @Mock private CurrentUserContextTracker mCurrentUserContextTracker;
private TestableLooper mTestableLooper;
@@ -129,6 +131,7 @@
allowTestableLooperAsMainThread();
when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
+ when(mCurrentUserContextTracker.getCurrentUserContext()).thenReturn(mContext);
mGlobalActionsDialog = new GlobalActionsDialog(mContext,
mWindowManagerFuncs,
mAudioManager,
@@ -161,7 +164,8 @@
mUiEventLogger,
mRingerModeTracker,
mSysUiState,
- mHandler
+ mHandler,
+ mCurrentUserContextTracker
);
mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
index 425bf88..f404f04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import android.content.ComponentName;
import android.graphics.Rect;
@@ -32,6 +33,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.wm.DisplayController;
import org.junit.Before;
import org.junit.Test;
@@ -63,7 +65,8 @@
@Before
public void setUp() throws Exception {
initializeMockResources();
- mPipBoundsHandler = new PipBoundsHandler(mContext, new PipSnapAlgorithm(mContext));
+ mPipBoundsHandler = new PipBoundsHandler(mContext, new PipSnapAlgorithm(mContext),
+ mock(DisplayController.class));
mTestComponentName1 = new ComponentName(mContext, "component1");
mTestComponentName2 = new ComponentName(mContext, "component2");
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 5cbfcc1..e254cd2 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
@@ -117,21 +117,12 @@
/**
* Sets up the state such that any requests to
- * {@link NotificationInterruptStateProviderImpl#canAlertAwakeCommon(NotificationEntry)} will
- * pass as long its provided NotificationEntry fulfills launch fullscreen check.
- */
- private void ensureStateForAlertAwakeCommon() {
- when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
- }
-
- /**
- * Sets up the state such that any requests to
* {@link NotificationInterruptStateProviderImpl#shouldHeadsUp(NotificationEntry)} will
* pass as long its provided NotificationEntry fulfills importance & DND checks.
*/
private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
ensureStateForAlertCommon();
- ensureStateForAlertAwakeCommon();
+ when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
when(mStatusBarStateController.isDozing()).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
@@ -157,7 +148,6 @@
*/
private void ensureStateForBubbleUp() {
ensureStateForAlertCommon();
- ensureStateForAlertAwakeCommon();
}
@Test
@@ -392,7 +382,6 @@
@Test
public void testShouldNotHeadsUp_snoozedPackage() {
NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
- ensureStateForAlertAwakeCommon();
when(mHeadsUpManager.isSnoozed(entry.getSbn().getPackageName())).thenReturn(true);
@@ -402,7 +391,6 @@
@Test
public void testShouldNotHeadsUp_justLaunchedFullscreen() {
- ensureStateForAlertAwakeCommon();
// On screen alerts don't happen when that package has just launched fullscreen.
NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
index f5d6e5a..08d2664 100644
--- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
+++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
@@ -89,7 +89,7 @@
return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, minTargetSdk);
}
// Only allow to opt-in for a targetSdk gated change.
- if (disabled || applicationInfo.targetSdkVersion < minTargetSdk) {
+ if (disabled || appTargetSdk <= minTargetSdk) {
return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
}
return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, minTargetSdk);
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 34d0bed..3091a71 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -198,6 +198,9 @@
if (mPrefixDiscoveryRunning && !isPrefixDiscoveryNeeded()) {
stopPrefixDiscovery();
}
+ if (!mPrefixDiscoveryRunning) {
+ setPrefix64(mNat64PrefixInUse);
+ }
}
/**
@@ -221,6 +224,10 @@
mIface = null;
mBaseIface = null;
+ if (!mPrefixDiscoveryRunning) {
+ setPrefix64(null);
+ }
+
if (isPrefixDiscoveryNeeded()) {
if (!mPrefixDiscoveryRunning) {
startPrefixDiscovery();
@@ -308,6 +315,16 @@
return requiresClat(mNetwork) && mNat64PrefixFromRa == null;
}
+ private void setPrefix64(IpPrefix prefix) {
+ final String prefixString = (prefix != null) ? prefix.toString() : "";
+ try {
+ mDnsResolver.setPrefix64(getNetId(), prefixString);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Slog.e(TAG, "Error setting NAT64 prefix on netId " + getNetId() + " to "
+ + prefix + ": " + e);
+ }
+ }
+
private void maybeHandleNat64PrefixChange() {
final IpPrefix newPrefix = selectNat64Prefix();
if (!Objects.equals(mNat64PrefixInUse, newPrefix)) {
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 910ed44..19fa920 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -30,6 +30,7 @@
import com.android.server.FgThread;
+import java.io.File;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@@ -124,9 +125,13 @@
}
}
- String getIdmapPath(String overlayPath, int userId) throws TimeoutException, RemoteException {
+ boolean idmapExists(String overlayPath, int userId) {
try (Connection c = connect()) {
- return mService.getIdmapPath(overlayPath, userId);
+ return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
+ } catch (Exception e) {
+ Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath + ": "
+ + e.getMessage());
+ return false;
}
}
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 90c85ad..735d669 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -27,10 +27,10 @@
import android.os.Build.VERSION_CODES;
import android.os.OverlayablePolicy;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.util.Slog;
-import java.io.File;
+import com.android.internal.util.ArrayUtils;
+
import java.io.IOException;
/**
@@ -55,12 +55,16 @@
VENDOR_IS_Q_OR_LATER = isQOrLater;
}
- private final OverlayableInfoCallback mOverlayableCallback;
private final IdmapDaemon mIdmapDaemon;
+ private final OverlayableInfoCallback mOverlayableCallback;
+ private final String mOverlayableConfigurator;
+ private final String[] mOverlayableConfiguratorTargets;
- IdmapManager(final OverlayableInfoCallback verifyCallback) {
+ IdmapManager(final IdmapDaemon idmapDaemon, final OverlayableInfoCallback verifyCallback) {
mOverlayableCallback = verifyCallback;
- mIdmapDaemon = IdmapDaemon.getInstance();
+ mIdmapDaemon = idmapDaemon;
+ mOverlayableConfigurator = verifyCallback.getOverlayableConfigurator();
+ mOverlayableConfiguratorTargets = verifyCallback.getOverlayableConfiguratorTargets() ;
}
/**
@@ -103,23 +107,11 @@
}
boolean idmapExists(@NonNull final OverlayInfo oi) {
- return new File(getIdmapPath(oi.baseCodePath, oi.userId)).isFile();
+ return mIdmapDaemon.idmapExists(oi.baseCodePath, oi.userId);
}
boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
- return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath(), userId))
- .isFile();
- }
-
- private @NonNull String getIdmapPath(@NonNull final String overlayPackagePath,
- final int userId) {
- try {
- return mIdmapDaemon.getIdmapPath(overlayPackagePath, userId);
- } catch (Exception e) {
- Slog.w(TAG, "failed to get idmap path for " + overlayPackagePath + ": "
- + e.getMessage());
- return "";
- }
+ return mIdmapDaemon.idmapExists(overlayPackage.applicationInfo.getBaseCodePath(), userId);
}
/**
@@ -198,9 +190,17 @@
String targetOverlayableName = overlayPackage.targetOverlayableName;
if (targetOverlayableName != null) {
try {
+ if (!mOverlayableConfigurator.isEmpty()
+ && ArrayUtils.contains(mOverlayableConfiguratorTargets,
+ targetPackage.packageName)
+ && mOverlayableCallback.signaturesMatching(mOverlayableConfigurator,
+ overlayPackage.packageName, userId)) {
+ return true;
+ }
+
OverlayableInfo overlayableInfo = mOverlayableCallback.getOverlayableForTarget(
targetPackage.packageName, targetOverlayableName, userId);
- if (overlayableInfo != null) {
+ if (overlayableInfo != null && overlayableInfo.actor != null) {
String actorPackageName = OverlayActorEnforcer.getPackageNameForActor(
overlayableInfo.actor, mOverlayableCallback.getNamedActors()).first;
if (mOverlayableCallback.signaturesMatching(actorPackageName,
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index ef6655d..2bc3499 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -50,8 +50,8 @@
/**
* @return nullable actor result with {@link ActorState} failure status
*/
- static Pair<String, ActorState> getPackageNameForActor(String actorUriString,
- Map<String, Map<String, String>> namedActors) {
+ static Pair<String, ActorState> getPackageNameForActor(@NonNull String actorUriString,
+ @NonNull Map<String, Map<String, String>> namedActors) {
Uri actorUri = Uri.parse(actorUriString);
String actorScheme = actorUri.getScheme();
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 6a8e465..086ab81 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -45,6 +45,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.res.ApkAssets;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
@@ -62,6 +63,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.R;
import com.android.internal.content.om.OverlayConfig;
import com.android.server.FgThread;
import com.android.server.IoThread;
@@ -246,7 +248,7 @@
new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays");
mPackageManager = new PackageManagerHelperImpl(context);
mUserManager = UserManagerService.getInstance();
- IdmapManager im = new IdmapManager(mPackageManager);
+ IdmapManager im = new IdmapManager(IdmapDaemon.getInstance(), mPackageManager);
mSettings = new OverlayManagerSettings();
mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
OverlayConfig.getSystemInstance(), getDefaultOverlayPackages(),
@@ -1119,6 +1121,17 @@
}
@Override
+ public String getOverlayableConfigurator() {
+ return Resources.getSystem().getString(R.string.config_overlayableConfigurator);
+ }
+
+ @Override
+ public String[] getOverlayableConfiguratorTargets() {
+ return Resources.getSystem().getStringArray(
+ R.array.config_overlayableConfiguratorTargets);
+ }
+
+ @Override
public List<PackageInfo> getOverlayPackages(final int userId) {
final List<PackageInfo> overlays = mPackageManagerInternal.getOverlayPackages(userId);
for (final PackageInfo info : overlays) {
diff --git a/services/core/java/com/android/server/om/OverlayableInfoCallback.java b/services/core/java/com/android/server/om/OverlayableInfoCallback.java
index 5066ecd..41c341a 100644
--- a/services/core/java/com/android/server/om/OverlayableInfoCallback.java
+++ b/services/core/java/com/android/server/om/OverlayableInfoCallback.java
@@ -80,4 +80,24 @@
* in the system returns {@link PackageManager#SIGNATURE_MATCH}
*/
boolean signaturesMatching(@NonNull String pkgName1, @NonNull String pkgName2, int userId);
+
+ /**
+ * Retrieves the package name that is recognized as an actor for the packages specified by
+ * {@link #getOverlayableConfiguratorTargets()}.
+ */
+ @NonNull
+ default String getOverlayableConfigurator() {
+ return "";
+ }
+
+ /**
+ * Retrieves the target packages that recognize the {@link #getOverlayableConfigurator} as an
+ * actor for its overlayable declarations. Overlays targeting one of the specified targets that
+ * are signed with the same signature as the overlayable configurator will be granted the
+ * "actor" policy.
+ */
+ @NonNull
+ default String[] getOverlayableConfiguratorTargets() {
+ return new String[0];
+ }
}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 830388d..4b6ee71 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -446,11 +446,6 @@
}
}
- if (!newPkgSetting.pkg.getProtectedBroadcasts().isEmpty()) {
- mProtectedBroadcasts.addAll(newPkgSetting.pkg.getProtectedBroadcasts());
- recomputeComponentVisibility(existingSettings, newPkgSetting.pkg.getPackageName());
- }
-
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage");
try {
final AndroidPackage newPkg = newPkgSetting.pkg;
@@ -459,6 +454,11 @@
return;
}
+ if (!newPkg.getProtectedBroadcasts().isEmpty()) {
+ mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts());
+ recomputeComponentVisibility(existingSettings, newPkg.getPackageName());
+ }
+
final boolean newIsForceQueryable =
mForceQueryable.contains(newPkgSetting.appId)
/* shared user that is already force queryable */
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 395e835..fbe8100 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -16267,10 +16267,16 @@
// signing certificate than the existing one, and if so, copy over the new
// details
if (signatureCheckPs.sharedUser != null) {
- if (parsedPackage.getSigningDetails().hasAncestor(
- signatureCheckPs.sharedUser.signatures.mSigningDetails)) {
- signatureCheckPs.sharedUser.signatures.mSigningDetails =
- parsedPackage.getSigningDetails();
+ // Attempt to merge the existing lineage for the shared SigningDetails with
+ // the lineage of the new package; if the shared SigningDetails are not
+ // returned this indicates the new package added new signers to the lineage
+ // and/or changed the capabilities of existing signers in the lineage.
+ SigningDetails sharedSigningDetails =
+ signatureCheckPs.sharedUser.signatures.mSigningDetails;
+ SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
+ signingDetails);
+ if (mergedDetails != sharedSigningDetails) {
+ signatureCheckPs.sharedUser.signatures.mSigningDetails = mergedDetails;
}
if (signatureCheckPs.sharedUser.signaturesChanged == null) {
signatureCheckPs.sharedUser.signaturesChanged = Boolean.FALSE;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 5c175a6..1fce07b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -611,7 +611,6 @@
final String packageName = pkgSetting.name;
boolean compatMatch = false;
if (pkgSetting.signatures.mSigningDetails.signatures != null) {
-
// Already existing package. Make sure signatures match
boolean match = parsedSignatures.checkCapability(
pkgSetting.signatures.mSigningDetails,
@@ -664,6 +663,13 @@
|| pkgSetting.getSharedUser().signatures.mSigningDetails.checkCapability(
parsedSignatures,
PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID);
+ // Special case: if the sharedUserId capability check failed it could be due to this
+ // being the only package in the sharedUserId so far and the lineage being updated to
+ // deny the sharedUserId capability of the previous key in the lineage.
+ if (!match && pkgSetting.getSharedUser().packages.size() == 1
+ && pkgSetting.getSharedUser().packages.valueAt(0).name.equals(packageName)) {
+ match = true;
+ }
if (!match && compareCompat) {
match = matchSignaturesCompat(
packageName, pkgSetting.getSharedUser().signatures, parsedSignatures);
@@ -686,6 +692,42 @@
+ " has no signatures that match those in shared user "
+ pkgSetting.getSharedUser().name + "; ignoring!");
}
+ // It is possible that this package contains a lineage that blocks sharedUserId access
+ // to an already installed package in the sharedUserId signed with a previous key.
+ // Iterate over all of the packages in the sharedUserId and ensure any that are signed
+ // with a key in this package's lineage have the SHARED_USER_ID capability granted.
+ if (parsedSignatures.hasPastSigningCertificates()) {
+ for (PackageSetting shUidPkgSetting : pkgSetting.getSharedUser().packages) {
+ // if the current package in the sharedUserId is the package being updated then
+ // skip this check as the update may revoke the sharedUserId capability from
+ // the key with which this app was previously signed.
+ if (packageName.equals(shUidPkgSetting.name)) {
+ continue;
+ }
+ PackageParser.SigningDetails shUidSigningDetails =
+ shUidPkgSetting.getSigningDetails();
+ // The capability check only needs to be performed against the package if it is
+ // signed with a key that is in the lineage of the package being installed.
+ if (parsedSignatures.hasAncestor(shUidSigningDetails)) {
+ if (!parsedSignatures.checkCapability(shUidSigningDetails,
+ PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)) {
+ throw new PackageManagerException(
+ INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+ "Package " + packageName
+ + " revoked the sharedUserId capability from the "
+ + "signing key used to sign " + shUidPkgSetting.name);
+ }
+ }
+ }
+ }
+ // If the lineage of this package diverges from the lineage of the sharedUserId then
+ // do not allow the installation to proceed.
+ if (!parsedSignatures.hasCommonAncestor(
+ pkgSetting.getSharedUser().signatures.mSigningDetails)) {
+ throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+ "Package " + packageName + " has a signing lineage "
+ + "that diverges from the lineage of the sharedUserId");
+ }
}
return compatMatch;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b5b82d3..5a6e0a1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6535,14 +6535,14 @@
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
final int requestedOrientation = getRequestedConfigurationOrientation();
- final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
+ final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED
+ && !mDisplayContent.ignoreRotationForApps();
final int orientation = orientationRequested
? requestedOrientation
: newParentConfiguration.orientation;
int rotation = newParentConfiguration.windowConfiguration.getRotation();
final boolean canChangeOrientation = handlesOrientationChangeFromDescendant();
- if (canChangeOrientation && mCompatDisplayInsets.mIsRotatable
- && !mCompatDisplayInsets.mIsFloating) {
+ if (canChangeOrientation && !mCompatDisplayInsets.mIsFloating) {
// Use parent rotation because the original display can rotate by requested orientation.
resolvedConfig.windowConfiguration.setRotation(rotation);
} else {
@@ -7628,7 +7628,6 @@
private final int mWidth;
private final int mHeight;
final boolean mIsFloating;
- final boolean mIsRotatable;
/**
* The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
@@ -7645,7 +7644,6 @@
/** Constructs the environment to simulate the bounds behavior of the given container. */
CompatDisplayInsets(DisplayContent display, WindowContainer container) {
mIsFloating = container.getWindowConfiguration().tasksAreFloating();
- mIsRotatable = !mIsFloating && !display.ignoreRotationForApps();
if (mIsFloating) {
final Rect containerBounds = container.getWindowConfiguration().getBounds();
mWidth = containerBounds.width();
@@ -7702,7 +7700,7 @@
return;
}
- if (mIsRotatable && canChangeOrientation) {
+ if (canChangeOrientation) {
getBoundsByRotation(outBounds, rotation);
if (orientationRequested) {
getFrameByOrientation(outAppBounds, orientation);
diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
index 425c724..d45589d 100644
--- a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
@@ -288,7 +288,8 @@
public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptin_allowOverride()
throws Exception {
CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
- .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 1).build();
+ .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 1)
+ .addTargetSdkChangeWithId(TARGET_SDK, 2).build();
IOverrideValidator overrideValidator = config.getOverrideValidator();
when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
.thenReturn(ApplicationInfoBuilder.create()
@@ -296,19 +297,23 @@
.withTargetSdk(TARGET_SDK)
.withPackageName(PACKAGE_NAME).build());
- OverrideAllowedState allowedState =
+ OverrideAllowedState stateTargetSdkGreaterChange =
overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkEqualChange =
+ overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
- assertThat(allowedState)
+ assertThat(stateTargetSdkGreaterChange)
.isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER));
+
+ assertThat(stateTargetSdkEqualChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK));
}
@Test
public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptout_rejectOverride()
throws Exception {
CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
- .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
- .addTargetSdkChangeWithId(TARGET_SDK, 2).build();
+ .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1).build();
IOverrideValidator overrideValidator = config.getOverrideValidator();
when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
.thenReturn(ApplicationInfoBuilder.create()
@@ -319,14 +324,10 @@
OverrideAllowedState stateTargetSdkLessChange =
overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
- OverrideAllowedState stateTargetSdkEqualChange =
- overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
assertThat(stateTargetSdkLessChange).isEqualTo(
new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK,
TARGET_SDK_BEFORE));
- assertThat(stateTargetSdkEqualChange).isEqualTo(
- new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK, TARGET_SDK));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index f35eecf..b7692f9 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -44,9 +44,9 @@
@Test
public void testUpdateOverlaysForUser() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
- installTargetPackage("some.other.target", USER);
- installOverlayPackage(OVERLAY, TARGET, USER);
+ addSystemPackage(target(TARGET), USER);
+ addSystemPackage(target("some.other.target"), USER);;
+ addSystemPackage(overlay(OVERLAY, TARGET), USER);
// do nothing, expect no change
final List<String> a = impl.updateOverlaysForUser(USER);
@@ -54,8 +54,7 @@
assertTrue(a.contains(TARGET));
// upgrade overlay, keep target
- beginUpgradeOverlayPackage(OVERLAY, USER);
- endUpgradeOverlayPackage(OVERLAY, TARGET, USER);
+ addSystemPackage(overlay(OVERLAY, TARGET), USER);
final List<String> b = impl.updateOverlaysForUser(USER);
assertEquals(1, b.size());
@@ -67,7 +66,7 @@
assertTrue(c.contains(TARGET));
// upgrade overlay, switch to new target
- addOverlayPackage(OVERLAY, "some.other.target", USER, true, false, 0);
+ addSystemPackage(overlay(OVERLAY, "some.other.target"), USER);
final List<String> d = impl.updateOverlaysForUser(USER);
assertEquals(2, d.size());
assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
@@ -81,23 +80,24 @@
@Test
public void testImmutableEnabledChange() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
- addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0);
+ configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertFalse(o1.isEnabled());
assertFalse(o1.isMutable);
- addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0);
+ configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o2);
assertTrue(o2.isEnabled());
assertFalse(o2.isMutable);
- addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0);
+ configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
@@ -108,23 +108,24 @@
@Test
public void testMutableEnabledChangeHasNoEffect() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
- addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertFalse(o1.isEnabled());
assertTrue(o1.isMutable);
- addOverlayPackage(OVERLAY, TARGET, USER, true, true, 0);
+ configureSystemOverlay(OVERLAY, true /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o2);
assertFalse(o2.isEnabled());
assertTrue(o2.isMutable);
- addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0);
+ configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
@@ -135,15 +136,16 @@
@Test
public void testMutableEnabledToImmutableEnabled() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
final BiConsumer<Boolean, Boolean> setOverlay = (mutable, enabled) -> {
- addOverlayPackage(OVERLAY, TARGET, USER, mutable, enabled, 0);
+ configureSystemOverlay(OVERLAY, mutable, enabled, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
- assertNotNull(o1);
- assertEquals(enabled, o1.isEnabled());
- assertEquals(mutable, o1.isMutable);
+ final OverlayInfo o = impl.getOverlayInfo(OVERLAY, USER);
+ assertNotNull(o);
+ assertEquals(enabled, o.isEnabled());
+ assertEquals(mutable, o.isMutable);
};
// Immutable/enabled -> mutable/enabled
@@ -178,70 +180,76 @@
@Test
public void testMutablePriorityChange() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
- addOverlayPackage(OVERLAY, TARGET, USER, true, true, 0);
- addOverlayPackage(OVERLAY2, TARGET, USER, true, true, 1);
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
+ configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 1 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertEquals(0, o1.priority);
+ assertFalse(o1.isEnabled());
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o2);
assertEquals(1, o2.priority);
+ assertFalse(o2.isEnabled());
// Overlay priority changing between reboots should not affect enable state of mutable
- // overlays
+ // overlays.
impl.setEnabled(OVERLAY, true, USER);
// Reorder the overlays
- addOverlayPackage(OVERLAY, TARGET, USER, true, true, 1);
- addOverlayPackage(OVERLAY2, TARGET, USER, true, true, 0);
+ configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 1 /* priority */);
+ configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
assertEquals(1, o3.priority);
+ assertTrue(o3.isEnabled());
final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o4);
assertEquals(0, o4.priority);
- assertTrue(o1.isEnabled());
+ assertFalse(o4.isEnabled());
}
@Test
public void testImmutablePriorityChange() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
- addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0);
- addOverlayPackage(OVERLAY2, TARGET, USER, false, true, 1);
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
+ configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 1 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertEquals(0, o1.priority);
+ assertTrue(o1.isEnabled());
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o2);
assertEquals(1, o2.priority);
-
- // Overlay priority changing between reboots should not affect enable state of mutable
- // overlays
- impl.setEnabled(OVERLAY, true, USER);
+ assertTrue(o2.isEnabled());
// Reorder the overlays
- addOverlayPackage(OVERLAY, TARGET, USER, false, true, 1);
- addOverlayPackage(OVERLAY2, TARGET, USER, false, true, 0);
+ configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 1 /* priority */);
+ configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
assertEquals(1, o3.priority);
+ assertTrue(o3.isEnabled());
final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o4);
assertEquals(0, o4.priority);
- assertTrue(o1.isEnabled());
+ assertTrue(o4.isEnabled());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index cd73432..b25af05 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertTrue;
import android.content.om.OverlayInfo;
+import android.os.OverlayablePolicy;
import androidx.test.runner.AndroidJUnit4;
@@ -49,11 +50,9 @@
private static final String OVERLAY3 = OVERLAY + "3";
private static final int USER3 = USER2 + 1;
- // tests: basics
-
@Test
- public void testGetOverlayInfo() throws Exception {
- installOverlayPackage(OVERLAY, TARGET, USER);
+ public void testGetOverlayInfo() {
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
final OverlayManagerServiceImpl impl = getImpl();
final OverlayInfo oi = impl.getOverlayInfo(OVERLAY, USER);
@@ -64,10 +63,10 @@
}
@Test
- public void testGetOverlayInfosForTarget() throws Exception {
- installOverlayPackage(OVERLAY, TARGET, USER);
- installOverlayPackage(OVERLAY2, TARGET, USER);
- installOverlayPackage(OVERLAY3, TARGET, USER2);
+ public void testGetOverlayInfosForTarget() {
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ installNewPackage(overlay(OVERLAY3, TARGET), USER2);
final OverlayManagerServiceImpl impl = getImpl();
final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
@@ -89,11 +88,11 @@
}
@Test
- public void testGetOverlayInfosForUser() throws Exception {
- installTargetPackage(TARGET, USER);
- installOverlayPackage(OVERLAY, TARGET, USER);
- installOverlayPackage(OVERLAY2, TARGET, USER);
- installOverlayPackage(OVERLAY3, TARGET2, USER);
+ public void testGetOverlayInfosForUser() {
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ installNewPackage(overlay(OVERLAY3, TARGET2), USER);
final OverlayManagerServiceImpl impl = getImpl();
final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
@@ -116,91 +115,86 @@
}
@Test
- public void testPriority() throws Exception {
- installOverlayPackage(OVERLAY, TARGET, USER);
- installOverlayPackage(OVERLAY2, TARGET, USER);
- installOverlayPackage(OVERLAY3, TARGET, USER);
+ public void testPriority() {
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ installNewPackage(overlay(OVERLAY3, TARGET), USER);
final OverlayManagerServiceImpl impl = getImpl();
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY3, USER);
- assertOverlayInfoList(TARGET, USER, o1, o2, o3);
+ assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
assertTrue(impl.setLowestPriority(OVERLAY3, USER));
- assertOverlayInfoList(TARGET, USER, o3, o1, o2);
+ assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
assertTrue(impl.setHighestPriority(OVERLAY3, USER));
- assertOverlayInfoList(TARGET, USER, o1, o2, o3);
+ assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
assertTrue(impl.setPriority(OVERLAY, OVERLAY2, USER));
- assertOverlayInfoList(TARGET, USER, o2, o1, o3);
+ assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
}
@Test
- public void testOverlayInfoStateTransitions() throws Exception {
+ public void testOverlayInfoStateTransitions() {
final OverlayManagerServiceImpl impl = getImpl();
assertNull(impl.getOverlayInfo(OVERLAY, USER));
- installOverlayPackage(OVERLAY, TARGET, USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
assertState(STATE_MISSING_TARGET, OVERLAY, USER);
- installTargetPackage(TARGET, USER);
+ final DummyDeviceState.PackageBuilder target = target(TARGET);
+ installNewPackage(target, USER);
assertState(STATE_DISABLED, OVERLAY, USER);
impl.setEnabled(OVERLAY, true, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
// target upgrades do not change the state of the overlay
- beginUpgradeTargetPackage(TARGET, USER);
+ upgradePackage(target, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
- endUpgradeTargetPackage(TARGET, USER);
- assertState(STATE_ENABLED, OVERLAY, USER);
-
- uninstallTargetPackage(TARGET, USER);
+ uninstallPackage(TARGET, USER);
assertState(STATE_MISSING_TARGET, OVERLAY, USER);
- installTargetPackage(TARGET, USER);
+ installNewPackage(target, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
}
@Test
- public void testOnOverlayPackageUpgraded() throws Exception {
- final OverlayManagerServiceImpl impl = getImpl();
+ public void testOnOverlayPackageUpgraded() {
final DummyListener listener = getListener();
- installTargetPackage(TARGET, USER);
- installOverlayPackage(OVERLAY, TARGET, USER);
- impl.onOverlayPackageReplacing(OVERLAY, USER);
+ final DummyDeviceState.PackageBuilder target = target(TARGET);
+ final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET);
+ installNewPackage(target, USER);
+ installNewPackage(overlay, USER);
listener.count = 0;
- impl.onOverlayPackageReplaced(OVERLAY, USER);
- assertEquals(1, listener.count);
-
- // upgrade to a version where the overlay has changed its target
- beginUpgradeOverlayPackage(OVERLAY, USER);
- listener.count = 0;
- endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER);
- // expect once for the old target package, once for the new target package
+ upgradePackage(overlay, USER);
assertEquals(2, listener.count);
- beginUpgradeOverlayPackage(OVERLAY, USER);
+ // upgrade to a version where the overlay has changed its target
+ // expect once for the old target package, once for the new target package
listener.count = 0;
- endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER);
- assertEquals(1, listener.count);
+ final DummyDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target");
+ upgradePackage(overlay2, USER);
+ assertEquals(3, listener.count);
+
+ listener.count = 0;
+ upgradePackage(overlay2, USER);
+ assertEquals(2, listener.count);
}
- // tests: listener interface
-
@Test
- public void testListener() throws Exception {
+ public void testListener() {
final OverlayManagerServiceImpl impl = getImpl();
final DummyListener listener = getListener();
- installOverlayPackage(OVERLAY, TARGET, USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
assertEquals(1, listener.count);
listener.count = 0;
- installTargetPackage(TARGET, USER);
+ installNewPackage(target(TARGET), USER);
assertEquals(1, listener.count);
listener.count = 0;
@@ -211,4 +205,49 @@
impl.setEnabled(OVERLAY, true, USER);
assertEquals(0, listener.count);
}
+
+ @Test
+ public void testConfigurator() {
+ final DummyPackageManagerHelper packageManager = getPackageManager();
+ packageManager.overlayableConfigurator = "actor";
+ packageManager.overlayableConfiguratorTargets = new String[]{TARGET};
+ reinitializeImpl();
+
+ installNewPackage(target("actor").setCertificate("one"), USER);
+ installNewPackage(target(TARGET)
+ .addOverlayable("TestResources")
+ .setCertificate("two"), USER);
+
+ final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources")
+ .setCertificate("one");
+ installNewPackage(overlay, USER);
+
+ final DummyIdmapDaemon idmapDaemon = getIdmapDaemon();
+ final DummyIdmapDaemon.IdmapHeader idmap = idmapDaemon.getIdmap(overlay.build().apkPath);
+ assertNotNull(idmap);
+ assertEquals(OverlayablePolicy.ACTOR_SIGNATURE,
+ idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
+ }
+
+ @Test
+ public void testConfiguratorDifferentSignatures() {
+ final DummyPackageManagerHelper packageManager = getPackageManager();
+ packageManager.overlayableConfigurator = "actor";
+ packageManager.overlayableConfiguratorTargets = new String[]{TARGET};
+ reinitializeImpl();
+
+ installNewPackage(target("actor").setCertificate("one"), USER);
+ installNewPackage(target(TARGET)
+ .addOverlayable("TestResources")
+ .setCertificate("two"), USER);
+
+ final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources")
+ .setCertificate("two");
+ installNewPackage(overlay, USER);
+
+ final DummyIdmapDaemon idmapDaemon = getIdmapDaemon();
+ final DummyIdmapDaemon.IdmapHeader idmap = idmapDaemon.getIdmap(overlay.build().apkPath);
+ assertNotNull(idmap);
+ assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 9eda718..ec6a481 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -17,6 +17,7 @@
package com.android.server.om;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -26,17 +27,19 @@
import android.content.om.OverlayableInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.util.ArrayMap;
import android.util.ArraySet;
import androidx.annotation.Nullable;
import com.android.internal.content.om.OverlayConfig;
+import org.junit.Assert;
import org.junit.Before;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Iterator;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -47,29 +50,48 @@
private OverlayManagerServiceImpl mImpl;
private DummyDeviceState mState;
private DummyListener mListener;
+ private DummyPackageManagerHelper mPackageManager;
+ private DummyIdmapDaemon mIdmapDaemon;
+ private OverlayConfig mOverlayConfig;
@Before
public void setUp() {
mState = new DummyDeviceState();
mListener = new DummyListener();
- final DummyPackageManagerHelper pmh = new DummyPackageManagerHelper(mState);
+ mPackageManager = new DummyPackageManagerHelper(mState);
+ mIdmapDaemon = new DummyIdmapDaemon(mState);
+ mOverlayConfig = mock(OverlayConfig.class);
+ when(mOverlayConfig.getPriority(any())).thenReturn(OverlayConfig.DEFAULT_PRIORITY);
+ when(mOverlayConfig.isEnabled(any())).thenReturn(false);
+ when(mOverlayConfig.isMutable(any())).thenReturn(true);
+ reinitializeImpl();
+ }
- mImpl = new OverlayManagerServiceImpl(pmh,
- new DummyIdmapManager(mState, pmh),
+ void reinitializeImpl() {
+ mImpl = new OverlayManagerServiceImpl(mPackageManager,
+ new IdmapManager(mIdmapDaemon, mPackageManager),
new OverlayManagerSettings(),
- mState.mOverlayConfig,
+ mOverlayConfig,
new String[0],
mListener);
}
- public OverlayManagerServiceImpl getImpl() {
+ OverlayManagerServiceImpl getImpl() {
return mImpl;
}
- public DummyListener getListener() {
+ DummyListener getListener() {
return mListener;
}
+ DummyPackageManagerHelper getPackageManager() {
+ return mPackageManager;
+ }
+
+ DummyIdmapDaemon getIdmapDaemon() {
+ return mIdmapDaemon;
+ }
+
void assertState(@State int expected, final String overlayPackageName, int userId) {
final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId);
if (info == null) {
@@ -81,7 +103,7 @@
assertEquals(msg, expected, info.state);
}
- void assertOverlayInfoList(final String targetPackageName, int userId,
+ void assertOverlayInfoForTarget(final String targetPackageName, int userId,
OverlayInfo... overlayInfos) {
final List<OverlayInfo> expected =
mImpl.getOverlayInfosForTarget(targetPackageName, userId);
@@ -89,198 +111,202 @@
assertEquals(expected, actual);
}
- /**
- * Creates an overlay configured through {@link OverlayConfig}.
- *
- * @throws IllegalStateException if the package is already installed
- */
- void addOverlayPackage(String packageName, String targetPackageName, int userId,
- boolean mutable, boolean enabled, int priority) {
- mState.addOverlay(packageName, targetPackageName, userId, mutable, enabled, priority);
+ DummyDeviceState.PackageBuilder target(String packageName) {
+ return new DummyDeviceState.PackageBuilder(packageName, null /* targetPackageName */,
+ null /* targetOverlayableName */);
+ }
+
+ DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName) {
+ return overlay(packageName, targetPackageName, null /* targetOverlayableName */);
+ }
+
+ DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName,
+ String targetOverlayableName) {
+ return new DummyDeviceState.PackageBuilder(packageName, targetPackageName,
+ targetOverlayableName);
+ }
+
+ void addSystemPackage(DummyDeviceState.PackageBuilder pkg, int userId) {
+ mState.add(pkg, userId);
+ }
+
+ void configureSystemOverlay(String packageName, boolean mutable, boolean enabled,
+ int priority) {
+ when(mOverlayConfig.getPriority(packageName)).thenReturn(priority);
+ when(mOverlayConfig.isEnabled(packageName)).thenReturn(enabled);
+ when(mOverlayConfig.isMutable(packageName)).thenReturn(mutable);
}
/**
- * Adds the target package to the device.
+ * Adds the package to the device.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
*
- * @throws IllegalStateException if the package is not currently installed
+ * @throws IllegalStateException if the package is currently installed
*/
- void installTargetPackage(String packageName, int userId) {
- if (mState.select(packageName, userId) != null) {
- throw new IllegalStateException("package already installed");
+ void installNewPackage(DummyDeviceState.PackageBuilder pkg, int userId) {
+ if (mState.select(pkg.packageName, userId) != null) {
+ throw new IllegalStateException("package " + pkg.packageName + " already installed");
}
- mState.addTarget(packageName, userId);
- mImpl.onTargetPackageAdded(packageName, userId);
+ mState.add(pkg, userId);
+ if (pkg.targetPackage == null) {
+ mImpl.onTargetPackageAdded(pkg.packageName, userId);
+ } else {
+ mImpl.onOverlayPackageAdded(pkg.packageName, userId);
+ }
}
/**
- * Begins upgrading the target package.
+ * Begins upgrading the package.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
- * {@link android.content.Intent#EXTRA_REPLACING} extra.
- *
- * @throws IllegalStateException if the package is not currently installed
- */
- void beginUpgradeTargetPackage(String packageName, int userId) {
- if (mState.select(packageName, userId) == null) {
- throw new IllegalStateException("package not installed");
- }
- mImpl.onTargetPackageReplacing(packageName, userId);
- }
-
- /**
- * Ends upgrading the target package.
- *
- * This corresponds to when the OMS receives the
+ * {@link android.content.Intent#EXTRA_REPLACING} extra and then receives the
* {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
* {@link android.content.Intent#EXTRA_REPLACING} extra.
*
* @throws IllegalStateException if the package is not currently installed
*/
- void endUpgradeTargetPackage(String packageName, int userId) {
- if (mState.select(packageName, userId) == null) {
- throw new IllegalStateException("package not installed");
+ void upgradePackage(DummyDeviceState.PackageBuilder pkg, int userId) {
+ final DummyDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
+ if (replacedPackage == null) {
+ throw new IllegalStateException("package " + pkg.packageName + " not installed");
}
- mState.addTarget(packageName, userId);
- mImpl.onTargetPackageReplaced(packageName, userId);
+ if (replacedPackage.targetPackageName != null) {
+ mImpl.onOverlayPackageReplacing(pkg.packageName, userId);
+ }
+
+ mState.add(pkg, userId);
+ if (pkg.targetPackage == null) {
+ mImpl.onTargetPackageReplaced(pkg.packageName, userId);
+ } else {
+ mImpl.onOverlayPackageReplaced(pkg.packageName, userId);
+ }
}
/**
- * Removes the target package from the device.
+ * Removes the package from the device.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast.
*
* @throws IllegalStateException if the package is not currently installed
*/
- void uninstallTargetPackage(String packageName, int userId) {
- if (mState.select(packageName, userId) == null) {
- throw new IllegalStateException("package not installed");
+ void uninstallPackage(String packageName, int userId) {
+ final DummyDeviceState.Package pkg = mState.select(packageName, userId);
+ if (pkg == null) {
+ throw new IllegalStateException("package " + packageName+ " not installed");
}
- mState.remove(packageName, userId);
- mImpl.onTargetPackageRemoved(packageName, userId);
+ mState.remove(pkg.packageName);
+ if (pkg.targetPackageName == null) {
+ mImpl.onTargetPackageRemoved(pkg.packageName, userId);
+ } else {
+ mImpl.onOverlayPackageRemoved(pkg.packageName, userId);
+ }
}
- /**
- * Adds the overlay package to the device.
- *
- * This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
- *
- * @throws IllegalStateException if the package is already installed
- */
- void installOverlayPackage(String packageName, String targetPackageName, int userId) {
- if (mState.select(packageName, userId) != null) {
- throw new IllegalStateException("package already installed");
- }
- mState.addOverlay(packageName, targetPackageName, userId);
- mImpl.onOverlayPackageAdded(packageName, userId);
- }
+ /** Represents the state of packages installed on a fake device. */
+ static class DummyDeviceState {
+ private ArrayMap<String, Package> mPackages = new ArrayMap<>();
- /**
- * Begins upgrading the overlay package.
- *
- * This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
- * {@link android.content.Intent#EXTRA_REPLACING} extra.
- *
- * @throws IllegalStateException if the package is not currently installed
- */
- void beginUpgradeOverlayPackage(String packageName, int userId) {
- if (mState.select(packageName, userId) == null) {
- throw new IllegalStateException("package not installed, cannot upgrade");
- }
+ void add(PackageBuilder pkgBuilder, int userId) {
+ final Package pkg = pkgBuilder.build();
+ final Package previousPkg = select(pkg.packageName, userId);
+ mPackages.put(pkg.packageName, pkg);
- mImpl.onOverlayPackageReplacing(packageName, userId);
- }
-
- /**
- * Ends upgrading the overlay package, potentially changing its target package.
- *
- * This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
- * {@link android.content.Intent#EXTRA_REPLACING} extra.
- *
- * @throws IllegalStateException if the package is not currently installed
- */
- void endUpgradeOverlayPackage(String packageName, String targetPackageName, int userId) {
- if (mState.select(packageName, userId) == null) {
- throw new IllegalStateException("package not installed, cannot upgrade");
- }
-
- mState.addOverlay(packageName, targetPackageName, userId);
- mImpl.onOverlayPackageReplaced(packageName, userId);
- }
-
- private static final class DummyDeviceState {
- private List<Package> mPackages = new ArrayList<>();
- private OverlayConfig mOverlayConfig = mock(OverlayConfig.class);
-
- /** Adds a non-overlay to the device. */
- public void addTarget(String packageName, int userId) {
- remove(packageName, userId);
- mPackages.add(new Package(packageName, userId, null, false, false, 0));
- }
-
- /** Adds an overlay to the device. */
- public void addOverlay(String packageName, String targetPackageName, int userId) {
- addOverlay(packageName, targetPackageName, userId, true, false, OverlayConfig.DEFAULT_PRIORITY);
- }
-
- /** Adds a configured overlay to the device. */
- public void addOverlay(String packageName, String targetPackageName, int userId,
- boolean mutable, boolean enabled, int priority) {
- remove(packageName, userId);
- mPackages.add(new Package(packageName, userId, targetPackageName, mutable, enabled,
- priority));
- when(mOverlayConfig.getPriority(packageName)).thenReturn(priority);
- when(mOverlayConfig.isEnabled(packageName)).thenReturn(enabled);
- when(mOverlayConfig.isMutable(packageName)).thenReturn(mutable);
- }
-
- /** Remove a package from the device. */
- public void remove(String packageName, int userId) {
- final Iterator<Package> iter = mPackages.iterator();
- while (iter.hasNext()) {
- final Package pkg = iter.next();
- if (pkg.packageName.equals(packageName) && pkg.userId == userId) {
- iter.remove();
- return;
- }
+ pkg.installedUserIds.add(userId);
+ if (previousPkg != null) {
+ pkg.installedUserIds.addAll(previousPkg.installedUserIds);
}
}
- /** Retrieves all packages on device for a particular user. */
- public List<Package> select(int userId) {
- return mPackages.stream().filter(p -> p.userId == userId).collect(Collectors.toList());
+ void remove(String packageName) {
+ mPackages.remove(packageName);
}
- /** Retrieves the package with the specified package name for a particular user. */
- public Package select(String packageName, int userId) {
- return mPackages.stream().filter(
- p -> p.packageName.equals(packageName) && p.userId == userId)
- .findFirst().orElse(null);
+ void uninstall(String packageName, int userId) {
+ final Package pkg = mPackages.get(packageName);
+ if (pkg != null) {
+ pkg.installedUserIds.remove(userId);
+ }
}
- private static final class Package {
- public final String packageName;
- public final int userId;
- public final String targetPackageName;
- public final boolean mutable;
- public final boolean enabled;
- public final int priority;
+ List<Package> select(int userId) {
+ return mPackages.values().stream().filter(p -> p.installedUserIds.contains(userId))
+ .collect(Collectors.toList());
+ }
- private Package(String packageName, int userId, String targetPackageName,
- boolean mutable, boolean enabled, int priority) {
+ Package select(String packageName, int userId) {
+ final Package pkg = mPackages.get(packageName);
+ return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null;
+ }
+
+ private Package selectFromPath(String path) {
+ return mPackages.values().stream()
+ .filter(p -> p.apkPath.equals(path)).findFirst().orElse(null);
+ }
+
+ static final class PackageBuilder {
+ private String packageName;
+ private String targetPackage;
+ private String certificate = "[default]";
+ private int version = 0;
+ private ArrayList<String> overlayableNames = new ArrayList<>();
+ private String targetOverlayableName;
+
+ private PackageBuilder(String packageName, String targetPackage,
+ String targetOverlayableName) {
this.packageName = packageName;
- this.userId = userId;
+ this.targetPackage = targetPackage;
+ this.targetOverlayableName = targetOverlayableName;
+ }
+
+ PackageBuilder setCertificate(String certificate) {
+ this.certificate = certificate;
+ return this;
+ }
+
+ PackageBuilder addOverlayable(String overlayableName) {
+ overlayableNames.add(overlayableName);
+ return this;
+ }
+
+ PackageBuilder setVersion(int version) {
+ this.version = version;
+ return this;
+ }
+
+ Package build() {
+ final String apkPath = String.format("%s/%s/base.apk",
+ targetPackage == null ? "/system/app/:" : "/vendor/overlay/:",
+ packageName);
+ final Package newPackage = new Package(packageName, targetPackage,
+ targetOverlayableName, version, apkPath, certificate);
+ newPackage.overlayableNames.addAll(overlayableNames);
+ return newPackage;
+ }
+ }
+
+ static final class Package {
+ final String packageName;
+ final String targetPackageName;
+ final String targetOverlayableName;
+ final int versionCode;
+ final String apkPath;
+ final String certificate;
+ final ArrayList<String> overlayableNames = new ArrayList<>();
+ private final ArraySet<Integer> installedUserIds = new ArraySet<>();
+
+ private Package(String packageName, String targetPackageName,
+ String targetOverlayableName, int versionCode, String apkPath,
+ String certificate) {
+ this.packageName = packageName;
this.targetPackageName = targetPackageName;
- this.mutable = mutable;
- this.enabled = enabled;
- this.priority = priority;
+ this.targetOverlayableName = targetOverlayableName;
+ this.versionCode = versionCode;
+ this.apkPath = apkPath;
+ this.certificate = certificate;
}
}
}
@@ -288,6 +314,8 @@
static final class DummyPackageManagerHelper implements PackageManagerHelper,
OverlayableInfoCallback {
private final DummyDeviceState mState;
+ String[] overlayableConfiguratorTargets = new String[0];
+ String overlayableConfigurator = "";
private DummyPackageManagerHelper(DummyDeviceState state) {
mState = state;
@@ -300,13 +328,12 @@
return null;
}
final ApplicationInfo ai = new ApplicationInfo();
- ai.sourceDir = String.format("%s/%s/base.apk",
- pkg.targetPackageName == null ? "/system/app/" : "/vendor/overlay/",
- pkg.packageName);
+ ai.sourceDir = pkg.apkPath;
PackageInfo pi = new PackageInfo();
pi.applicationInfo = ai;
pi.packageName = pkg.packageName;
pi.overlayTarget = pkg.targetPackageName;
+ pi.targetOverlayableName = pkg.targetOverlayableName;
pi.overlayCategory = "dummy-category-" + pkg.targetPackageName;
return pi;
}
@@ -314,14 +341,16 @@
@Override
public boolean signaturesMatching(@NonNull String packageName1,
@NonNull String packageName2, int userId) {
- return false;
+ final DummyDeviceState.Package pkg1 = mState.select(packageName1, userId);
+ final DummyDeviceState.Package pkg2 = mState.select(packageName2, userId);
+ return pkg1 != null && pkg2 != null && pkg1.certificate.equals(pkg2.certificate);
}
@Override
public List<PackageInfo> getOverlayPackages(int userId) {
return mState.select(userId).stream()
.filter(p -> p.targetPackageName != null)
- .map(p -> getPackageInfo(p.packageName, p.userId))
+ .map(p -> getPackageInfo(p.packageName, userId))
.collect(Collectors.toList());
}
@@ -329,7 +358,11 @@
@Override
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId) {
- throw new UnsupportedOperationException();
+ final DummyDeviceState.Package pkg = mState.select(packageName, userId);
+ if (pkg == null || !pkg.overlayableNames.contains(targetOverlayableName)) {
+ return null;
+ }
+ return new OverlayableInfo(targetOverlayableName, null /* actor */);
}
@Nullable
@@ -341,69 +374,98 @@
@NonNull
@Override
public Map<String, Map<String, String>> getNamedActors() {
- throw new UnsupportedOperationException();
+ return Collections.emptyMap();
}
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId) {
- throw new UnsupportedOperationException();
+ final DummyDeviceState.Package pkg = mState.select(targetPackageName, userId);
+ return pkg != null && pkg.overlayableNames.contains(targetPackageName);
}
@Override
public void enforcePermission(String permission, String message) throws SecurityException {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public String[] getOverlayableConfiguratorTargets() {
+ return overlayableConfiguratorTargets;
+ }
+
+ @Override
+ public String getOverlayableConfigurator() {
+ return overlayableConfigurator;
+ }
}
- static class DummyIdmapManager extends IdmapManager {
+ static class DummyIdmapDaemon extends IdmapDaemon {
private final DummyDeviceState mState;
- private Set<String> mIdmapFiles = new ArraySet<>();
+ private final ArrayMap<String, IdmapHeader> mIdmapFiles = new ArrayMap<>();
- private DummyIdmapManager(DummyDeviceState state,
- DummyPackageManagerHelper packageManagerHelper) {
- super(packageManagerHelper);
- mState = state;
+ DummyIdmapDaemon(DummyDeviceState state) {
+ this.mState = state;
+ }
+
+ private int getCrc(@NonNull final String path) {
+ final DummyDeviceState.Package pkg = mState.selectFromPath(path);
+ Assert.assertNotNull(pkg);
+ return pkg.versionCode;
}
@Override
- boolean createIdmap(@NonNull final PackageInfo targetPackage,
- @NonNull final PackageInfo overlayPackage, int userId) {
- final DummyDeviceState.Package t = mState.select(targetPackage.packageName, userId);
- if (t == null) {
+ String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
+ int userId) {
+ mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath),
+ getCrc(overlayPath), targetPath, policies, enforce));
+ return overlayPath;
+ }
+
+ @Override
+ boolean removeIdmap(String overlayPath, int userId) {
+ return mIdmapFiles.remove(overlayPath) != null;
+ }
+
+ @Override
+ boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
+ int userId) {
+ final IdmapHeader idmap = mIdmapFiles.get(overlayPath);
+ if (idmap == null) {
return false;
}
- final DummyDeviceState.Package o = mState.select(overlayPackage.packageName, userId);
- if (o == null) {
- return false;
+ return idmap.isUpToDate(getCrc(targetPath), getCrc(overlayPath), targetPath);
+ }
+
+ @Override
+ boolean idmapExists(String overlayPath, int userId) {
+ return mIdmapFiles.containsKey(overlayPath);
+ }
+
+ IdmapHeader getIdmap(String overlayPath) {
+ return mIdmapFiles.get(overlayPath);
+ }
+
+ static class IdmapHeader {
+ private final int targetCrc;
+ private final int overlayCrc;
+ final String targetPath;
+ final int policies;
+ final boolean enforceOverlayable;
+
+ private IdmapHeader(int targetCrc, int overlayCrc, String targetPath, int policies,
+ boolean enforceOverlayable) {
+ this.targetCrc = targetCrc;
+ this.overlayCrc = overlayCrc;
+ this.targetPath = targetPath;
+ this.policies = policies;
+ this.enforceOverlayable = enforceOverlayable;
}
- final String key = createKey(overlayPackage.packageName, userId);
- return mIdmapFiles.add(key);
- }
- @Override
- boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) {
- final String key = createKey(oi.packageName, oi.userId);
- if (!mIdmapFiles.contains(key)) {
- return false;
+ private boolean isUpToDate(int expectedTargetCrc, int expectedOverlayCrc,
+ String expectedTargetPath) {
+ return expectedTargetCrc == targetCrc && expectedOverlayCrc == overlayCrc
+ && expectedTargetPath.equals(targetPath);
}
- mIdmapFiles.remove(key);
- return true;
- }
-
- @Override
- boolean idmapExists(@NonNull final OverlayInfo oi) {
- final String key = createKey(oi.packageName, oi.userId);
- return mIdmapFiles.contains(key);
- }
-
- @Override
- boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
- final String key = createKey(overlayPackage.packageName, userId);
- return mIdmapFiles.contains(key);
- }
-
- private String createKey(@NonNull final String packageName, final int userId) {
- return String.format("%s:%d", packageName, userId);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e47792f..68bc584 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -138,7 +138,7 @@
// Rotation is ignored so because the display size is close to square (700/600<1.333).
assertTrue(mActivity.mDisplayContent.ignoreRotationForApps());
- final Rect displayBounds = mActivity.mDisplayContent.getBounds();
+ final Rect displayBounds = mActivity.mDisplayContent.getWindowConfiguration().getBounds();
final float aspectRatio = 1.2f;
mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = aspectRatio;
prepareUnresizable(-1f, SCREEN_ORIENTATION_UNSPECIFIED);
@@ -160,13 +160,22 @@
assertFitted();
// After the orientation of activity is changed, even display is not rotated, the aspect
- // ratio should be the same (bounds=[0, 0 - 600, 600], appBounds=[0, 100 - 600, 600]).
+ // ratio should be the same (appBounds=[9, 100 - 592, 800], x-offset=round((600-583)/2)=9).
assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */);
// The notch is still on top.
assertEquals(mActivity.getBounds().height(), appBounds.height() + notchHeight);
mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertFitted();
+
+ // Close-to-square display can rotate without being restricted by the requested orientation.
+ // The notch becomes on the left side. The activity is horizontal centered in 100 ~ 800.
+ // So the bounds and appBounds will be [200, 0 - 700, 600] (500x600) that is still fitted.
+ // Left = 100 + (800 - 100 - 500) / 2 = 200.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+ assertFitted();
+ assertEquals(appBounds.left,
+ notchHeight + (displayBounds.width() - notchHeight - appBounds.width()) / 2);
}
@Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 83ca9b2..c86d388 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -6314,6 +6314,7 @@
int netId = network.getNetId();
callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString());
+ inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString());
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
callback.assertNoCallback();
assertEquals(pref64FromRa, mCm.getLinkProperties(network).getNat64Prefix());
@@ -6323,6 +6324,7 @@
mCellNetworkAgent.sendLinkProperties(lp);
expectNat64PrefixChange(callback, mCellNetworkAgent, null);
inOrder.verify(mMockNetd).clatdStop(iface);
+ inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
// If the RA prefix appears while DNS discovery is in progress, discovery is stopped and
@@ -6332,6 +6334,7 @@
expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromRa);
inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString());
inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
+ inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString());
// Withdraw the RA prefix so we can test the case where an RA prefix appears after DNS
// discovery has succeeded.
@@ -6339,6 +6342,7 @@
mCellNetworkAgent.sendLinkProperties(lp);
expectNat64PrefixChange(callback, mCellNetworkAgent, null);
inOrder.verify(mMockNetd).clatdStop(iface);
+ inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */,
@@ -6346,13 +6350,40 @@
expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromDns);
inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString());
- // If the RA prefix reappears, clatd is restarted and prefix discovery is stopped.
+ // If an RA advertises the same prefix that was discovered by DNS, nothing happens: prefix
+ // discovery is not stopped, and there are no callbacks.
+ lp.setNat64Prefix(pref64FromDns);
+ mCellNetworkAgent.sendLinkProperties(lp);
+ callback.assertNoCallback();
+ inOrder.verify(mMockNetd, never()).clatdStop(iface);
+ inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString());
+ inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
+ inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
+ inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());
+
+ // If the RA is later withdrawn, nothing happens again.
+ lp.setNat64Prefix(null);
+ mCellNetworkAgent.sendLinkProperties(lp);
+ callback.assertNoCallback();
+ inOrder.verify(mMockNetd, never()).clatdStop(iface);
+ inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString());
+ inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
+ inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
+ inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());
+
+ // If the RA prefix changes, clatd is restarted and prefix discovery is stopped.
lp.setNat64Prefix(pref64FromRa);
mCellNetworkAgent.sendLinkProperties(lp);
expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromRa);
inOrder.verify(mMockNetd).clatdStop(iface);
inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
+
+ // Stopping prefix discovery results in a prefix removed notification.
+ mService.mNetdEventCallback.onNat64PrefixEvent(netId, false /* added */,
+ pref64FromDnsStr, 96);
+
inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString());
+ inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString());
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
// If the RA prefix changes, clatd is restarted and prefix discovery is not started.
@@ -6360,7 +6391,9 @@
mCellNetworkAgent.sendLinkProperties(lp);
expectNat64PrefixChange(callback, mCellNetworkAgent, newPref64FromRa);
inOrder.verify(mMockNetd).clatdStop(iface);
+ inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
inOrder.verify(mMockNetd).clatdStart(iface, newPref64FromRa.toString());
+ inOrder.verify(mMockDnsResolver).setPrefix64(netId, newPref64FromRa.toString());
inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
@@ -6373,11 +6406,45 @@
inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString());
inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
+ inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());
// The transition between no prefix and DNS prefix is tested in testStackedLinkProperties.
+ // If the same prefix is learned first by DNS and then by RA, and clat is later stopped,
+ // (e.g., because the network disconnects) setPrefix64(netid, "") is never called.
+ lp.setNat64Prefix(null);
+ mCellNetworkAgent.sendLinkProperties(lp);
+ expectNat64PrefixChange(callback, mCellNetworkAgent, null);
+ inOrder.verify(mMockNetd).clatdStop(iface);
+ inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
+ inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
+ mService.mNetdEventCallback.onNat64PrefixEvent(netId, true /* added */,
+ pref64FromDnsStr, 96);
+ expectNat64PrefixChange(callback, mCellNetworkAgent, pref64FromDns);
+ inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString());
+ inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any());
+
+ lp.setNat64Prefix(pref64FromDns);
+ mCellNetworkAgent.sendLinkProperties(lp);
callback.assertNoCallback();
+ inOrder.verify(mMockNetd, never()).clatdStop(iface);
+ inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString());
+ inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId);
+ inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
+ inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());
+
+ // When tearing down a network, clat state is only updated after CALLBACK_LOST is fired, but
+ // before CONNECTIVITY_ACTION is sent. Wait for CONNECTIVITY_ACTION before verifying that
+ // clat has been stopped, or the test will be flaky.
+ ConditionVariable cv = registerConnectivityBroadcast(1);
mCellNetworkAgent.disconnect();
+ callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ waitFor(cv);
+
+ inOrder.verify(mMockNetd).clatdStop(iface);
+ inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
+ inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());
+
mCm.unregisterNetworkCallback(callback);
}