diff options
160 files changed, 2431 insertions, 803 deletions
diff --git a/apex/Android.bp b/apex/Android.bp index 63ec7c3017d3..de4b24aa274c 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -71,6 +71,11 @@ java_defaults { // 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 3d0eeb840064..8587e1452543 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -160,7 +160,7 @@ public: 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 2e6043df0e64..72decf2c7bd0 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -278,7 +278,7 @@ message GaugeMetric { 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 @@ message ValueMetric { 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 d086459080f7..0d8618fe7e86 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -5968,6 +5968,206 @@ public class PackageParser { } } + /** + * 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 @@ public class PackageParser { 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 3b84ae775ad1..b2875e91f80a 100644 --- a/core/java/android/content/pm/Signature.java +++ b/core/java/android/content/pm/Signature.java @@ -123,6 +123,21 @@ public class Signature implements Parcelable { } /** + * 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 ebf4cc5c938e..bf105ced65b9 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2216,6 +2216,20 @@ public class UserManager { } }; + // 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 @@ public class UserManager { /** {@hide} */ public void disableIsUserUnlockedCache() { mIsUserUnlockedCache.disableLocal(); + mIsUserUnlockingOrUnlockedCache.disableLocal(); } /** {@hide} */ @@ -2263,11 +2278,7 @@ public class UserManager { @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 389e33ae7ae2..8b0dd8ae18ce 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.MeasureSpec; 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 @@ public class ChooserActivity extends ResolverActivity implements 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 @@ public class ChooserActivity extends ResolverActivity implements @Override public void onListRebuilt(ResolverListAdapter listAdapter) { setupScrollListener(); + maybeSetupGlobalLayoutListener(); ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter; if (chooserListAdapter.getUserHandle() @@ -2859,6 +2860,28 @@ public class ChooserActivity extends ResolverActivity implements }); } + 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 a205fcf9520b..2dc3996e3d7b 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 758a4f7baffc..6ce25d45b16c 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 51af048bcae8..24f45a57dabc 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 androidx.test.filters.SmallTest; 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 @@ public class SigningDetailsTest { 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 1dd02919a093..c0482e1b3e04 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 1418bf8604bf..2a715d0c3494 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 837252b6d716..ca4e76ee104b 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 a5867638b183..788376494032 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 0e84d517759a..d20ab49a22e6 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 ed0b4853994d..cb321cdc6c4d 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 69ec78eab593..ede4696a96c3 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 @@ package com.android.systemui.car.navigationbar; 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 @@ import com.android.internal.app.IVoiceInteractionSessionShowCallback; * 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 class AssitantButton extends CarNavigationButton { 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 @@ public class AssitantButton extends CarNavigationButton { 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 000000000000..5c83c025bc20 --- /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 5c6472ecb4ef..4c720abb4c74 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 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks 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 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks @UiBackground Executor uiBgExecutor, IStatusBarService barService, Lazy<KeyguardStateController> keyguardStateControllerLazy, - ButtonSelectionStateController buttonSelectionStateController, Lazy<PhoneStatusBarPolicy> iconPolicyLazy, Lazy<StatusBarIconController> iconControllerLazy ) { @@ -156,7 +154,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks mUiBgExecutor = uiBgExecutor; mBarService = barService; mKeyguardStateControllerLazy = keyguardStateControllerLazy; - mButtonSelectionStateController = buttonSelectionStateController; mIconPolicyLazy = iconPolicyLazy; mIconControllerLazy = iconControllerLazy; @@ -280,10 +277,9 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks * 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 288e5cf13c2e..ca780ae645c9 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 @@ public class CarNavigationBarController { 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 class CarNavigationBarController { 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 @@ public class CarNavigationBarController { 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 @@ public class CarNavigationBarController { 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 5f4ac2dcb141..5e113d6366a1 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.view.View; 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 @@ public class CarNavigationButton extends LinearLayout { 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 @@ public class CarNavigationButton extends LinearLayout { 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 @@ public class CarNavigationButton extends LinearLayout { 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 @@ public class CarNavigationButton extends LinearLayout { } /** + * 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 @@ public class CarNavigationButton extends LinearLayout { }; } - /** * Initializes view-related aspects of the button. */ @@ -256,28 +292,27 @@ public class CarNavigationButton extends LinearLayout { 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 000000000000..25ec2c179c8c --- /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 f0e02164a24b..a8e83d6d7717 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 b0ca8dc4cd34..94edc4b5e245 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 576928cda089..44f834040391 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 000000000000..a57736bb3502 --- /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 911f624d1fb3..e84e42c77245 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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @Mock private ButtonSelectionStateController mButtonSelectionStateController; @Mock + private ButtonRoleHolderController mButtonRoleHolderController; + @Mock private HvacController mHvacController; @Before @@ -66,10 +68,15 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { 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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { } @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 04e0a7324adf..0caa86f0eab2 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 @@ public class CarNavigationBarTest extends SysuiTestCase { @Mock private ButtonSelectionStateListener mButtonSelectionStateListener; @Mock + private ButtonRoleHolderController mButtonRoleHolderController; + @Mock private IStatusBarService mBarService; @Mock private KeyguardStateController mKeyguardStateController; @@ -137,8 +139,8 @@ public class CarNavigationBarTest extends SysuiTestCase { 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 11f2fa48783f..54282d39998b 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 @@ public class CarNavigationButtonTest extends SysuiTestCase { } @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 66787c2a8663..35e2295ced33 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 ead0ebfa73bd..25cea4db553c 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 c1f4fd95813f..bd94c4065a1f 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 b5d642a9ae8f..5572994f0d20 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 aecaf704b02f..39ab0783e444 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 092bd42e60ce..78fd0bf3b100 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 d610973d891c..30a9e0ee81ec 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 5c3fe75857dc..c8dd82f626c0 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 27d370714a0d..02ca679fca27 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 a9d9e7025236..785460f0ba2f 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 5ed0f1b2d3ca..50c03745fb6d 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 d4d37353fb82..9fb63ea13798 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 89dbf2581746..dc4f873a7595 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 c2030b162e56..bee2d7995d55 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 bfc183e40a53..b1d07acc3d8a 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 9d869ba30717..e51456af9b14 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 9d869ba30717..e51456af9b14 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 9d869ba30717..e51456af9b14 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 9d869ba30717..e51456af9b14 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 77658243864e..34ff568bb315 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 6a76bebc78ee..fb5e9d760b04 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 010b85b86df3..a6a89dd2578d 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 ad2a56152452..65f1043a7b8d 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 1ceb738914f2..6864db195ce1 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 d3f1c56cd1d7..5e13c1b07256 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 5ab01c4b51b4..1ec71c71abb6 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 26e50421cad8..0a32ca51f769 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 b8fc50dc028b..bb069b55c7dd 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 8dc58ef6f32f..a27f2c4078e0 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 9f69a2c0fa20..cf1e5eedfd33 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 dc8e28bb9df2..89b7f679612e 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 f8878396d867..f57a302e43f3 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 d7d269406f84..e8699dc05115 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 a5ef6b557e4b..3f2b41421ad5 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 04ca2f69cc61..dd1f9994c916 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 ce60eb50031d..5dad63d03ae4 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 bef644b60a3b..02b853384b98 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 ed796326c708..af6a8706ed71 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 3f08a6ac83e2..080c1a9116f5 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 2ebc3ee32c38..23de5b23f7be 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 91e548830ee9..821c5663ffec 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 143f5f90a457..f93ab25930b9 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 1df141ca9d9b..e41877966e19 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 86e165027652..6b273a36e0f0 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 925f4a706f70..d035362b41d8 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 3bf1996591f5..e188f3d6ec7a 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 153c9956dc84..075032cdf261 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 f6e7f35d9211..c37811fc0b44 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 d55f1aff6f96..1f5908c1c7cf 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 c5267d6b5b0a..6ee9777f3887 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 0a01f8480d0c..29647df89f8b 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 075c748f1bb7..99bf1e8fc8e8 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 cdf32a7b39ce..bdecf640e9b1 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 2bd3b450238f..1519380832f9 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 de5bed63b70a..a2f862ea5854 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 bfe295a4066a..159050e2a3df 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 a82271981fca..7e3005b76aea 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 ab9fbc364f43..19e250ba3acc 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 35d8cbab0b36..184f4cd5ec46 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 54ed131797a9..eebea5f3c70b 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 c6dc1d3376b9..142ef478111a 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 999e684f124d..0c6f488dbba5 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 c6dc1d3376b9..142ef478111a 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 ba2f36fc5c1d..036cf830e160 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 8ec387584c5a..6b3975406fbd 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 01c36349bb8f..9074c632bcbd 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 b5cbf435f356..cd3109fc9f6d 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 86cccb859e27..0e492c7d1e42 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 bd3353cfc2d3..4a80bcd10fe8 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 29f23b48d5e0..321645ebcf5e 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 a40100e83f51..cd28c86b986d 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 fedf1c85946c..2bc038e27cb1 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 345f0a7c560d..a28fdce2d653 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 c4fda01c19d2..b7bb6e8f5e37 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 a9032ebf4fd2..2f9ac46d1033 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 712c06bbea74..5064c1efd8cd 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 8818498bbcfd..454ca3b45861 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 f6568e3dbb5e..7e969f7bac4b 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 f13f8fb40847..e3b28c7df66e 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 7b8f627ac0db..04707c53f9e5 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 35cecc1a7b9f..e7fbf467cdc1 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 c69ea2d4b668..bda7d8b05bb4 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 b0324d9a2f46..841de63a8397 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 0574c7ecf626..a6c1647ec9f1 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 b60553a052e4..c43758093537 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 d3f9cd4f9034..9d1b3cf116d9 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.Context; 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 class BluetoothUtils { */ 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 @@ public class BluetoothUtils { 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 @@ public class BluetoothUtils { } } - 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 74a59399feee..a2f77e2d5046 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 @@ public class BluetoothMediaDevice extends MediaDevice { } @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 83a96716e284..22dc90643128 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 @@ public class InfoMediaDevice extends MediaDevice { 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 008a433bb7a4..959c42fe75c9 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 @@ import java.util.concurrent.Executors; 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 @@ public class InfoMediaManager extends MediaManager { 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 7d95f1992aaf..f34903cf7f98 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 @@ public class LocalMediaManager implements BluetoothCallback { 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 @@ public class LocalMediaManager implements BluetoothCallback { 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 8a178a9a81f2..317077b14e30 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 class MediaDevice implements Comparable<MediaDevice> { 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 9a3ae1be14c0..c43a51246fb5 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 @@ public class PhoneMediaDevice extends MediaDevice { 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 734866f49a53..c51467123233 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 @@ public class InfoMediaManagerTest { 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 @@ public class InfoMediaManagerTest { @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 @@ public class InfoMediaManagerTest { 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 517071b5511f..009f75a393e2 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 static org.mockito.Mockito.when; 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 @@ public class LocalMediaManagerTest { 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 055b2beaaa65..985269b2bb75 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 2587369cf0f5..eb4ba6f682af 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } + 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 769740032509..c4b4f4316d93 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 class BubbleExpandedView extends LinearLayout { 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 f4eb580d70ff..af6e66aeb989 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 @@ public class BubbleOverflow implements BubbleViewProvider { } 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 @@ public class BubbleOverflow implements BubbleViewProvider { 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 08ec789bba6a..8fec3385d491 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 @@ public class BubbleOverflowActivity extends Activity { } @Override public boolean canScrollVertically() { + if (mBubbleController.inLandscape()) { + return super.canScrollVertically(); + } return false; } } @@ -89,6 +92,14 @@ public class BubbleOverflowActivity extends Activity { 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 @@ public class BubbleOverflowActivity extends Activity { 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 @@ public class BubbleOverflowActivity extends Activity { 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 @@ public class BubbleOverflowActivity extends Activity { @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 ec45f93b38ee..bdfcb35f7df2 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.AnimatorListenerAdapter; 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.RectF; 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 @@ public class BubbleStackView extends FrameLayout 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 @@ public class BubbleStackView extends FrameLayout 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 d52c35b48ccc..fd1f879e7f4b 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 @@ public class StackAnimationController extends * 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 @@ public class StackAnimationController extends 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 @@ public class StackAnimationController extends ? 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 @@ public class StackAnimationController extends @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 @@ public class StackAnimationController extends 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 181170b0dee5..f8f913e8573d 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 @@ class ControlsControllerImpl @Inject constructor ( 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 @@ class ControlsControllerImpl @Inject constructor ( 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 @@ class ControlsControllerImpl @Inject constructor ( 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 @@ class ControlsControllerImpl @Inject constructor ( 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 @@ class ControlsControllerImpl @Inject constructor ( 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 @@ class ControlsControllerImpl @Inject constructor ( 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 @@ class ControlsControllerImpl @Inject constructor ( null ) contentResolver.registerContentObserver(URI, false, settingObserver, UserHandle.USER_ALL) + listingController.addCallback(listingCallback) } fun destroy() { @@ -258,7 +257,6 @@ class ControlsControllerImpl @Inject constructor ( if (shouldLoad) { Favorites.load(persistenceWrapper.readFavorites()) - listingController.addCallback(listingCallback) } } @@ -569,12 +567,12 @@ class UserStructure(context: Context, user: UserHandle) { 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 a2adcf953cc4..1cd9712cd01b 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.settingslib.widget.CandidateInfo 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 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( 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 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( 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 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( } override fun changeUser(newUser: UserHandle) { - backgroundExecutor.execute { - serviceListing.setListening(false) + userChangeInProgress.incrementAndGet() + 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()) + backgroundExecutor.execute { + 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 @@ class ControlsListingControllerImpl @VisibleForTesting constructor( */ 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 3a8212cd733e..58aad8673172 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.KeyguardManager; 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 @@ public class SystemServicesModule { 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 ac289cb5f822..61c9a967cc4c 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.model.SysUiState; 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mControlsController = controlsController; mSysUiState = sysUiState; mMainHandler = handler; + mCurrentUserContextTracker = currentUserContextTracker; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -382,12 +385,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 b9b8a25c6d31..a10972e8721b 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.Size; 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 @@ public class PipBoundsHandler { 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 @@ public class PipBoundsHandler { 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 @@ public class PipBoundsHandler { * * @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 @@ public class PipBoundsHandler { 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 @@ public class PipBoundsHandler { mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds, snapFraction); + getInsetBounds(outInsetBounds); outBounds.set(postChangeStackBounds); t.setBounds(pinnedStackInfo.stackToken, outBounds); return true; @@ -425,15 +443,11 @@ public class PipBoundsHandler { * 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 64df2ffee1c6..02bf74577407 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 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio 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 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio @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 c408caaccbfb..c274ee96b170 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 @@ public class PipTouchHandler { 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 fefad531377f..da31fe03c9e7 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 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter 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 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter 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 e3f25c6b0c72..b3888875e82e 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 @@ class ControlsControllerImplTest : SysuiTestCase() { } @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 128a7e856ec5..841b2553ebda 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 @@ class ControlsListingControllerImplTest : SysuiTestCase() { @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 @@ class ControlsListingControllerImplTest : SysuiTestCase() { 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 @@ class ControlsListingControllerImplTest : SysuiTestCase() { } @Test - fun testChangeUserResetsExistingCallbackServices() { + fun testChangeUserSendsCorrectServiceUpdate() { val list = listOf(serviceInfo) controller.addCallback(mockCallback) @@ -197,10 +201,21 @@ class ControlsListingControllerImplTest : SysuiTestCase() { 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 8db57cdf5f71..487452b0d26a 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.model.SysUiState; 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 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock GlobalActionsPanelPlugin mWalletPlugin; @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController; @Mock private Handler mHandler; + @Mock private CurrentUserContextTracker mCurrentUserContextTracker; private TestableLooper mTestableLooper; @@ -129,6 +131,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase { allowTestableLooperAsMainThread(); when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); + when(mCurrentUserContextTracker.getCurrentUserContext()).thenReturn(mContext); mGlobalActionsDialog = new GlobalActionsDialog(mContext, mWindowManagerFuncs, mAudioManager, @@ -161,7 +164,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { 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 425bf88ebec0..f404f0489e01 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 @@ package com.android.systemui.pip; 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 android.view.Gravity; 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 @@ public class PipBoundsHandlerTest extends SysuiTestCase { @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 5cbfcc1bcd06..e254cd2c82a7 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { /** * 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { */ private void ensureStateForBubbleUp() { ensureStateForAlertCommon(); - ensureStateForAlertAwakeCommon(); } @Test @@ -392,7 +382,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @Test public void testShouldNotHeadsUp_snoozedPackage() { NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); - ensureStateForAlertAwakeCommon(); when(mHeadsUpManager.isSnoozed(entry.getSbn().getPackageName())).thenReturn(true); @@ -402,7 +391,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @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 f5d6e5ac46e5..08d266478f4b 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 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub { 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 34d0bed9a802..3091a7178377 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 @@ public class Nat464Xlat extends BaseNetworkObserver { if (mPrefixDiscoveryRunning && !isPrefixDiscoveryNeeded()) { stopPrefixDiscovery(); } + if (!mPrefixDiscoveryRunning) { + setPrefix64(mNat64PrefixInUse); + } } /** @@ -221,6 +224,10 @@ public class Nat464Xlat extends BaseNetworkObserver { mIface = null; mBaseIface = null; + if (!mPrefixDiscoveryRunning) { + setPrefix64(null); + } + if (isPrefixDiscoveryNeeded()) { if (!mPrefixDiscoveryRunning) { startPrefixDiscovery(); @@ -308,6 +315,16 @@ public class Nat464Xlat extends BaseNetworkObserver { 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 910ed44df0f8..19fa9204935d 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 android.util.Slog; import com.android.server.FgThread; +import java.io.File; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; @@ -124,9 +125,13 @@ class IdmapDaemon { } } - 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 90c85ada9def..735d66983520 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.content.pm.PackageInfo; 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 @@ class IdmapManager { 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 @@ class IdmapManager { } 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 @@ class IdmapManager { 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 ef6655dd224e..2bc34998785b 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 @@ public class OverlayActorEnforcer { /** * @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 6a8e465529d7..086ab8183254 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.PackageInfo; 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.AtomicFile; 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 @@ public final class OverlayManagerService extends SystemService { 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 @@ public final class OverlayManagerService extends SystemService { } @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 5066ecdd6316..41c341adf1bc 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 @@ public interface OverlayableInfoCallback { * 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 830388d8f2ac..4b6ee71803a7 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 @@ public class AppsFilter { } } - 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 @@ public class AppsFilter { 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 395e835b6a2a..fbe810025edb 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 @@ public class PackageManagerService extends IPackageManager.Stub // 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 5c175a6ef847..1fce07b0cbf1 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 @@ public class PackageManagerServiceUtils { 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 @@ public class PackageManagerServiceUtils { || 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 @@ public class PackageManagerServiceUtils { + " 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 b5b82d39b921..5a6e0a1ff9c3 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 class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 425c7247e50f..d45589d90ce3 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 class OverrideValidatorImplTest { 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 @@ public class OverrideValidatorImplTest { .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 @@ public class OverrideValidatorImplTest { 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 f35eecf05f32..b7692f912e39 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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI 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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI 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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @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 cd7343235750..b25af0543829 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.assertNull; import static org.junit.Assert.assertTrue; import android.content.om.OverlayInfo; +import android.os.OverlayablePolicy; import androidx.test.runner.AndroidJUnit4; @@ -49,11 +50,9 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes 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 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @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 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @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 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @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); - assertState(STATE_ENABLED, OVERLAY, USER); - - endUpgradeTargetPackage(TARGET, USER); + upgradePackage(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); + upgradePackage(overlay, USER); + assertEquals(2, 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 - assertEquals(2, listener.count); + listener.count = 0; + final DummyDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target"); + upgradePackage(overlay2, USER); + assertEquals(3, listener.count); - beginUpgradeOverlayPackage(OVERLAY, USER); listener.count = 0; - endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER); - assertEquals(1, listener.count); + 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 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes 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 9eda718ed922..ec6a48182a25 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.OverlayInfo.State; 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 @@ class OverlayManagerServiceImplTestsBase { 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 @@ class OverlayManagerServiceImplTestsBase { 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 @@ class OverlayManagerServiceImplTestsBase { 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 */); } - /** - * Adds the target 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 - */ - void installTargetPackage(String packageName, int userId) { - if (mState.select(packageName, userId) != null) { - throw new IllegalStateException("package already installed"); - } - mState.addTarget(packageName, userId); - mImpl.onTargetPackageAdded(packageName, userId); + DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName) { + return overlay(packageName, targetPackageName, null /* targetOverlayableName */); } - /** - * Begins upgrading the target 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); + DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName, + String targetOverlayableName) { + return new DummyDeviceState.PackageBuilder(packageName, targetPackageName, + targetOverlayableName); } - /** - * Ends upgrading the 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 endUpgradeTargetPackage(String packageName, int userId) { - if (mState.select(packageName, userId) == null) { - throw new IllegalStateException("package not installed"); - } - mState.addTarget(packageName, userId); - mImpl.onTargetPackageReplaced(packageName, userId); + void addSystemPackage(DummyDeviceState.PackageBuilder pkg, int userId) { + mState.add(pkg, userId); } - /** - * Removes the target 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"); - } - mState.remove(packageName, userId); - mImpl.onTargetPackageRemoved(packageName, 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 overlay 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 already installed + * @throws IllegalStateException if the package is currently installed */ - void installOverlayPackage(String packageName, String targetPackageName, 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.add(pkg, userId); + if (pkg.targetPackage == null) { + mImpl.onTargetPackageAdded(pkg.packageName, userId); + } else { + mImpl.onOverlayPackageAdded(pkg.packageName, userId); } - mState.addOverlay(packageName, targetPackageName, userId); - mImpl.onOverlayPackageAdded(packageName, userId); } /** - * Begins upgrading the overlay 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 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 beginUpgradeOverlayPackage(String packageName, int userId) { - if (mState.select(packageName, userId) == null) { - throw new IllegalStateException("package not installed, cannot upgrade"); + 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"); + } + if (replacedPackage.targetPackageName != null) { + mImpl.onOverlayPackageReplacing(pkg.packageName, userId); } - mImpl.onOverlayPackageReplacing(packageName, userId); + mState.add(pkg, userId); + if (pkg.targetPackage == null) { + mImpl.onTargetPackageReplaced(pkg.packageName, userId); + } else { + mImpl.onOverlayPackageReplaced(pkg.packageName, userId); + } } /** - * Ends upgrading the overlay package, potentially changing its target package. + * Removes the package from the device. * * 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. + * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast. * * @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"); + 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(pkg.packageName); + if (pkg.targetPackageName == null) { + mImpl.onTargetPackageRemoved(pkg.packageName, userId); + } else { + mImpl.onOverlayPackageRemoved(pkg.packageName, userId); } - - 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; - } + /** Represents the state of packages installed on a fake device. */ + static class DummyDeviceState { + private ArrayMap<String, Package> mPackages = new ArrayMap<>(); + + void add(PackageBuilder pkgBuilder, int userId) { + final Package pkg = pkgBuilder.build(); + final Package previousPkg = select(pkg.packageName, userId); + mPackages.put(pkg.packageName, pkg); + + 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); + } + + void uninstall(String packageName, int userId) { + final Package pkg = mPackages.get(packageName); + if (pkg != null) { + pkg.installedUserIds.remove(userId); + } + } + + List<Package> select(int userId) { + return mPackages.values().stream().filter(p -> p.installedUserIds.contains(userId)) + .collect(Collectors.toList()); + } + + Package select(String packageName, int userId) { + final Package pkg = mPackages.get(packageName); + return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null; } - /** 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); + private Package selectFromPath(String path) { + return mPackages.values().stream() + .filter(p -> p.apkPath.equals(path)).findFirst().orElse(null); } - 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; + 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 Package(String packageName, int userId, String targetPackageName, - boolean mutable, boolean enabled, int priority) { + private PackageBuilder(String packageName, String targetPackage, + String targetOverlayableName) { + this.packageName = packageName; + 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.userId = userId; 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 @@ class OverlayManagerServiceImplTestsBase { 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 @@ class OverlayManagerServiceImplTestsBase { 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 @@ class OverlayManagerServiceImplTestsBase { @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 @@ class OverlayManagerServiceImplTestsBase { @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 @@ class OverlayManagerServiceImplTestsBase { @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) { - return false; - } - final DummyDeviceState.Package o = mState.select(overlayPackage.packageName, userId); - if (o == null) { - return false; - } - final String key = createKey(overlayPackage.packageName, userId); - return mIdmapFiles.add(key); + 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 removeIdmap(@NonNull final OverlayInfo oi, final int userId) { - final String key = createKey(oi.packageName, oi.userId); - if (!mIdmapFiles.contains(key)) { + boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce, + int userId) { + final IdmapHeader idmap = mIdmapFiles.get(overlayPath); + if (idmap == null) { return false; } - mIdmapFiles.remove(key); - return true; + return idmap.isUpToDate(getCrc(targetPath), getCrc(overlayPath), targetPath); } @Override - boolean idmapExists(@NonNull final OverlayInfo oi) { - final String key = createKey(oi.packageName, oi.userId); - return mIdmapFiles.contains(key); + boolean idmapExists(String overlayPath, int userId) { + return mIdmapFiles.containsKey(overlayPath); } - @Override - boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) { - final String key = createKey(overlayPackage.packageName, userId); - return mIdmapFiles.contains(key); + IdmapHeader getIdmap(String overlayPath) { + return mIdmapFiles.get(overlayPath); } - private String createKey(@NonNull final String packageName, final int userId) { - return String.format("%s:%d", packageName, userId); + 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; + } + + private boolean isUpToDate(int expectedTargetCrc, int expectedOverlayCrc, + String expectedTargetPath) { + return expectedTargetCrc == targetCrc && expectedOverlayCrc == overlayCrc + && expectedTargetPath.equals(targetPath); + } } } 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 e47792f4920c..68bc58493f13 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 @@ public class SizeCompatTests extends ActivityTestsBase { // 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 @@ public class SizeCompatTests extends ActivityTestsBase { 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 83ca9b28e9b5..c86d388a3db9 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -6314,6 +6314,7 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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); } |