diff options
620 files changed, 14001 insertions, 5947 deletions
diff --git a/Android.bp b/Android.bp index 632f49da5df9..7c2d6eb9301e 100644 --- a/Android.bp +++ b/Android.bp @@ -264,6 +264,7 @@ filegroup { ":libcamera_client_aidl", ":libcamera_client_framework_aidl", ":libupdate_engine_aidl", + ":resourcemanager_aidl", ":storaged_aidl", ":vold_aidl", diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java index 1667c1658a07..6122ef254855 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java @@ -23,6 +23,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.hamcrest.core.AnyOf.anyOf; import static org.hamcrest.core.Is.is; +import android.app.ActivityManager; import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; @@ -121,6 +122,12 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { @AfterClass public static void tearDownClass() { sSetUpClassException = null; + try { + // Recents activity may stop app switches. Restore the state to avoid affecting + // the next test. + ActivityManager.resumeAppSwitches(); + } catch (RemoteException ignored) { + } sUiAutomation.dropShellPermissionIdentity(); } diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java index 8139a2e963c5..f04e55567520 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java @@ -88,10 +88,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { public void testRelayout() throws Throwable { final Activity activity = mActivityRule.getActivity(); final ContentView contentView = new ContentView(activity); - mActivityRule.runOnUiThread(() -> { - activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - activity.setContentView(contentView); - }); + mActivityRule.runOnUiThread(() -> activity.setContentView(contentView)); getInstrumentation().waitForIdleSync(); final RelayoutRunner relayoutRunner = new RelayoutRunner(activity, contentView.getWindow(), diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java index 9e17e940a06b..655d2f7f8aa7 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java @@ -19,11 +19,13 @@ package android.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import android.app.Activity; +import android.app.KeyguardManager; import android.app.UiAutomation; import android.content.Context; import android.content.Intent; import android.os.BatteryManager; import android.os.ParcelFileDescriptor; +import android.os.PowerManager; import android.perftests.utils.PerfTestActivity; import android.provider.Settings; @@ -61,24 +63,32 @@ public class WindowManagerPerfTestBase { @BeforeClass public static void setUpOnce() { final Context context = getInstrumentation().getContext(); - sOriginalStayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(), + final int stayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(), Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); - // Keep the device awake during testing. - setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_USB); + sOriginalStayOnWhilePluggedIn = -1; + if (stayOnWhilePluggedIn != BatteryManager.BATTERY_PLUGGED_ANY) { + sOriginalStayOnWhilePluggedIn = stayOnWhilePluggedIn; + // Keep the device awake during testing. + setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_ANY); + } if (!BASE_OUT_PATH.exists()) { executeShellCommand("mkdir -p " + BASE_OUT_PATH); } - // In order to be closer to the real use case. - executeShellCommand("input keyevent KEYCODE_WAKEUP"); - executeShellCommand("wm dismiss-keyguard"); + if (!context.getSystemService(PowerManager.class).isInteractive() + || context.getSystemService(KeyguardManager.class).isKeyguardLocked()) { + executeShellCommand("input keyevent KEYCODE_WAKEUP"); + executeShellCommand("wm dismiss-keyguard"); + } context.startActivity(new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } @AfterClass public static void tearDownOnce() { - setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); + if (sOriginalStayOnWhilePluggedIn != -1) { + setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); + } } private static void setStayOnWhilePluggedIn(int value) { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java index ec7ba287acec..ba0fab6b4bc5 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java @@ -44,7 +44,7 @@ import java.util.Objects; /** * Class for representing how a blob can be shared. * - * Note that this class is not thread-safe, callers need to take of synchronizing access. + * Note that this class is not thread-safe, callers need to take care of synchronizing access. */ class BlobAccessMode { @Retention(RetentionPolicy.SOURCE) @@ -127,6 +127,14 @@ class BlobAccessMode { return false; } + int getAccessType() { + return mAccessType; + } + + int getNumWhitelistedPackages() { + return mWhitelistedPackages.size(); + } + void dump(IndentingPrintWriter fout) { fout.println("accessType: " + DebugUtils.flagsToString( BlobAccessMode.class, "ACCESS_TYPE_", mAccessType)); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java index cea7fcca6e20..1193ae9cf990 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -29,6 +29,8 @@ import static android.app.blob.XmlTags.TAG_COMMITTER; import static android.app.blob.XmlTags.TAG_LEASEE; import static android.os.Process.INVALID_UID; import static android.system.OsConstants.O_RDONLY; +import static android.text.format.Formatter.FLAG_IEC_UNITS; +import static android.text.format.Formatter.formatFileSize; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME; @@ -54,6 +56,8 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; +import android.util.StatsEvent; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -61,6 +65,8 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import com.android.server.blob.BlobStoreManagerService.DumpArgs; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -331,7 +337,9 @@ class BlobMetadata { } void forEachLeasee(Consumer<Leasee> consumer) { - mLeasees.forEach(consumer); + synchronized (mMetadataLock) { + mLeasees.forEach(consumer); + } } File getBlobFile() { @@ -349,14 +357,16 @@ class BlobMetadata { } catch (ErrnoException e) { throw e.rethrowAsIOException(); } - synchronized (mMetadataLock) { - return createRevocableFdLocked(fd, callingPackage); + try { + return createRevocableFd(fd, callingPackage); + } catch (IOException e) { + IoUtils.closeQuietly(fd); + throw e; } } - @GuardedBy("mMetadataLock") @NonNull - private ParcelFileDescriptor createRevocableFdLocked(FileDescriptor fd, + private ParcelFileDescriptor createRevocableFd(FileDescriptor fd, String callingPackage) throws IOException { final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); @@ -410,55 +420,101 @@ class BlobMetadata { return true; } - void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { - fout.println("blobHandle:"); - fout.increaseIndent(); - mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); - fout.decreaseIndent(); - - fout.println("Committers:"); - fout.increaseIndent(); - if (mCommitters.isEmpty()) { - fout.println("<empty>"); - } else { - for (int i = 0, count = mCommitters.size(); i < count; ++i) { + StatsEvent dumpAsStatsEvent(int atomTag) { + synchronized (mMetadataLock) { + ProtoOutputStream proto = new ProtoOutputStream(); + // Write Committer data to proto format + for (int i = 0, size = mCommitters.size(); i < size; ++i) { final Committer committer = mCommitters.valueAt(i); - fout.println("committer " + committer.toString()); - fout.increaseIndent(); - committer.dump(fout); - fout.decreaseIndent(); + final long token = proto.start( + BlobStatsEventProto.BlobCommitterListProto.COMMITTER); + proto.write(BlobStatsEventProto.BlobCommitterProto.UID, committer.uid); + proto.write(BlobStatsEventProto.BlobCommitterProto.COMMIT_TIMESTAMP_MILLIS, + committer.commitTimeMs); + proto.write(BlobStatsEventProto.BlobCommitterProto.ACCESS_MODE, + committer.blobAccessMode.getAccessType()); + proto.write(BlobStatsEventProto.BlobCommitterProto.NUM_WHITELISTED_PACKAGE, + committer.blobAccessMode.getNumWhitelistedPackages()); + proto.end(token); } - } - fout.decreaseIndent(); + final byte[] committersBytes = proto.getBytes(); - fout.println("Leasees:"); - fout.increaseIndent(); - if (mLeasees.isEmpty()) { - fout.println("<empty>"); - } else { - for (int i = 0, count = mLeasees.size(); i < count; ++i) { + proto = new ProtoOutputStream(); + // Write Leasee data to proto format + for (int i = 0, size = mLeasees.size(); i < size; ++i) { final Leasee leasee = mLeasees.valueAt(i); - fout.println("leasee " + leasee.toString()); - fout.increaseIndent(); - leasee.dump(mContext, fout); - fout.decreaseIndent(); + final long token = proto.start(BlobStatsEventProto.BlobLeaseeListProto.LEASEE); + proto.write(BlobStatsEventProto.BlobLeaseeProto.UID, leasee.uid); + proto.write(BlobStatsEventProto.BlobLeaseeProto.LEASE_EXPIRY_TIMESTAMP_MILLIS, + leasee.expiryTimeMillis); + proto.end(token); } + final byte[] leaseesBytes = proto.getBytes(); + + // Construct the StatsEvent to represent this Blob + return StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeLong(mBlobId) + .writeLong(getSize()) + .writeLong(mBlobHandle.getExpiryTimeMillis()) + .writeByteArray(committersBytes) + .writeByteArray(leaseesBytes) + .build(); } - fout.decreaseIndent(); - - fout.println("Open fds:"); - fout.increaseIndent(); - if (mRevocableFds.isEmpty()) { - fout.println("<empty>"); - } else { - for (int i = 0, count = mRevocableFds.size(); i < count; ++i) { - final String packageName = mRevocableFds.keyAt(i); - final ArraySet<RevocableFileDescriptor> packageFds = - mRevocableFds.valueAt(i); - fout.println(packageName + "#" + packageFds.size()); + } + + void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { + synchronized (mMetadataLock) { + fout.println("blobHandle:"); + fout.increaseIndent(); + mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); + fout.decreaseIndent(); + fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); + + fout.println("Committers:"); + fout.increaseIndent(); + if (mCommitters.isEmpty()) { + fout.println("<empty>"); + } else { + for (int i = 0, count = mCommitters.size(); i < count; ++i) { + final Committer committer = mCommitters.valueAt(i); + fout.println("committer " + committer.toString()); + fout.increaseIndent(); + committer.dump(fout); + fout.decreaseIndent(); + } + } + fout.decreaseIndent(); + + fout.println("Leasees:"); + fout.increaseIndent(); + if (mLeasees.isEmpty()) { + fout.println("<empty>"); + } else { + for (int i = 0, count = mLeasees.size(); i < count; ++i) { + final Leasee leasee = mLeasees.valueAt(i); + fout.println("leasee " + leasee.toString()); + fout.increaseIndent(); + leasee.dump(mContext, fout); + fout.decreaseIndent(); + } } + fout.decreaseIndent(); + + fout.println("Open fds:"); + fout.increaseIndent(); + if (mRevocableFds.isEmpty()) { + fout.println("<empty>"); + } else { + for (int i = 0, count = mRevocableFds.size(); i < count; ++i) { + final String packageName = mRevocableFds.keyAt(i); + final ArraySet<RevocableFileDescriptor> packageFds = + mRevocableFds.valueAt(i); + fout.println(packageName + "#" + packageFds.size()); + } + } + fout.decreaseIndent(); } - fout.decreaseIndent(); } void writeToXml(XmlSerializer out) throws IOException { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index 656726622cfd..08ee24460722 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -49,6 +49,9 @@ class BlobStoreConfig { public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_SESSION_CREATION_TIME; + public static final long INVALID_BLOB_ID = 0; + public static final long INVALID_BLOB_SIZE = 0; + private static final String ROOT_DIR_NAME = "blobstore"; private static final String BLOBS_DIR_NAME = "blobs"; private static final String SESSIONS_INDEX_FILE_NAME = "sessions_index.xml"; diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 68c4bb675907..78eab0b0a21e 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -28,6 +28,8 @@ import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.os.UserHandle.USER_CURRENT; import static android.os.UserHandle.USER_NULL; +import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_ID; +import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE; import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT; @@ -48,6 +50,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.StatsManager; import android.app.blob.BlobHandle; import android.app.blob.BlobInfo; import android.app.blob.IBlobStoreManager; @@ -80,6 +83,7 @@ import android.util.ExceptionUtils; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; +import android.util.StatsEvent; import android.util.Xml; import com.android.internal.annotations.GuardedBy; @@ -88,6 +92,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; @@ -159,6 +164,8 @@ public class BlobStoreManagerService extends SystemService { new SessionStateChangeListener(); private PackageManagerInternal mPackageManagerInternal; + private StatsManager mStatsManager; + private StatsPullAtomCallbackImpl mStatsCallbackImpl = new StatsPullAtomCallbackImpl(); private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo; private final Runnable mSaveSessionsRunnable = this::writeBlobSessions; @@ -192,6 +199,7 @@ public class BlobStoreManagerService extends SystemService { LocalServices.addService(BlobStoreManagerInternal.class, new LocalService()); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mStatsManager = getContext().getSystemService(StatsManager.class); registerReceivers(); LocalServices.getService(StorageStatsManagerInternal.class) .registerStorageStatsAugmenter(new BlobStorageStatsAugmenter(), TAG); @@ -207,6 +215,7 @@ public class BlobStoreManagerService extends SystemService { readBlobSessionsLocked(allPackages); readBlobsInfoLocked(allPackages); } + registerBlobStorePuller(); } else if (phase == PHASE_BOOT_COMPLETED) { BlobStoreIdleJobService.schedule(mContext); } @@ -218,8 +227,9 @@ public class BlobStoreManagerService extends SystemService { int n = 0; long sessionId; do { - sessionId = Math.abs(mRandom.nextLong()); - if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != 0) { + final long randomLong = mRandom.nextLong(); + sessionId = (randomLong == Long.MIN_VALUE) ? INVALID_BLOB_ID : Math.abs(randomLong); + if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != INVALID_BLOB_ID) { return sessionId; } } while (n++ < 32); @@ -376,9 +386,23 @@ public class BlobStoreManagerService extends SystemService { .get(blobHandle); if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( callingPackage, callingUid)) { + if (blobMetadata == null) { + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, + INVALID_BLOB_ID, INVALID_BLOB_SIZE, + FrameworkStatsLog.BLOB_OPENED__RESULT__BLOB_DNE); + } else { + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED); + } throw new SecurityException("Caller not allowed to access " + blobHandle + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); } + + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS); + return blobMetadata.openForRead(callingPackage); } } @@ -391,19 +415,41 @@ public class BlobStoreManagerService extends SystemService { .get(blobHandle); if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( callingPackage, callingUid)) { + if (blobMetadata == null) { + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + INVALID_BLOB_ID, INVALID_BLOB_SIZE, + FrameworkStatsLog.BLOB_LEASED__RESULT__BLOB_DNE); + } else { + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED); + } throw new SecurityException("Caller not allowed to access " + blobHandle + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); } if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0 && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) { + + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID); throw new IllegalArgumentException( "Lease expiry cannot be later than blobs expiry time"); } if (blobMetadata.getSize() > getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) { + + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_LEASED__RESULT__DATA_SIZE_LIMIT_EXCEEDED); throw new LimitExceededException("Total amount of data with an active lease" + " is exceeding the max limit"); } + + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_LEASED__RESULT__SUCCESS); + blobMetadata.addOrReplaceLeasee(callingPackage, callingUid, descriptionResId, description, leaseExpiryTimeMillis); if (LOGV) { @@ -587,6 +633,9 @@ public class BlobStoreManagerService extends SystemService { blob.addOrReplaceCommitter(newCommitter); try { writeBlobsInfoLocked(); + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, + session.getOwnerUid(), blob.getBlobId(), blob.getSize(), + FrameworkStatsLog.BLOB_COMMITTED__RESULT__SUCCESS); session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS); } catch (Exception e) { if (existingCommitter == null) { @@ -595,7 +644,21 @@ public class BlobStoreManagerService extends SystemService { blob.addOrReplaceCommitter(existingCommitter); } Slog.d(TAG, "Error committing the blob", e); + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, + session.getOwnerUid(), blob.getBlobId(), blob.getSize(), + FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT); session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); + // If the commit fails and this blob data didn't exist before, delete it. + // But if it is a recommit, just leave it as is. + if (session.getSessionId() == blob.getBlobId()) { + deleteBlobLocked(blob); + userBlobs.remove(blob.getBlobHandle()); + } + } + // Delete redundant data from recommits. + if (session.getSessionId() != blob.getBlobId()) { + session.getSessionFile().delete(); + mActiveBlobIds.remove(session.getSessionId()); } getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) .remove(session.getSessionId()); @@ -1007,10 +1070,8 @@ public class BlobStoreManagerService extends SystemService { return shouldRemove; }); } - if (LOGV) { - Slog.v(TAG, "Completed idle maintenance; deleted " - + Arrays.toString(deletedBlobIds.toArray())); - } + Slog.d(TAG, "Completed idle maintenance; deleted " + + Arrays.toString(deletedBlobIds.toArray())); writeBlobSessionsAsync(); } @@ -1492,7 +1553,7 @@ public class BlobStoreManagerService extends SystemService { public int handleShellCommand(@NonNull ParcelFileDescriptor in, @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args) { - return (new BlobStoreManagerShellCommand(BlobStoreManagerService.this)).exec(this, + return new BlobStoreManagerShellCommand(BlobStoreManagerService.this).exec(this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); } } @@ -1684,6 +1745,40 @@ public class BlobStoreManagerService extends SystemService { } } + private void registerBlobStorePuller() { + mStatsManager.setPullAtomCallback( + FrameworkStatsLog.BLOB_INFO, + null, // use default PullAtomMetadata values + BackgroundThread.getExecutor(), + mStatsCallbackImpl + ); + } + + private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback { + @Override + public int onPullAtom(int atomTag, List<StatsEvent> data) { + switch (atomTag) { + case FrameworkStatsLog.BLOB_INFO: + return pullBlobData(atomTag, data); + default: + throw new UnsupportedOperationException("Unknown tagId=" + atomTag); + } + } + } + + private int pullBlobData(int atomTag, List<StatsEvent> data) { + synchronized (mBlobsLock) { + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); + for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { + final BlobMetadata blob = userBlobs.valueAt(j); + data.add(blob.dumpAsStatsEvent(atomTag)); + } + } + } + return StatsManager.PULL_SUCCESS; + } + private class LocalService extends BlobStoreManagerInternal { @Override public void onIdleMaintenance() { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index 22d5d11e9ccb..00983058841c 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -27,6 +27,8 @@ import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_RDWR; import static android.system.OsConstants.SEEK_SET; +import static android.text.format.Formatter.FLAG_IEC_UNITS; +import static android.text.format.Formatter.formatFileSize; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME; @@ -53,12 +55,15 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.blob.BlobStoreManagerService.DumpArgs; import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -97,7 +102,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { private File mSessionFile; @GuardedBy("mRevocableFds") - private ArrayList<RevocableFileDescriptor> mRevocableFds = new ArrayList<>(); + private final ArrayList<RevocableFileDescriptor> mRevocableFds = new ArrayList<>(); // This will be accessed from only one thread at any point of time, so no need to grab // a lock for this. @@ -207,27 +212,37 @@ class BlobStoreSession extends IBlobStoreSession.Stub { throw new IllegalStateException("Not allowed to write in state: " + stateToString(mState)); } + } - try { - return openWriteLocked(offsetBytes, lengthBytes); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); + FileDescriptor fd = null; + try { + fd = openWriteInternal(offsetBytes, lengthBytes); + final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); + synchronized (mSessionLock) { + if (mState != STATE_OPENED) { + IoUtils.closeQuietly(fd); + throw new IllegalStateException("Not allowed to write in state: " + + stateToString(mState)); + } + trackRevocableFdLocked(revocableFd); + return revocableFd.getRevocableFileDescriptor(); } + } catch (IOException e) { + IoUtils.closeQuietly(fd); + throw ExceptionUtils.wrap(e); } } - @GuardedBy("mSessionLock") @NonNull - private ParcelFileDescriptor openWriteLocked(@BytesLong long offsetBytes, + private FileDescriptor openWriteInternal(@BytesLong long offsetBytes, @BytesLong long lengthBytes) throws IOException { // TODO: Add limit on active open sessions/writes/reads - FileDescriptor fd = null; try { final File sessionFile = getSessionFile(); if (sessionFile == null) { throw new IllegalStateException("Couldn't get the file for this session"); } - fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600); + final FileDescriptor fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600); if (offsetBytes > 0) { final long curOffset = Os.lseek(fd, offsetBytes, SEEK_SET); if (curOffset != offsetBytes) { @@ -238,10 +253,10 @@ class BlobStoreSession extends IBlobStoreSession.Stub { if (lengthBytes > 0) { mContext.getSystemService(StorageManager.class).allocateBytes(fd, lengthBytes); } + return fd; } catch (ErrnoException e) { - e.rethrowAsIOException(); + throw e.rethrowAsIOException(); } - return createRevocableFdLocked(fd); } @Override @@ -253,29 +268,40 @@ class BlobStoreSession extends IBlobStoreSession.Stub { throw new IllegalStateException("Not allowed to read in state: " + stateToString(mState)); } + } - try { - return openReadLocked(); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); + FileDescriptor fd = null; + try { + fd = openReadInternal(); + final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); + synchronized (mSessionLock) { + if (mState != STATE_OPENED) { + IoUtils.closeQuietly(fd); + throw new IllegalStateException("Not allowed to read in state: " + + stateToString(mState)); + } + trackRevocableFdLocked(revocableFd); + return revocableFd.getRevocableFileDescriptor(); } + + } catch (IOException e) { + IoUtils.closeQuietly(fd); + throw ExceptionUtils.wrap(e); } } - @GuardedBy("mSessionLock") @NonNull - private ParcelFileDescriptor openReadLocked() throws IOException { - FileDescriptor fd = null; + private FileDescriptor openReadInternal() throws IOException { try { final File sessionFile = getSessionFile(); if (sessionFile == null) { throw new IllegalStateException("Couldn't get the file for this session"); } - fd = Os.open(sessionFile.getPath(), O_RDONLY, 0); + final FileDescriptor fd = Os.open(sessionFile.getPath(), O_RDONLY, 0); + return fd; } catch (ErrnoException e) { - e.rethrowAsIOException(); + throw e.rethrowAsIOException(); } - return createRevocableFdLocked(fd); } @Override @@ -396,7 +422,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } mState = state; - revokeAllFdsLocked(); + revokeAllFds(); if (sendCallback) { mListener.onStateChanged(this); @@ -427,26 +453,26 @@ class BlobStoreSession extends IBlobStoreSession.Stub { + ") didn't match the given BlobHandle.digest (" + BlobHandle.safeDigest(mBlobHandle.digest) + ")"); mState = STATE_VERIFIED_INVALID; + + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, getOwnerUid(), mSessionId, + getSize(), FrameworkStatsLog.BLOB_COMMITTED__RESULT__DIGEST_MISMATCH); sendCommitCallbackResult(COMMIT_RESULT_ERROR); } mListener.onStateChanged(this); } } - @GuardedBy("mSessionLock") - private void revokeAllFdsLocked() { - for (int i = mRevocableFds.size() - 1; i >= 0; --i) { - mRevocableFds.get(i).revoke(); + private void revokeAllFds() { + synchronized (mRevocableFds) { + for (int i = mRevocableFds.size() - 1; i >= 0; --i) { + mRevocableFds.get(i).revoke(); + } + mRevocableFds.clear(); } - mRevocableFds.clear(); } @GuardedBy("mSessionLock") - @NonNull - private ParcelFileDescriptor createRevocableFdLocked(FileDescriptor fd) - throws IOException { - final RevocableFileDescriptor revocableFd = - new RevocableFileDescriptor(mContext, fd); + private void trackRevocableFdLocked(RevocableFileDescriptor revocableFd) { synchronized (mRevocableFds) { mRevocableFds.add(revocableFd); } @@ -455,7 +481,6 @@ class BlobStoreSession extends IBlobStoreSession.Stub { mRevocableFds.remove(revocableFd); } }); - return revocableFd.getRevocableFileDescriptor(); } @Nullable @@ -510,6 +535,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { fout.println("ownerUid: " + mOwnerUid); fout.println("ownerPkg: " + mOwnerPackageName); fout.println("creation time: " + BlobStoreUtils.formatTime(mCreationTimeMs)); + fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); fout.println("blobHandle:"); fout.increaseIndent(); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 280a6870a5e1..5ceea2aedb85 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -82,10 +82,10 @@ import android.os.BatteryStats; import android.os.Build; import android.os.Environment; import android.os.Handler; +import android.os.IDeviceIdleController; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.PowerWhitelistManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -93,7 +93,6 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings.Global; import android.telephony.TelephonyManager; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Slog; @@ -205,6 +204,10 @@ public class AppStandbyController implements AppStandbyInternal { */ private static final long WAIT_FOR_ADMIN_DATA_TIMEOUT_MS = 10_000; + private static final int HEADLESS_APP_CHECK_FLAGS = + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS; + // To name the lock for stack traces static class Lock {} @@ -233,8 +236,8 @@ public class AppStandbyController implements AppStandbyInternal { * Set of system apps that are headless (don't have any declared activities, enabled or * disabled). Presence in this map indicates that the app is a headless system app. */ - @GuardedBy("mAppIdleLock") - private final ArrayMap<String, Boolean> mHeadlessSystemApps = new ArrayMap<>(); + @GuardedBy("mHeadlessSystemApps") + private final ArraySet<String> mHeadlessSystemApps = new ArraySet<>(); private final CountDownLatch mAdminDataAvailableLatch = new CountDownLatch(1); @@ -387,6 +390,7 @@ public class AppStandbyController implements AppStandbyInternal { DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver(); IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING); deviceStates.addAction(BatteryManager.ACTION_DISCHARGING); + deviceStates.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); mContext.registerReceiver(deviceStateReceiver, deviceStates); synchronized (mAppIdleLock) { @@ -442,12 +446,16 @@ public class AppStandbyController implements AppStandbyInternal { mSystemServicesReady = true; + // Offload to handler thread to avoid boot time impact. + mHandler.post(mInjector::updatePowerWhitelistCache); + boolean userFileExists; synchronized (mAppIdleLock) { userFileExists = mAppIdleHistory.userFileExists(UserHandle.USER_SYSTEM); } - loadHeadlessSystemAppCache(); + // Offload to handler thread to avoid boottime impact. + mHandler.post(this::loadHeadlessSystemAppCache); if (mPendingInitializeDefaults || !userFileExists) { initializeDefaultsForSystemApps(UserHandle.USER_SYSTEM); @@ -1079,15 +1087,11 @@ public class AppStandbyController implements AppStandbyInternal { return STANDBY_BUCKET_EXEMPTED; } if (mSystemServicesReady) { - try { - // We allow all whitelisted apps, including those that don't want to be whitelisted - // for idle mode, because app idle (aka app standby) is really not as big an issue - // for controlling who participates vs. doze mode. - if (mInjector.isNonIdleWhitelisted(packageName)) { - return STANDBY_BUCKET_EXEMPTED; - } - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); + // We allow all whitelisted apps, including those that don't want to be whitelisted + // for idle mode, because app idle (aka app standby) is really not as big an issue + // for controlling who participates vs. doze mode. + if (mInjector.isNonIdleWhitelisted(packageName)) { + return STANDBY_BUCKET_EXEMPTED; } if (isActiveDeviceAdmin(packageName, userId)) { @@ -1121,7 +1125,9 @@ public class AppStandbyController implements AppStandbyInternal { } private boolean isHeadlessSystemApp(String packageName) { - return mHeadlessSystemApps.containsKey(packageName); + synchronized (mHeadlessSystemApps) { + return mHeadlessSystemApps.contains(packageName); + } } @Override @@ -1692,24 +1698,29 @@ public class AppStandbyController implements AppStandbyInternal { return; } try { - PackageInfo pi = mPackageManager.getPackageInfoAsUser(packageName, - PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS, - userId); + PackageInfo pi = mPackageManager.getPackageInfoAsUser( + packageName, HEADLESS_APP_CHECK_FLAGS, userId); evaluateSystemAppException(pi); } catch (PackageManager.NameNotFoundException e) { - mHeadlessSystemApps.remove(packageName); + synchronized (mHeadlessSystemApps) { + mHeadlessSystemApps.remove(packageName); + } } } - private void evaluateSystemAppException(@Nullable PackageInfo pkgInfo) { - if (pkgInfo.applicationInfo != null && pkgInfo.applicationInfo.isSystemApp()) { - synchronized (mAppIdleLock) { - if (pkgInfo.activities == null || pkgInfo.activities.length == 0) { - // Headless system app. - mHeadlessSystemApps.put(pkgInfo.packageName, true); - } else { - mHeadlessSystemApps.remove(pkgInfo.packageName); - } + /** Returns true if the exception status changed. */ + private boolean evaluateSystemAppException(@Nullable PackageInfo pkgInfo) { + if (pkgInfo == null || pkgInfo.applicationInfo == null + || (!pkgInfo.applicationInfo.isSystemApp() + && !pkgInfo.applicationInfo.isUpdatedSystemApp())) { + return false; + } + synchronized (mHeadlessSystemApps) { + if (pkgInfo.activities == null || pkgInfo.activities.length == 0) { + // Headless system app. + return mHeadlessSystemApps.add(pkgInfo.packageName); + } else { + return mHeadlessSystemApps.remove(pkgInfo.packageName); } } } @@ -1746,15 +1757,19 @@ public class AppStandbyController implements AppStandbyInternal { } } - /** Call on a system update to temporarily reset system app buckets. */ + /** Call on system boot to get the initial set of headless system apps. */ private void loadHeadlessSystemAppCache() { Slog.d(TAG, "Loading headless system app cache. appIdleEnabled=" + mAppIdleEnabled); final List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser( - PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS, - UserHandle.USER_SYSTEM); + HEADLESS_APP_CHECK_FLAGS, UserHandle.USER_SYSTEM); final int packageCount = packages.size(); for (int i = 0; i < packageCount; i++) { - evaluateSystemAppException(packages.get(i)); + PackageInfo pkgInfo = packages.get(i); + if (pkgInfo != null && evaluateSystemAppException(pkgInfo)) { + mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, + UserHandle.USER_SYSTEM, -1, pkgInfo.packageName) + .sendToTarget(); + } } } @@ -1794,8 +1809,6 @@ public class AppStandbyController implements AppStandbyInternal { + "): " + mCarrierPrivilegedApps); } - final long now = System.currentTimeMillis(); - pw.println(); pw.println("Settings:"); @@ -1852,12 +1865,17 @@ public class AppStandbyController implements AppStandbyInternal { pw.println(); pw.println("mHeadlessSystemApps=["); - for (int i = mHeadlessSystemApps.size() - 1; i >= 0; --i) { - pw.print(mHeadlessSystemApps.keyAt(i)); - pw.println(","); + synchronized (mHeadlessSystemApps) { + for (int i = mHeadlessSystemApps.size() - 1; i >= 0; --i) { + pw.print(" "); + pw.print(mHeadlessSystemApps.valueAt(i)); + pw.println(","); + } } pw.println("]"); pw.println(); + + mInjector.dump(pw); } /** @@ -1874,7 +1892,7 @@ public class AppStandbyController implements AppStandbyInternal { private PackageManagerInternal mPackageManagerInternal; private DisplayManager mDisplayManager; private PowerManager mPowerManager; - private PowerWhitelistManager mPowerWhitelistManager; + private IDeviceIdleController mDeviceIdleController; private CrossProfileAppsInternal mCrossProfileAppsInternal; int mBootPhase; /** @@ -1882,6 +1900,11 @@ public class AppStandbyController implements AppStandbyInternal { * automatically placed in the RESTRICTED bucket. */ long mAutoRestrictedBucketDelayMs = ONE_DAY; + /** + * Cached set of apps that are power whitelisted, including those not whitelisted from idle. + */ + @GuardedBy("mPowerWhitelistedApps") + private final ArraySet<String> mPowerWhitelistedApps = new ArraySet<>(); Injector(Context context, Looper looper) { mContext = context; @@ -1898,7 +1921,8 @@ public class AppStandbyController implements AppStandbyInternal { void onBootPhase(int phase) { if (phase == PHASE_SYSTEM_SERVICES_READY) { - mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class); + mDeviceIdleController = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mBatteryStats = IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); @@ -1949,8 +1973,34 @@ public class AppStandbyController implements AppStandbyInternal { return mBatteryManager.isCharging(); } - boolean isNonIdleWhitelisted(String packageName) throws RemoteException { - return mPowerWhitelistManager.isWhitelisted(packageName, false); + boolean isNonIdleWhitelisted(String packageName) { + if (mBootPhase < PHASE_SYSTEM_SERVICES_READY) { + return false; + } + synchronized (mPowerWhitelistedApps) { + return mPowerWhitelistedApps.contains(packageName); + } + } + + private void updatePowerWhitelistCache() { + if (mBootPhase < PHASE_SYSTEM_SERVICES_READY) { + return; + } + try { + // Don't call out to DeviceIdleController with the lock held. + final String[] whitelistedPkgs = + mDeviceIdleController.getFullPowerWhitelistExceptIdle(); + synchronized (mPowerWhitelistedApps) { + mPowerWhitelistedApps.clear(); + final int len = whitelistedPkgs.length; + for (int i = 0; i < len; ++i) { + mPowerWhitelistedApps.add(whitelistedPkgs[i]); + } + } + } catch (RemoteException e) { + // Should not happen. + Slog.wtf(TAG, "Failed to get power whitelist", e); + } } boolean isRestrictedBucketEnabled() { @@ -2037,6 +2087,19 @@ public class AppStandbyController implements AppStandbyInternal { } return mCrossProfileAppsInternal.getTargetUserProfiles(pkg, userId); } + + void dump(PrintWriter pw) { + pw.println("mPowerWhitelistedApps=["); + synchronized (mPowerWhitelistedApps) { + for (int i = mPowerWhitelistedApps.size() - 1; i >= 0; --i) { + pw.print(" "); + pw.print(mPowerWhitelistedApps.valueAt(i)); + pw.println(","); + } + } + pw.println("]"); + pw.println(); + } } class AppStandbyHandler extends Handler { @@ -2122,6 +2185,11 @@ public class AppStandbyController implements AppStandbyInternal { case BatteryManager.ACTION_DISCHARGING: setChargingState(false); break; + case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: + if (mSystemServicesReady) { + mHandler.post(mInjector::updatePowerWhitelistCache); + } + break; } } } diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index 15a2f22e0fea..8a0f66040711 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -88,27 +88,4 @@ java_sdk_library { "com.android.os.statsd", "test_com.android.os.statsd", ], -} - -android_test { - name: "FrameworkStatsdTest", - platform_apis: true, - srcs: [ - // TODO(b/147705194): Use framework-statsd as a lib dependency instead. - ":framework-statsd-sources", - "test/**/*.java", - ], - manifest: "test/AndroidManifest.xml", - static_libs: [ - "androidx.test.rules", - "truth-prebuilt", - ], - libs: [ - "android.test.runner.stubs", - "android.test.base.stubs", - ], - test_suites: [ - "device-tests", - ], -} - +}
\ No newline at end of file diff --git a/apex/statsd/framework/test/Android.bp b/apex/statsd/framework/test/Android.bp new file mode 100644 index 000000000000..b113d595b57c --- /dev/null +++ b/apex/statsd/framework/test/Android.bp @@ -0,0 +1,36 @@ +// 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. + +android_test { + name: "FrameworkStatsdTest", + platform_apis: true, + srcs: [ + // TODO(b/147705194): Use framework-statsd as a lib dependency instead. + ":framework-statsd-sources", + "**/*.java", + ], + manifest: "AndroidManifest.xml", + static_libs: [ + "androidx.test.rules", + "truth-prebuilt", + ], + libs: [ + "android.test.runner.stubs", + "android.test.base.stubs", + ], + test_suites: [ + "device-tests", + "mts", + ], +}
\ No newline at end of file diff --git a/apex/statsd/framework/test/AndroidTest.xml b/apex/statsd/framework/test/AndroidTest.xml new file mode 100644 index 000000000000..fb519150ecd5 --- /dev/null +++ b/apex/statsd/framework/test/AndroidTest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs Tests for Statsd."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="FrameworkStatsdTest.apk" /> + <option name="install-arg" value="-g" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="mts" /> + <option name="test-tag" value="FrameworkStatsdTest" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.os.statsd.framework.test" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> + + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.os.statsd" /> + </object> +</configuration>
\ No newline at end of file diff --git a/api/test-current.txt b/api/test-current.txt index 1142fb631891..fc82bc788ac7 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -3323,6 +3323,7 @@ package android.provider { field public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component"; field public static final String NOTIFICATION_BADGING = "notification_badging"; field public static final String POWER_MENU_LOCKED_SHOW_CONTENT = "power_menu_locked_show_content"; + field public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds"; field public static final String USER_SETUP_COMPLETE = "user_setup_complete"; field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; @@ -5102,6 +5103,7 @@ package android.view { method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut(); method public int getType(); method public boolean hasAccess(int); + field public static final int FLAG_TRUSTED = 128; // 0x80 field public static final int TYPE_EXTERNAL = 2; // 0x2 field public static final int TYPE_INTERNAL = 1; // 0x1 field public static final int TYPE_OVERLAY = 4; // 0x4 diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java index 2adbc1f6e1ae..7c30c8b1e1dd 100644 --- a/cmds/am/src/com/android/commands/am/Instrument.java +++ b/cmds/am/src/com/android/commands/am/Instrument.java @@ -17,8 +17,8 @@ package com.android.commands.am; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS; +import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS; -import static android.app.ActivityManager.INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL; import android.app.IActivityManager; import android.app.IInstrumentationWatcher; @@ -512,7 +512,7 @@ public class Instrument { flags |= INSTR_FLAG_DISABLE_TEST_API_CHECKS; } if (disableIsolatedStorage) { - flags |= INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL; + flags |= INSTR_FLAG_DISABLE_ISOLATED_STORAGE; } if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId, abi)) { diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp index fb5830506925..878cef94b674 100644 --- a/cmds/idmap2/Android.bp +++ b/cmds/idmap2/Android.bp @@ -23,9 +23,16 @@ cc_defaults { "misc-*", "readability-*", ], + tidy_checks_as_errors: [ + "modernize-*", + "-modernize-avoid-c-arrays", + "-modernize-use-trailing-return-type", + "android-*", + "misc-*", + "readability-*", + ], tidy_flags: [ "-system-headers", - "-warnings-as-errors=*", ], } diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 47bab2947aaf..6f952f637506 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -267,8 +267,11 @@ void StatsService::dumpIncidentSection(int out) { for (const ConfigKey& configKey : mConfigManager->GetAllConfigKeys()) { uint64_t reportsListToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS_LIST); + // Don't include the current bucket to avoid skipping buckets. + // If we need to include the current bucket later, consider changing to NO_TIME_CONSTRAINTS + // or other alternatives to avoid skipping buckets for pulled metrics. mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), - true /* includeCurrentBucket */, false /* erase_data */, + false /* includeCurrentBucket */, false /* erase_data */, ADB_DUMP, FAST, &proto); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 1823bad0076d..dc20a02156cb 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -43,6 +43,7 @@ import "frameworks/base/core/proto/android/server/location/enums.proto"; import "frameworks/base/core/proto/android/service/procstats_enum.proto"; import "frameworks/base/core/proto/android/service/usb.proto"; import "frameworks/base/core/proto/android/stats/connectivity/network_stack.proto"; +import "frameworks/base/core/proto/android/stats/connectivity/tethering.proto"; import "frameworks/base/core/proto/android/stats/dnsresolver/dns_resolver.proto"; import "frameworks/base/core/proto/android/stats/devicepolicy/device_policy.proto"; import "frameworks/base/core/proto/android/stats/devicepolicy/device_policy_enums.proto"; @@ -465,13 +466,33 @@ message Atom { 288 [(module) = "car"]; CarUserHalSetUserAssociationResponseReported car_user_hal_set_user_association_response_reported = 289 [(module) = "car"]; + NetworkIpProvisioningReported network_ip_provisioning_reported = + 290 [(module) = "network_stack"]; + NetworkDhcpRenewReported network_dhcp_renew_reported = 291 [(module) = "network_stack"]; + NetworkValidationReported network_validation_reported = 292 [(module) = "network_stack"]; + NetworkStackQuirkReported network_stack_quirk_reported = 293 [(module) = "network_stack"]; + MediametricsAudioRecordDeviceUsageReported mediametrics_audiorecorddeviceusage_reported = + 294; + MediametricsAudioThreadDeviceUsageReported mediametrics_audiothreaddeviceusage_reported = + 295; + MediametricsAudioTrackDeviceUsageReported mediametrics_audiotrackdeviceusage_reported = + 296; + MediametricsAudioDeviceConnectionReported mediametrics_audiodeviceconnection_reported = + 297; + BlobCommitted blob_committed = 298 [(module) = "framework"]; + BlobLeased blob_leased = 299 [(module) = "framework"]; + BlobOpened blob_opened = 300 [(module) = "framework"]; + ContactsProviderStatusReported contacts_provider_status_reported = 301; + KeystoreKeyEventReported keystore_key_event_reported = 302; + NetworkTetheringReported network_tethering_reported = + 303 [(module) = "network_tethering"]; // StatsdStats tracks platform atoms with ids upto 500. // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value. } // Pulled events will start at field 10000. - // Next: 10081 + // Next: 10084 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000 [(module) = "framework"]; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001 [(module) = "framework"]; @@ -562,7 +583,7 @@ message Atom { SimSlotState sim_slot_state = 10078 [(module) = "telephony"]; SupportedRadioAccessFamily supported_radio_access_family = 10079 [(module) = "telephony"]; SettingSnapshot setting_snapshot = 10080 [(module) = "framework"]; - //10081 free for use + BlobInfo blob_info = 10081 [(module) = "framework"]; DataUsageBytesTransfer data_usage_bytes_transfer = 10082 [(module) = "framework"]; BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered = 10083 [(module) = "framework"]; @@ -4892,6 +4913,94 @@ message SnapshotMergeReported { optional int64 cow_file_size_bytes = 5; } +/** + * Event representing when BlobStoreManager.Session#commit() is called + * + * Logged from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobCommitted { + // Uid of the Blob committer + optional int32 uid = 1 [(is_uid) = true]; + + // Id of the Blob committed + optional int64 blob_id = 2; + + // Size of the Blob + optional int64 size = 3; + + enum Result { + UNKNOWN = 0; + // Commit Succeeded + SUCCESS = 1; + // Commit Failed: Error occurred during commit + ERROR_DURING_COMMIT = 2; + // Commit Failed: Digest of the data did not match Blob digest + DIGEST_MISMATCH = 3; + } + optional Result result = 4; +} + +/** + * Event representing when BlobStoreManager#acquireLease() is called + * + * Logged from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobLeased{ + // Uid of the Blob leasee + optional int32 uid = 1 [(is_uid) = true]; + + // Id of the Blob leased or 0 if the Blob does not exist + optional int64 blob_id = 2; + + // Size of the Blob or 0 if the Blob does not exist + optional int64 size = 3; + + enum Result { + UNKNOWN = 0; + // Lease Succeeded + SUCCESS = 1; + // Lease Failed: Blob does not exist + BLOB_DNE = 2; + // Lease Failed: Leasee does not have access to the Blob + ACCESS_NOT_ALLOWED = 3; + // Lease Failed: Leasee requested an invalid expiry duration + LEASE_EXPIRY_INVALID = 4; + // Lease Failed: Leasee has exceeded the total data lease limit + DATA_SIZE_LIMIT_EXCEEDED = 5; + } + optional Result result = 4; +} + +/** + * Event representing when BlobStoreManager#openBlob() is called + * + * Logged from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobOpened{ + // Uid of the Blob opener + optional int32 uid = 1 [(is_uid) = true]; + + // Id of the Blob opened or 0 if the Blob does not exist + optional int64 blob_id = 2; + + // Size of the Blob or 0 if the Blob does not exist + optional int64 size = 3; + + enum Result { + UNKNOWN = 0; + // Open Succeeded + SUCCESS = 1; + // Open Failed: Blob does not exist + BLOB_DNE = 2; + // Open Failed: Opener does not have access to the Blob + ACCESS_NOT_ALLOWED = 3; + } + optional Result result = 4; +} + ////////////////////////////////////////////////////////////////////// // Pulled atoms below this line // ////////////////////////////////////////////////////////////////////// @@ -6785,6 +6894,24 @@ message AppCompacted { } /** + * Logs when a Tethering event occurs. + * + */ +message NetworkTetheringReported { + // tethering error code + optional android.stats.connectivity.ErrorCode error_code = 1; + + // tethering downstream type + optional android.stats.connectivity.DownstreamType downstream_type = 2; + + // transport type of upstream network + optional android.stats.connectivity.UpstreamType upstream_type = 3; + + // The user type of Tethering + optional android.stats.connectivity.UserType user_type= 4; +} + +/** * Logs a DNS lookup operation initiated by the system resolver on behalf of an application * invoking native APIs such as getaddrinfo() or Java APIs such as Network#getAllByName(). * @@ -6822,6 +6949,172 @@ message NetworkDnsEventReported { } /** + * logs the CapportApiData info + * Logged from: + * packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java + */ +message CapportApiData { + // The TTL of the network connection provided by captive portal + optional int32 remaining_ttl_secs = 1; + + // The limit traffic data of the network connection provided by captive portal + optional int32 remaining_bytes = 2; + + // Is portal url option included in the DHCP packet (Yes, No) + optional bool has_portal_url = 3; + + // Is venue info (e.g. store info, maps, flight status) included (Yes, No) + optional bool has_venue_info = 4; +} + +/** + * logs a network Probe Event + * Logged from: + * packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java + */ +message ProbeEvent { + // The probe type (http or https, or captive portal API...) + optional android.stats.connectivity.ProbeType probe_type = 1; + + // The latency in microseconds of the probe event + optional int32 latency_micros = 2; + + // The result of the probe event + optional android.stats.connectivity.ProbeResult probe_result = 3; + + // The CaptivePortal API info + optional CapportApiData capport_api_data = 4; +} + +/** + * log each ProbeEvent in ProbeEvents + * Logged from: + * packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java + */ +message ProbeEvents { + // Record probe event during the validation + repeated ProbeEvent probe_event = 1; +} + +/** + * The DHCP (Dynamic Host Configuration Protocol) session info + * Logged from: + * packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java + */ +message DhcpSession { + // The DHCP Feature(s) enabled in this session + repeated android.stats.connectivity.DhcpFeature used_features = 1; + + // The discover packet (re)transmit count + optional int32 discover_count = 2; + + // The request packet (re)transmit count + optional int32 request_count = 3; + + // The IPv4 address conflict count + // (only be meaningful when duplicate address detection is enabled) + optional int32 conflict_count = 4; + + // The DHCP packet parsing error code in this session + // (defined in android.net.metrics.DhcpErrorEvent) + repeated android.stats.connectivity.DhcpErrorCode error_code = 5; + + // The result of DHCP hostname transliteration + optional android.stats.connectivity.HostnameTransResult ht_result = 6; +} + +/** + * Logs Network IP provisioning event + * Logged from: + * packages/modules/NetworkStack/src/com/android/networkstack/metrics/NetworkIpProvisioningMetrics.java + */ +message NetworkIpProvisioningReported { + // Transport type (WIFI, CELLULAR, BLUETOOTH, ..) + optional android.stats.connectivity.TransportType transport_type = 1; + + // The latency in microseconds of IP Provisioning over IPV4 + optional int32 ipv4_latency_micros = 2; + + // The latency in microseconds of IP Provisioning over IPV6 + optional int32 ipv6_latency_micros = 3; + + // The time duration between provisioning start and end (success or failure) + optional int64 provisioning_duration_micros = 4; + + // The specific disconnect reason for this IP provisioning + optional android.stats.connectivity.DisconnectCode disconnect_code = 5; + + // Log DHCP session info (Only valid for IPv4) + optional DhcpSession dhcp_session = 6 [(log_mode) = MODE_BYTES]; + + // The random number between 0 ~ 999 for sampling + optional int32 random_number = 7; +} + +/** + * Logs Network DHCP Renew event + * Logged from: + * packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java + */ +message NetworkDhcpRenewReported { + // Transport type (WIFI, CELLULAR, BLUETOOTH, ..) + optional android.stats.connectivity.TransportType transport_type = 1; + + // The request packet (re)transmit count + optional int32 request_count = 2; + + // The latency in microseconds of DHCP Renew + optional int32 latency_micros = 3; + + // The DHCP error code is defined in android.net.metrics.DhcpErrorEvent + optional android.stats.connectivity.DhcpErrorCode error_code = 4; + + // The result of DHCP renew + optional android.stats.connectivity.DhcpRenewResult renew_result = 5; + + // The random number between 0 ~ 999 for sampling + optional int32 random_number = 6; +} + +/** + * Logs Network Validation event + * Logged from: + * packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java + */ +message NetworkValidationReported { + // Transport type (WIFI, CELLULAR, BLUETOOTH, ..) + optional android.stats.connectivity.TransportType transport_type = 1; + + // Record each probe event + optional ProbeEvents probe_events = 2 [(log_mode) = MODE_BYTES]; + + // The result of the network validation + optional android.stats.connectivity.ValidationResult validation_result = 3; + + // The latency in microseconds of network validation + optional int32 latency_micros = 4; + + // The validation index (the first validation attempt or second, third...) + optional int32 validation_index = 5; + + // The random number between 0 ~ 999 for sampling + optional int32 random_number = 6; +} + +/** + * Logs NetworkStack Quirk event + * Logged from: + * packages/modules/NetworkStack/src/com/android/networkstack/ + */ +message NetworkStackQuirkReported { + // Transport type (WIFI, CELLULAR, BLUETOOTH, ..) + optional android.stats.connectivity.TransportType transport_type = 1; + + // Record each Quirk event + optional android.stats.connectivity.NetworkQuirkEvent event = 2; +} + +/** * Logs when a data stall event occurs. * * Log from: @@ -9637,6 +9930,48 @@ message TvCasSessionOpenStatus { } /** + * Logs for ContactsProvider general usage. + * This is atom ID 301. + * + * Logged from: + * packages/providers/ContactsProvider/src/com/android/providers/contacts/ContactsProvider2.java + */ +message ContactsProviderStatusReported { + enum ApiType { + UNKNOWN_API = 0; + QUERY = 1; + // INSERT includes insert and bulkInsert, and inserts triggered by applyBatch. + INSERT = 2; + // UPDATE and DELETE includes update/delete and the ones triggered by applyBatch. + UPDATE = 3; + DELETE = 4; + } + + enum ResultType { + UNKNOWN_RESULT = 0; + SUCCESS = 1; + FAIL = 2; + ILLEGAL_ARGUMENT = 3; + UNSUPPORTED_OPERATION = 4; + } + + enum CallerType { + UNSPECIFIED_CALLER_TYPE = 0; + CALLER_IS_SYNC_ADAPTER = 1; + CALLER_IS_NOT_SYNC_ADAPTER = 2; + } + + optional ApiType api_type = 1; + // Defined in + // packages/providers/ContactsProvider/src/com/android/providers/contacts/ContactsProvider2.java + optional int32 uri_type = 2; + optional CallerType caller_type = 3; + optional ResultType result_type = 4; + optional int32 result_count = 5; + optional int64 latency_micros = 6; +} + +/** * Logs when an app is frozen or unfrozen. * * Logged from: @@ -9829,7 +10164,7 @@ message GnssStats { optional int64 time_to_first_fix_reports = 3; // Total pulled reported time to first fix (in milli-seconds) since boot - optional int64 time_to_first_fix_milli_s = 4; + optional int64 time_to_first_fix_millis = 4; // Number of position accuracy reports since boot optional int64 position_accuracy_reports = 5; @@ -10378,3 +10713,414 @@ message AssistantInvocationReported { // Whether the Assistant handles were showing at the time of invocation. optional bool assistant_handles_showing = 6; } + +/** + * Logs when an AudioRecord finishes running on an audio device + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioRecordDeviceUsageReported { + // The devices connected to this AudioRecord. + // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2". + // See lookup<INPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string devices = 1; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 2; + + // The amount of time spent in the device as measured by the active track in AudioFlinger. + optional int64 device_time_nanos = 3; + + // The audio data format used for encoding. + // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t + optional string encoding = 4; + + // The client-server buffer framecount. + // The framecount is generally between 960 - 48000 for PCM encoding. + // The framecount represents raw buffer size in bytes for non-PCM encoding. + optional int32 frame_count = 5; + + // The number of audio intervals (contiguous, continuous playbacks). + optional int32 interval_count = 6; + + // The sample rate of the AudioRecord. + // A number generally between 8000-96000 (frames per second). + optional int32 sample_rate = 7; + + // The audio input flags used to construct the AudioRecord. + // A string OR from system/media/audio/include/system/audio-base.h audio_input_flags_t + optional string flags = 8; + + // The santized package name of the audio client associated with the AudioRecord. + // See getSanitizedPackageNameAndVersionCode() in + // frameworks/av/services/mediametrics/MediaMetricsService.cpp + optional string package_name = 9; + + // The selected device id (nonzero if a non-default device is selected) + optional int32 selected_device_id = 10; + + // The caller of the AudioRecord. + // See lookup<CALLER_NAME>() in frameworks/av/services/mediametrics/AudioTypes.cpp + optional string caller = 11; + + // The audio source for AudioRecord. + // An enumeration from system/media/audio/include/system/audio-base.h audio_source_t + optional string source = 12; +} + +/** + * Logs when an AudioThread finishes running on an audio device + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioThreadDeviceUsageReported { + // The devices connected to this audio thread. + // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2". + // (for record threads): + // See lookup<INPUT_DEVICE> in frameworks/av/services/mediametrics/AudioTypes.cpp + // (for playback threads): + // See lookup<OUTPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string devices = 1; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 2; + + // The amount of time spent in the device as measured by the active track in AudioFlinger. + optional int64 device_time_nanos = 3; + + // The audio data format used for encoding. + // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t + optional string encoding = 4; + + // The framecount of the buffer delivered to (or from) the HAL. + // The framecount is generally ~960 for PCM encoding. + // The framecount represents raw buffer size in bytes for non-PCM encoding. + optional int32 frame_count = 5; + + // The number of audio intervals (contiguous, continuous playbacks). + optional int32 interval_count = 6; + + // The sample rate of the audio thread. + // A number generally between 8000-96000 (frames per second). + optional int32 sample_rate = 7; + + // The audio flags used to construct the thread + // (for record threads): + // A string OR from system/media/audio/include/system/audio-base.h audio_input_flags_t + // (for playback threads): + // A string OR from system/media/audio/include/system/audio-base.h audio_output_flags_t + optional string flags = 8; + + // The number of underruns encountered for a playback thread or the + // number of overruns encountered for a capture thread. + optional int32 xruns = 9; + + // The type of thread + // A thread type enumeration from + // frameworks/av/mediametrics/services/Translate.h + optional string type = 10; +} + +/** + * Logs when an AudioTrack finishes running on an audio device + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioTrackDeviceUsageReported { + // The output devices connected to this AudioTrack. + // A string OR of various output device categories, e.g. "DEVICE1|DEVICE2". + // See lookup<OUTPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string devices = 1; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 2; + + // The amount of time spent in the device as measured by the active track in AudioFlinger. + optional int64 device_time_nanos = 3; + + // The audio data format used for encoding. + // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t + optional string encoding = 4; + + // The client-server buffer framecount. + // The framecount is generally between 960 - 48000 for PCM encoding. + // The framecount represents raw buffer size in bytes for non-PCM encoding. + // A static track (see traits) may have a very large framecount. + optional int32 frame_count = 5; + + // The number of audio intervals (contiguous, continuous playbacks). + optional int32 interval_count = 6; + + // The sample rate of the AudioTrack. + // A number generally between 8000-96000 (frames per second). + optional int32 sample_rate = 7; + + // The audio flags used to construct the AudioTrack. + // A string OR from system/media/audio/include/system/audio-base.h audio_output_flags_t + optional string flags = 8; + + // The number of underruns encountered. + optional int32 xruns = 9; + + // The santized package name of the audio client associated with the AudioTrack. + // See getSanitizedPackageNameAndVersionCode() in + // frameworks/av/services/mediametrics/MediaMetricsService.cpp + optional string package_name = 10; + + // The latency of the last sample in the buffer in milliseconds. + optional float device_latency_millis = 11; + + // The startup time in milliseconds from start() to sample played. + optional float device_startup_millis = 12; + + // The average volume of the track on the device [ 0.f - 1.f ] + optional float device_volume = 13; + + // The selected device id (nonzero if a non-default device is selected) + optional int32 selected_device_id = 14; + + // The stream_type category for the AudioTrack. + // An enumeration from system/media/audio/include/system/audio-base.h audio_stream_type_t + optional string stream_type = 15; + + // The usage for the AudioTrack. + // An enumeration from system/media/audio/include/system/audio-base.h audio_usage_t + optional string usage = 16; + + // The content type of the AudioTrack. + // An enumeration from system/media/audio/include/system/audio-base.h audio_content_type_t + optional string content_type = 17; + + // The caller of the AudioTrack. + // See lookup<CALLER_NAME>() in frameworks/av/services/mediametrics/AudioTypes.cpp + optional string caller = 18; + + // The traits of the AudioTrack. + // A string OR of different traits, may be empty string. + // Only "static" is supported for R. + // See lookup<TRACK_TRAITS>() in frameworks/av/services/mediametrics/AudioTypes.cpp + optional string traits = 19; +} + +/** + * Logs the status of an audio device connection attempt. + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioDeviceConnectionReported { + // The input devices represented by this report. + // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2". + // See lookup<INPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string input_devices = 1; + + // The output devices represented by this report. + // A string OR of various output device categories. + // See lookup<OUTPUT_DEVICE>() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string output_devices = 2; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 3; + + // The result of the audio device connection. + // 0 indicates success: connection verified. + // 1 indicates unknown: connection not verified or not known if diverted properly. + // Other values indicate specific status. + // See DeviceConnectionResult in frameworks/av/services/mediametrics/AudioTypes.h + optional int32 result = 4; + + // Average milliseconds of time to connect + optional float time_to_connect_millis = 5; + + // Number of connections if aggregated statistics, otherwise 1. + optional int32 connection_count = 6; +} + +/** + * Logs: i) creation of different types of cryptographic keys in the keystore, + * ii) operations performed using the keys, + * iii) attestation of the keys + * Logged from: system/security/keystore/key_event_log_handler.cpp + */ +message KeystoreKeyEventReported { + + enum Algorithm { + /** Asymmetric algorithms. */ + RSA = 1; + // 2 removed, do not reuse. + EC = 3; + /** Block cipher algorithms */ + AES = 32; + TRIPLE_DES = 33; + /** MAC algorithms */ + HMAC = 128; + }; + /** Algorithm associated with the key */ + optional Algorithm algorithm = 1; + + /** Size of the key */ + optional int32 key_size = 2; + + enum KeyOrigin { + /** Generated in keymaster. Should not exist outside the TEE. */ + GENERATED = 0; + /** Derived inside keymaster. Likely exists off-device. */ + DERIVED = 1; + /** Imported into keymaster. Existed as cleartext in Android. */ + IMPORTED = 2; + /** Keymaster did not record origin. */ + UNKNOWN = 3; + /** Securely imported into Keymaster. */ + SECURELY_IMPORTED = 4; + }; + /* Logs whether the key was generated, imported, securely imported, or derived.*/ + optional KeyOrigin key_origin = 3; + + enum HardwareAuthenticatorType { + NONE = 0; + PASSWORD = 1; + FINGERPRINT = 2; + // Additional entries must be powers of 2. + }; + /** + * What auth types does this key require? If none, + * then no auth required. + */ + optional HardwareAuthenticatorType user_auth_type = 4; + + /** + * If user authentication is required, is the requirement time based? If it + * is not time based then this field will not be used and the key is per + * operation. Per operation keys must be user authenticated on each usage. + */ + optional int32 user_auth_key_timeout_secs = 5; + + /** + * padding mode, digest, block_mode and purpose should ideally be repeated + * fields. However, since statsd does not support repeated fields in + * pushed atoms, they are represented using bitmaps. + */ + + /** Track which padding mode is being used.*/ + optional int32 padding_mode_bitmap = 6; + + /** Track which digest is being used. */ + optional int32 digest_bitmap = 7; + + /** Track what block mode is being used (for encryption). */ + optional int32 block_mode_bitmap = 8; + + /** Track what purpose is this key serving. */ + optional int32 purpose_bitmap = 9; + + enum EcCurve { + P_224 = 0; + P_256 = 1; + P_384 = 2; + P_521 = 3; + }; + /** Which ec curve was selected if elliptic curve cryptography is in use **/ + optional EcCurve ec_curve = 10; + + enum KeyBlobUsageRequirements { + STANDALONE = 0; + REQUIRES_FILE_SYSTEM = 1; + }; + /** Standalone or is a file system required */ + optional KeyBlobUsageRequirements key_blob_usage_reqs = 11; + + enum Type { + key_operation = 0; + key_creation = 1; + key_attestation = 2; + } + /** Key creation event, operation event or attestation event? */ + optional Type type = 12; + + /** Was the key creation, operation, or attestation successful? */ + optional bool was_successful = 13; + + /** Response code or error code */ + optional int32 error_code = 14; +} + +// Blob Committer stats +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobCommitterProto { + // Committer app's uid + optional int32 uid = 1 [(is_uid) = true]; + + // Unix epoch timestamp of the commit in milliseconds + optional int64 commit_timestamp_millis = 2; + + // Flags of what access types the committer has set for the Blob + optional int32 access_mode = 3; + + // Number of packages that have been whitelisted for ACCESS_TYPE_WHITELIST + optional int32 num_whitelisted_package = 4; +} + +// Blob Leasee stats +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobLeaseeProto { + // Leasee app's uid + optional int32 uid = 1 [(is_uid) = true]; + + // Unix epoch timestamp for lease expiration in milliseconds + optional int64 lease_expiry_timestamp_millis = 2; +} + +// List of Blob Committers +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobCommitterListProto { + repeated BlobCommitterProto committer = 1; +} + +// List of Blob Leasees +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobLeaseeListProto { + repeated BlobLeaseeProto leasee = 1; +} + +/** + * Logs the current state of a Blob committed with BlobStoreManager + * + * Pulled from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobInfo { + // Id of the Blob + optional int64 blob_id = 1; + + // Size of the Blob data + optional int64 size = 2; + + // Unix epoch timestamp of the Blob's expiration in milliseconds + optional int64 expiry_timestamp_millis = 3; + + // List of committers of this Blob + optional BlobCommitterListProto committers = 4; + + // List of leasees of this Blob + optional BlobLeaseeListProto leasees = 5; +} diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 7cac026c2421..9d46dcea1896 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -101,7 +101,7 @@ public: // Per atom dimension key size limit static const std::map<int, std::pair<size_t, size_t>> kAtomDimensionKeySizeLimitMap; - const static int kMaxConfigCountPerUid = 10; + const static int kMaxConfigCountPerUid = 20; const static int kMaxAlertCountPerConfig = 100; const static int kMaxConditionCountPerConfig = 300; const static int kMaxMetricCountPerConfig = 1000; diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index e8c575a1adea..7e825efddb75 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -361,8 +361,12 @@ void MetricsManager::onDumpReport(const int64_t dumpTimeStampNs, protoOutput->end(token); } - mLastReportTimeNs = dumpTimeStampNs; - mLastReportWallClockNs = getWallClockNs(); + // Do not update the timestamps when data is not cleared to avoid timestamps from being + // misaligned. + if (erase_data) { + mLastReportTimeNs = dumpTimeStampNs; + mLastReportWallClockNs = getWallClockNs(); + } VLOG("=========================Metric Reports End=========================="); } diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 8203f38de393..5987a723a421 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -951,6 +951,7 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, const int64_t& nextBucketStartTimeNs) { if (mCondition == ConditionState::kUnknown) { StatsdStats::getInstance().noteBucketUnknownCondition(mMetricId); + invalidateCurrentBucketWithoutResetBase(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN); } VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs, @@ -959,7 +960,10 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); int64_t bucketEndTime = fullBucketEndTimeNs; int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs); - if (numBucketsForward > 1) { + + // Skip buckets if this is a pulled metric or a pushed metric that is diffed. + if (numBucketsForward > 1 && (mIsPulled || mUseDiff)) { + VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); StatsdStats::getInstance().noteSkippedForwardBuckets(mMetricId); // Something went wrong. Maybe the device was sleeping for a long time. It is better diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp index 23f8ca4e74e6..a21eb9b9147f 100644 --- a/cmds/statsd/tests/FieldValue_test.cpp +++ b/cmds/statsd/tests/FieldValue_test.cpp @@ -33,6 +33,12 @@ namespace android { namespace os { namespace statsd { +// These constants must be kept in sync with those in StatsDimensionsValue.java. +const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; +const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; +const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; +const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; + namespace { void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, const vector<int>& attributionUids, const vector<string>& attributionTags, @@ -291,34 +297,76 @@ TEST(AtomMatcherTest, TestWriteDimensionPath) { } } -//TODO(b/149050405) Update this test for StatsDimensionValueParcel -//TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { -// HashableDimensionKey dim; -// -// int pos1[] = {1, 1, 1}; -// int pos2[] = {1, 1, 2}; -// int pos3[] = {1, 1, 3}; -// int pos4[] = {2, 0, 0}; -// -// Field field1(10, pos1, 2); -// Field field2(10, pos2, 2); -// Field field3(10, pos3, 2); -// Field field4(10, pos4, 0); -// -// Value value1((int32_t)10025); -// Value value2("tag"); -// Value value3((int32_t)987654); -// Value value4((int32_t)99999); -// -// dim.addValue(FieldValue(field1, value1)); -// dim.addValue(FieldValue(field2, value2)); -// dim.addValue(FieldValue(field3, value3)); -// dim.addValue(FieldValue(field4, value4)); -// -// SubscriberReporter::getStatsDimensionsValue(dim); -// // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't -// // have any read api. -//} +void checkAttributionNodeInDimensionsValueParcel(StatsDimensionsValueParcel& attributionNodeParcel, + int32_t nodeDepthInAttributionChain, + int32_t uid, string tag) { + EXPECT_EQ(attributionNodeParcel.field, nodeDepthInAttributionChain /*position at depth 1*/); + ASSERT_EQ(attributionNodeParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(attributionNodeParcel.tupleValue.size(), 2); + + StatsDimensionsValueParcel uidParcel = attributionNodeParcel.tupleValue[0]; + EXPECT_EQ(uidParcel.field, 1 /*position at depth 2*/); + EXPECT_EQ(uidParcel.valueType, STATS_DIMENSIONS_VALUE_INT_TYPE); + EXPECT_EQ(uidParcel.intValue, uid); + + StatsDimensionsValueParcel tagParcel = attributionNodeParcel.tupleValue[1]; + EXPECT_EQ(tagParcel.field, 2 /*position at depth 2*/); + EXPECT_EQ(tagParcel.valueType, STATS_DIMENSIONS_VALUE_STRING_TYPE); + EXPECT_EQ(tagParcel.stringValue, tag); +} + +// Test conversion of a HashableDimensionKey into a StatsDimensionValueParcel +TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { + int atomId = 10; + // First four fields form an attribution chain + int pos1[] = {1, 1, 1}; + int pos2[] = {1, 1, 2}; + int pos3[] = {1, 2, 1}; + int pos4[] = {1, 2, 2}; + int pos5[] = {2, 1, 1}; + + Field field1(atomId, pos1, /*depth=*/2); + Field field2(atomId, pos2, /*depth=*/2); + Field field3(atomId, pos3, /*depth=*/2); + Field field4(atomId, pos4, /*depth=*/2); + Field field5(atomId, pos5, /*depth=*/0); + + Value value1((int32_t)1); + Value value2("string2"); + Value value3((int32_t)3); + Value value4("string4"); + Value value5((float)5.0); + + HashableDimensionKey dimensionKey; + dimensionKey.addValue(FieldValue(field1, value1)); + dimensionKey.addValue(FieldValue(field2, value2)); + dimensionKey.addValue(FieldValue(field3, value3)); + dimensionKey.addValue(FieldValue(field4, value4)); + dimensionKey.addValue(FieldValue(field5, value5)); + + StatsDimensionsValueParcel rootParcel = dimensionKey.toStatsDimensionsValueParcel(); + EXPECT_EQ(rootParcel.field, atomId); + ASSERT_EQ(rootParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(rootParcel.tupleValue.size(), 2); + + // Check that attribution chain is populated correctly + StatsDimensionsValueParcel attributionChainParcel = rootParcel.tupleValue[0]; + EXPECT_EQ(attributionChainParcel.field, 1 /*position at depth 0*/); + ASSERT_EQ(attributionChainParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(attributionChainParcel.tupleValue.size(), 2); + checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[0], + /*nodeDepthInAttributionChain=*/1, + value1.int_value, value2.str_value); + checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[1], + /*nodeDepthInAttributionChain=*/2, + value3.int_value, value4.str_value); + + // Check that the float is populated correctly + StatsDimensionsValueParcel floatParcel = rootParcel.tupleValue[1]; + EXPECT_EQ(floatParcel.field, 2 /*position at depth 0*/); + EXPECT_EQ(floatParcel.valueType, STATS_DIMENSIONS_VALUE_FLOAT_TYPE); + EXPECT_EQ(floatParcel.floatValue, value5.float_value); +} TEST(AtomMatcherTest, TestWriteDimensionToProto) { HashableDimensionKey dim; diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 9a9702c34562..1e6680c47567 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -13,6 +13,11 @@ // limitations under the License. #include "StatsLogProcessor.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <stdio.h> + #include "StatsService.h" #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" @@ -20,16 +25,10 @@ #include "guardrail/StatsdStats.h" #include "logd/LogEvent.h" #include "packages/UidMap.h" -#include "storage/StorageManager.h" #include "statslog_statsdtest.h" - -#include <gmock/gmock.h> -#include <gtest/gtest.h> - +#include "storage/StorageManager.h" #include "tests/statsd_test_util.h" -#include <stdio.h> - using namespace android; using namespace testing; using ::ndk::SharedRefBase; @@ -1831,6 +1830,53 @@ TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUidAttributionCha EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value); } +TEST(StatsLogProcessorTest, TestDumpReportWithoutErasingDataDoesNotUpdateTimestamp) { + int hostUid = 20; + int isolatedUid = 30; + sp<MockUidMap> mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey key(3, 4); + + // TODO: All tests should not persist state on disk. This removes any reports that were present. + ProtoOutputStream proto; + StorageManager::appendConfigMetricsReport(key, &proto, /*erase data=*/true, /*isAdb=*/false); + + StatsdConfig config = MakeConfig(false); + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(1, 1, config, key, nullptr, 0, mockUidMap); + vector<uint8_t> bytes; + + int64_t dumpTime1Ns = 1 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime1Ns, false /* include_current_bucket */, + true /* erase_data */, ADB_DUMP, FAST, &bytes); + + ConfigMetricsReportList output; + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime1Ns); + + int64_t dumpTime2Ns = 5 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime2Ns, false /* include_current_bucket */, + false /* erase_data */, ADB_DUMP, FAST, &bytes); + + // Check that the dump report without clearing data is successful. + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime2Ns); + EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns); + + int64_t dumpTime3Ns = 10 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime3Ns, false /* include_current_bucket */, + true /* erase_data */, ADB_DUMP, FAST, &bytes); + + // Check that the previous dump report that didn't clear data did not overwrite the first dump's + // timestamps. + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime3Ns); + EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns); + +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index f0419964e892..5666501d7d51 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -3295,11 +3295,15 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionEventWron report.value_metrics().skipped(0).start_bucket_elapsed_millis()); EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size()); auto dropEvent = report.value_metrics().skipped(0).drop_event(0); EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason()); EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), dropEvent.drop_time_millis()); } /* @@ -3615,7 +3619,7 @@ TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) { sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerWithCondition( - pullerManager, metric, ConditionState::kUnknown); + pullerManager, metric, ConditionState::kFalse); // Check dump report. ProtoOutputStream output; @@ -3641,6 +3645,94 @@ TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) { } /* + * Test that all buckets are dropped due to condition unknown until the first onConditionChanged. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestConditionUnknownMultipleBuckets) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 15 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 15 * NS_PER_SEC, 15)); + return true; + })); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Bucket should be dropped because of condition unknown. + int64_t appUpgradeTimeNs = bucketStartTimeNs + 5 * NS_PER_SEC; + valueProducer->notifyAppUpgrade(appUpgradeTimeNs); + + // Bucket also dropped due to condition unknown + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 3)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // This bucket is also dropped due to condition unknown. + int64_t conditionChangeTimeNs = bucket2StartTimeNs + 10 * NS_PER_SEC; + valueProducer->onConditionChanged(true, conditionChangeTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC; // 15 seconds + valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(3, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), dropEvent.drop_time_millis()); + + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), + report.value_metrics().skipped(1).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(1).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size()); + + dropEvent = report.value_metrics().skipped(1).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(2).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(2).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(2).drop_event_size()); + + dropEvent = report.value_metrics().skipped(2).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(conditionChangeTimeNs), dropEvent.drop_time_millis()); +} + +/* * Test that a skipped bucket is logged when a forced bucket split occurs when the previous bucket * was not flushed in time. */ @@ -4957,7 +5049,7 @@ TEST(ValueMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBuck ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); auto dropEvent = report.value_metrics().skipped(0).drop_event(0); - EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); } diff --git a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp index db402a0dd658..32cecd3b9dbc 100644 --- a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp +++ b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp @@ -50,13 +50,13 @@ TEST(MultiConditionTrigger, TestMultipleConditions) { }); vector<thread> threads; - vector<bool> done(numConditions, false); + vector<int> done(numConditions, 0); int i = 0; for (const string& conditionName : conditionNames) { threads.emplace_back([&done, &conditionName, &trigger, i] { sleep_for(chrono::milliseconds(3)); - done[i] = true; + done[i] = 1; trigger.markComplete(conditionName); }); i++; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index b608a343fc7d..acf6315ddc5d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -159,10 +159,10 @@ public class ActivityManager { */ public static final int INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS = 1 << 0; /** - * Mount full external storage for the newly started instrumentation. + * Grant full access to the external storage for the newly started instrumentation. * @hide */ - public static final int INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL = 1 << 1; + public static final int INSTR_FLAG_DISABLE_ISOLATED_STORAGE = 1 << 1; /** * Disable test API access for the newly started instrumentation. diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index d650bbcdfa33..b749c3504811 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -360,18 +360,9 @@ public class ActivityView extends ViewGroup implements android.window.TaskEmbedd } /** - * Release this container. Activity launching will no longer be permitted. - * <p>Note: Calling this method is allowed after - * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before - * {@link StateCallback#onActivityViewDestroyed(ActivityView)}. - * - * @see StateCallback + * Release this container if it is initialized. Activity launching will no longer be permitted. */ public void release() { - if (!mTaskEmbedder.isInitialized()) { - throw new IllegalStateException( - "Trying to release container that is not initialized."); - } performRelease(); } @@ -487,7 +478,9 @@ public class ActivityView extends ViewGroup implements android.window.TaskEmbedd return; } mSurfaceView.getHolder().removeCallback(mSurfaceCallback); - mTaskEmbedder.release(); + if (mTaskEmbedder.isInitialized()) { + mTaskEmbedder.release(); + } mTaskEmbedder.setListener(null); mGuard.close(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index ddc57474a027..71b866b7b16a 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -261,8 +261,9 @@ public class AppOpsManager { < SystemClock.elapsedRealtime()) { String stackTrace = getFormattedStackTrace(); try { + String packageName = ActivityThread.currentOpPackageName(); sConfig = getService().reportRuntimeAppOpAccessMessageAndGetConfig( - ActivityThread.currentOpPackageName(), op, stackTrace); + packageName == null ? "" : packageName, op, stackTrace); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -1121,8 +1122,11 @@ public class AppOpsManager { AppProtoEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER; /** @hide */ + public static final int OP_NO_ISOLATED_STORAGE = AppProtoEnums.APP_OP_NO_ISOLATED_STORAGE; + + /** @hide */ @UnsupportedAppUsage - public static final int _NUM_OP = 99; + public static final int _NUM_OP = 100; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1433,6 +1437,12 @@ public class AppOpsManager { @SystemApi public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats"; + /** + * AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage} + * + * @hide + */ + public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage"; /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; @@ -1622,6 +1632,7 @@ public class AppOpsManager { OP_DEPRECATED_1, // deprecated OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, //AUTO_REVOKE_PERMISSIONS_IF_UNUSED OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, //OP_AUTO_REVOKE_MANAGED_BY_INSTALLER + OP_NO_ISOLATED_STORAGE, // NO_ISOLATED_STORAGE }; /** @@ -1727,6 +1738,7 @@ public class AppOpsManager { "", // deprecated OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER, + OPSTR_NO_ISOLATED_STORAGE, }; /** @@ -1833,6 +1845,7 @@ public class AppOpsManager { "deprecated", "AUTO_REVOKE_PERMISSIONS_IF_UNUSED", "AUTO_REVOKE_MANAGED_BY_INSTALLER", + "NO_ISOLATED_STORAGE", }; /** @@ -1940,6 +1953,7 @@ public class AppOpsManager { null, // deprecated operation null, // no permission for OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED null, // no permission for OP_AUTO_REVOKE_MANAGED_BY_INSTALLER + null, // no permission for OP_NO_ISOLATED_STORAGE }; /** @@ -2047,6 +2061,7 @@ public class AppOpsManager { null, // deprecated operation null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED null, // AUTO_REVOKE_MANAGED_BY_INSTALLER + null, // NO_ISOLATED_STORAGE }; /** @@ -2153,6 +2168,7 @@ public class AppOpsManager { null, // deprecated operation null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED null, // AUTO_REVOKE_MANAGED_BY_INSTALLER + null, // NO_ISOLATED_STORAGE }; /** @@ -2258,6 +2274,7 @@ public class AppOpsManager { AppOpsManager.MODE_IGNORED, // deprecated operation AppOpsManager.MODE_DEFAULT, // OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED AppOpsManager.MODE_ALLOWED, // OP_AUTO_REVOKE_MANAGED_BY_INSTALLER + AppOpsManager.MODE_ERRORED, // OP_NO_ISOLATED_STORAGE }; /** @@ -2367,6 +2384,7 @@ public class AppOpsManager { false, // deprecated operation false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED false, // AUTO_REVOKE_MANAGED_BY_INSTALLER + true, // NO_ISOLATED_STORAGE }; /** diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index f881616fd35a..0e3f35e358c0 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -7495,6 +7495,7 @@ public class Notification implements Parcelable mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); + mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON); } /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index c65064324c8c..322cac81d58b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4247,6 +4247,12 @@ public class DevicePolicyManager { * device. After this method is called, the device must be unlocked using strong authentication * (PIN, pattern, or password). This API is intended for use only by device admins. * <p> + * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have + * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is + * true, then the method will return without completing any action. Before version + * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature, + * regardless of the caller's permissions. + * <p> * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} * to be able to call this method; if it has not, a security exception will be thrown. * <p> @@ -4274,6 +4280,12 @@ public class DevicePolicyManager { * device. After this method is called, the device must be unlocked using strong authentication * (PIN, pattern, or password). This API is intended for use only by device admins. * <p> + * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have + * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is + * true, then the method will return without completing any action. Before version + * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature, + * regardless of the caller's permissions. + * <p> * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} * to be able to call this method; if it has not, a security exception will be thrown. * <p> @@ -5868,12 +5880,22 @@ public class DevicePolicyManager { * returned by {@link #getParentProfileInstance(ComponentName)}, where the caller must be * the profile owner of an organization-owned managed profile. * <p> - * If the caller is device owner or called on the parent instance, then the - * restriction will be applied to all users. + * If the caller is device owner, then the restriction will be applied to all users. If + * called on the parent instance, then the restriction will be applied on the personal profile. * <p> * The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} to be able to call this method; if it has * not, a security exception will be thrown. + * <p> + * <b>Note</b>, this policy type is deprecated for legacy device admins since + * {@link android.os.Build.VERSION_CODES#Q}. On Android + * {@link android.os.Build.VERSION_CODES#Q} devices, legacy device admins targeting SDK + * version {@link android.os.Build.VERSION_CODES#P} or below can still call this API to + * disable camera, while legacy device admins targeting SDK version + * {@link android.os.Build.VERSION_CODES#Q} will receive a SecurityException. Starting + * from Android {@link android.os.Build.VERSION_CODES#R}, requests to disable camera from + * legacy device admins targeting SDK version {@link android.os.Build.VERSION_CODES#P} or + * below will be silently ignored. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param disabled Whether or not the camera should be disabled. diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 0f999ad68a62..3522b1b8aff5 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -133,6 +133,7 @@ public final class UsageEvents implements Parcelable { /** * An event type denoting that a component was in the foreground when the stats * rolled-over. This is effectively treated as a {@link #ACTIVITY_PAUSED}. + * This event has a non-null packageName, and a null className. * {@hide} */ public static final int END_OF_DAY = 3; diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java index 24be45cb20fe..8e687413b7e1 100644 --- a/core/java/android/companion/BluetoothDeviceFilterUtils.java +++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java @@ -51,13 +51,6 @@ public class BluetoothDeviceFilterUtils { return s == null ? null : Pattern.compile(s); } - static boolean matches(ScanFilter filter, BluetoothDevice device) { - boolean result = matchesAddress(filter.getDeviceAddress(), device) - && matchesServiceUuid(filter.getServiceUuid(), filter.getServiceUuidMask(), device); - if (DEBUG) debugLogMatchResult(result, device, filter); - return result; - } - static boolean matchesAddress(String deviceAddress, BluetoothDevice device) { final boolean result = deviceAddress == null || (device != null && deviceAddress.equals(device.getAddress())); diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java index dccfb0346c9c..8c071fe99104 100644 --- a/core/java/android/companion/BluetoothLeDeviceFilter.java +++ b/core/java/android/companion/BluetoothLeDeviceFilter.java @@ -37,7 +37,6 @@ import android.util.Log; import com.android.internal.util.BitUtils; import com.android.internal.util.ObjectUtils; -import com.android.internal.util.Preconditions; import libcore.util.HexEncoding; @@ -166,21 +165,18 @@ public final class BluetoothLeDeviceFilter implements DeviceFilter<ScanResult> { /** @hide */ @Override - public boolean matches(ScanResult device) { - boolean result = matches(device.getDevice()) + public boolean matches(ScanResult scanResult) { + BluetoothDevice device = scanResult.getDevice(); + boolean result = getScanFilter().matches(scanResult) + && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device) && (mRawDataFilter == null - || BitUtils.maskedEquals(device.getScanRecord().getBytes(), + || BitUtils.maskedEquals(scanResult.getScanRecord().getBytes(), mRawDataFilter, mRawDataFilterMask)); if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device + ") -> " + result); return result; } - private boolean matches(BluetoothDevice device) { - return BluetoothDeviceFilterUtils.matches(getScanFilter(), device) - && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device); - } - /** @hide */ @Override public int getMediumType() { diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index bd1ee27ece9e..1a694b34474a 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -1243,14 +1243,7 @@ public class LauncherApps { private ParcelFileDescriptor getUriShortcutIconFd(@NonNull String packageName, @NonNull String shortcutId, int userId) { - String uri = null; - try { - uri = mService.getShortcutIconUri(mContext.getPackageName(), packageName, shortcutId, - userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - + String uri = getShortcutIconUri(packageName, shortcutId, userId); if (uri == null) { return null; } @@ -1262,6 +1255,18 @@ public class LauncherApps { } } + private String getShortcutIconUri(@NonNull String packageName, + @NonNull String shortcutId, int userId) { + String uri = null; + try { + uri = mService.getShortcutIconUri(mContext.getPackageName(), packageName, shortcutId, + userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return uri; + } + /** * Returns the icon for this shortcut, without any badging for the profile. * @@ -1357,6 +1362,17 @@ public class LauncherApps { } catch (IOException ignore) { } } + } else if (shortcut.hasIconUri()) { + String uri = getShortcutIconUri(shortcut.getPackage(), shortcut.getId(), + shortcut.getUserId()); + if (uri == null) { + return null; + } + if (shortcut.hasAdaptiveBitmap()) { + return Icon.createWithAdaptiveBitmapContentUri(uri); + } else { + return Icon.createWithContentUri(uri); + } } else if (shortcut.hasIconResource()) { return Icon.createWithResource(shortcut.getPackage(), shortcut.getIconResourceId()); } else { diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index c8dd4d9d9d51..885ffbac3d30 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -205,6 +205,7 @@ public class PackageParser { public static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m"; public static final String TAG_USES_SDK = "uses-sdk"; public static final String TAG_USES_SPLIT = "uses-split"; + public static final String TAG_PROFILEABLE = "profileable"; public static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect"; public static final String METADATA_SUPPORTS_SIZE_CHANGES = "android.supports_size_changes"; @@ -459,6 +460,9 @@ public class PackageParser { public final SigningDetails signingDetails; public final boolean coreApp; public final boolean debuggable; + // This does not represent the actual manifest structure since the 'profilable' tag + // could be used with attributes other than 'shell'. Extend if necessary. + public final boolean profilableByShell; public final boolean multiArch; public final boolean use32bitAbi; public final boolean extractNativeLibs; @@ -470,15 +474,13 @@ public class PackageParser { public final int overlayPriority; public ApkLite(String codePath, String packageName, String splitName, - boolean isFeatureSplit, - String configForSplit, String usesSplitName, boolean isSplitRequired, - int versionCode, int versionCodeMajor, - int revisionCode, int installLocation, List<VerifierInfo> verifiers, - SigningDetails signingDetails, boolean coreApp, - boolean debuggable, boolean multiArch, boolean use32bitAbi, - boolean useEmbeddedDex, boolean extractNativeLibs, boolean isolatedSplits, - String targetPackageName, boolean overlayIsStatic, int overlayPriority, - int minSdkVersion, int targetSdkVersion) { + boolean isFeatureSplit, String configForSplit, String usesSplitName, + boolean isSplitRequired, int versionCode, int versionCodeMajor, int revisionCode, + int installLocation, List<VerifierInfo> verifiers, SigningDetails signingDetails, + boolean coreApp, boolean debuggable, boolean profilableByShell, boolean multiArch, + boolean use32bitAbi, boolean useEmbeddedDex, boolean extractNativeLibs, + boolean isolatedSplits, String targetPackageName, boolean overlayIsStatic, + int overlayPriority, int minSdkVersion, int targetSdkVersion) { this.codePath = codePath; this.packageName = packageName; this.splitName = splitName; @@ -493,6 +495,7 @@ public class PackageParser { this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]); this.coreApp = coreApp; this.debuggable = debuggable; + this.profilableByShell = profilableByShell; this.multiArch = multiArch; this.use32bitAbi = use32bitAbi; this.useEmbeddedDex = useEmbeddedDex; @@ -1573,6 +1576,7 @@ public class PackageParser { int revisionCode = 0; boolean coreApp = false; boolean debuggable = false; + boolean profilableByShell = false; boolean multiArch = false; boolean use32bitAbi = false; boolean extractNativeLibs = true; @@ -1638,6 +1642,10 @@ public class PackageParser { final String attr = attrs.getAttributeName(i); if ("debuggable".equals(attr)) { debuggable = attrs.getAttributeBooleanValue(i, false); + if (debuggable) { + // Debuggable implies profileable + profilableByShell = true; + } } if ("multiArch".equals(attr)) { multiArch = attrs.getAttributeBooleanValue(i, false); @@ -1690,6 +1698,13 @@ public class PackageParser { minSdkVersion = attrs.getAttributeIntValue(i, DEFAULT_MIN_SDK_VERSION); } } + } else if (TAG_PROFILEABLE.equals(parser.getName())) { + for (int i = 0; i < attrs.getAttributeCount(); ++i) { + final String attr = attrs.getAttributeName(i); + if ("shell".equals(attr)) { + profilableByShell = attrs.getAttributeBooleanValue(i, profilableByShell); + } + } } } @@ -1707,8 +1722,9 @@ public class PackageParser { return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode, versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails, coreApp, debuggable, - multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, isolatedSplits, - targetPackage, overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion); + profilableByShell, multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, + isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, minSdkVersion, + targetSdkVersion); } /** diff --git a/core/java/android/content/pm/dex/ArtManagerInternal.java b/core/java/android/content/pm/dex/ArtManagerInternal.java index 62ab9e02f858..23fef29803e7 100644 --- a/core/java/android/content/pm/dex/ArtManagerInternal.java +++ b/core/java/android/content/pm/dex/ArtManagerInternal.java @@ -30,5 +30,5 @@ public abstract class ArtManagerInternal { * in executes using the specified {@code abi}. */ public abstract PackageOptimizationInfo getPackageOptimizationInfo( - ApplicationInfo info, String abi); + ApplicationInfo info, String abi, String activityName); } diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index d2172d3741d1..c3e9402a389e 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -20,7 +20,6 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; -import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; @@ -303,6 +302,7 @@ public class ApkLiteParseUtils { int revisionCode = 0; boolean coreApp = false; boolean debuggable = false; + boolean profilableByShell = false; boolean multiArch = false; boolean use32bitAbi = false; boolean extractNativeLibs = true; @@ -379,6 +379,10 @@ public class ApkLiteParseUtils { switch (attr) { case "debuggable": debuggable = attrs.getAttributeBooleanValue(i, false); + if (debuggable) { + // Debuggable implies profileable + profilableByShell = true; + } break; case "multiArch": multiArch = attrs.getAttributeBooleanValue(i, false); @@ -431,6 +435,13 @@ public class ApkLiteParseUtils { minSdkVersion = attrs.getAttributeIntValue(i, DEFAULT_MIN_SDK_VERSION); } } + } else if (PackageParser.TAG_PROFILEABLE.equals(parser.getName())) { + for (int i = 0; i < attrs.getAttributeCount(); ++i) { + final String attr = attrs.getAttributeName(i); + if ("shell".equals(attr)) { + profilableByShell = attrs.getAttributeBooleanValue(i, profilableByShell); + } + } } } @@ -445,12 +456,13 @@ public class ApkLiteParseUtils { overlayPriority = 0; } - return input.success(new PackageParser.ApkLite(codePath, packageSplit.first, - packageSplit.second, isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, - versionCode, versionCodeMajor, revisionCode, installLocation, verifiers, - signingDetails, coreApp, debuggable, multiArch, use32bitAbi, useEmbeddedDex, - extractNativeLibs, isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, - minSdkVersion, targetSdkVersion)); + return input.success( + new PackageParser.ApkLite(codePath, packageSplit.first, packageSplit.second, + isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode, + versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails, + coreApp, debuggable, profilableByShell, multiArch, use32bitAbi, + useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage, + overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion)); } public static ParseResult<Pair<String, String>> parsePackageSplitNames(ParseInput input, diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 570cc2c11738..2d2dda04b146 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -26,6 +26,8 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.RemoteException; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; import android.util.Slog; /** @@ -82,6 +84,9 @@ public class BiometricManager { * * <p>Types may combined via bitwise OR into a single integer representing multiple * authenticators (e.g. <code>DEVICE_CREDENTIAL | BIOMETRIC_WEAK</code>). + * + * @see #canAuthenticate(int) + * @see BiometricPrompt.Builder#setAllowedAuthenticators(int) */ public interface Authenticators { /** @@ -118,6 +123,10 @@ public class BiometricManager { * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the * requirements for <strong>Tier 3</strong> (formerly <strong>Strong</strong>), as defined * by the Android CDD. + * + * <p>This corresponds to {@link KeyProperties#AUTH_BIOMETRIC_STRONG} during key generation. + * + * @see KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int) */ int BIOMETRIC_STRONG = 0x000F; @@ -156,6 +165,11 @@ public class BiometricManager { * The non-biometric credential used to secure the device (i.e., PIN, pattern, or password). * This should typically only be used in combination with a biometric auth type, such as * {@link #BIOMETRIC_WEAK}. + * + * <p>This corresponds to {@link KeyProperties#AUTH_DEVICE_CREDENTIAL} during key + * generation. + * + * @see KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int) */ int DEVICE_CREDENTIAL = 1 << 15; } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 5af7cef3e2b4..74caceae07c9 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -36,6 +36,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.security.identity.IdentityCredential; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; import android.text.TextUtils; import android.util.Log; @@ -371,6 +373,14 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * button on the prompt, making it an error to also call * {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. * + * <p>If unlocking cryptographic operation(s), it is the application's responsibility to + * request authentication with the proper set of authenticators (e.g. match the + * authenticators specified during key generation). + * + * @see KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int) + * @see KeyProperties#AUTH_BIOMETRIC_STRONG + * @see KeyProperties#AUTH_DEVICE_CREDENTIAL + * * @param authenticators A bit field representing all valid authenticator types that may be * invoked by the prompt. * @return This builder. @@ -606,8 +616,24 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** - * A wrapper class for the crypto objects supported by BiometricPrompt. Currently the framework - * supports {@link Signature}, {@link Cipher} and {@link Mac} objects. + * A wrapper class for the cryptographic operations supported by BiometricPrompt. + * + * <p>Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, and + * {@link IdentityCredential}. + * + * <p>Cryptographic operations in Android can be split into two categories: auth-per-use and + * time-based. This is specified during key creation via the timeout parameter of the + * {@link KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int)} API. + * + * <p>CryptoObjects are used to unlock auth-per-use keys via + * {@link BiometricPrompt#authenticate(CryptoObject, CancellationSignal, Executor, + * AuthenticationCallback)}, whereas time-based keys are unlocked for their specified duration + * any time the user authenticates with the specified authenticators (e.g. unlocking keyguard). + * If a time-based key is not available for use (i.e. none of the allowed authenticators have + * been unlocked recently), applications can prompt the user to authenticate via + * {@link BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} + * + * @see Builder#setAllowedAuthenticators(int) */ public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { public CryptoObject(@NonNull Signature signature) { diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index ea5cc7f2e8bc..c1ba2094d3cf 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -303,13 +303,25 @@ public final class DisplayManager { /** * Virtual display flag: Indicates that the display should support system decorations. Virtual * displays without this flag shouldn't show home, IME or any other system decorations. + * <p>This flag doesn't work without {@link #VIRTUAL_DISPLAY_FLAG_TRUSTED}</p> * * @see #createVirtualDisplay + * @see #VIRTUAL_DISPLAY_FLAG_TRUSTED * @hide */ // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9; + /** + * Virtual display flags: Indicates that the display is trusted to show system decorations and + * receive inputs without users' touch. + * + * @see #createVirtualDisplay + * @see #VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + * @hide + */ + public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10; + /** @hide */ public DisplayManager(Context context) { mContext = context; diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index 275e38c74451..704f31d7f773 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -711,6 +711,13 @@ public class ConnectivityDiagnosticsManager { * not currently registered. If a ConnectivityDiagnosticsCallback instance is registered with * multiple NetworkRequests, an IllegalArgumentException will be thrown. * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * callbacks in {@link ConnectivityManager}. Registering a callback with this method will count + * toward this limit. If this limit is exceeded, an exception will be thrown. To avoid hitting + * this issue and to conserve resources, make sure to unregister the callbacks with + * {@link #unregisterConnectivityDiagnosticsCallback}. + * * @param request The NetworkRequest that will be used to match with Networks for which * callbacks will be fired * @param e The Executor to be used for running the callback method invocations @@ -718,6 +725,7 @@ public class ConnectivityDiagnosticsManager { * System * @throws IllegalArgumentException if the same callback instance is registered with multiple * NetworkRequests + * @throws RuntimeException if the app already has too many callbacks registered. */ public void registerConnectivityDiagnosticsCallback( @NonNull NetworkRequest request, diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 36ffe50ef82d..a29f8782601e 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2246,26 +2246,6 @@ public class ConnectivityManager { .getPackageNameForUid(context, uid), true /* throwException */); } - /** {@hide} */ - public static final void enforceTetherChangePermission(Context context, String callingPkg) { - Preconditions.checkNotNull(context, "Context cannot be null"); - Preconditions.checkNotNull(callingPkg, "callingPkg cannot be null"); - - if (context.getResources().getStringArray( - com.android.internal.R.array.config_mobile_hotspot_provision_app).length == 2) { - // Have a provisioning app - must only let system apps (which check this app) - // turn on tethering - context.enforceCallingOrSelfPermission( - android.Manifest.permission.TETHER_PRIVILEGED, "ConnectivityService"); - } else { - int uid = Binder.getCallingUid(); - // If callingPkg's uid is not same as Binder.getCallingUid(), - // AppOpsService throws SecurityException. - Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPkg, - true /* throwException */); - } - } - /** * @deprecated - use getSystemService. This is a kludge to support static access in certain * situations where a Context pointer is unavailable. @@ -3814,13 +3794,22 @@ public class ConnectivityManager { * or the ability to modify system settings as determined by * {@link android.provider.Settings.System#canWrite}.</p> * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * * @param request {@link NetworkRequest} describing this request. * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note * the callback must not be shared - it uniquely specifies this request. * The callback is invoked on the default internal Handler. * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. * @throws SecurityException if missing the appropriate permissions. - * @throws RuntimeException if request limit per UID is exceeded. + * @throws RuntimeException if the app already has too many callbacks registered. */ public void requestNetwork(@NonNull NetworkRequest request, @NonNull NetworkCallback networkCallback) { @@ -3834,8 +3823,8 @@ public class ConnectivityManager { * but runs all the callbacks on the passed Handler. * * <p>This method has the same permission requirements as - * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and throws the same exceptions in - * the same conditions. + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. * * @param request {@link NetworkRequest} describing this request. * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note @@ -3866,8 +3855,8 @@ public class ConnectivityManager { * for that purpose. Calling this method will attempt to bring up the requested network. * * <p>This method has the same permission requirements as - * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and throws the same exceptions in - * the same conditions. + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. * * @param request {@link NetworkRequest} describing this request. * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note @@ -3893,8 +3882,8 @@ public class ConnectivityManager { * on the passed Handler. * * <p>This method has the same permission requirements as - * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} and throws the same exceptions - * in the same conditions. + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. * * @param request {@link NetworkRequest} describing this request. * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note @@ -3963,6 +3952,15 @@ public class ConnectivityManager { * is unknown prior to bringing up the network so the framework does not * know how to go about satisfying a request with these capabilities. * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with {@link #unregisterNetworkCallback(PendingIntent)} + * or {@link #releaseNetworkRequest(PendingIntent)}. + * * <p>This method requires the caller to hold either the * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission * or the ability to modify system settings as determined by @@ -3974,7 +3972,7 @@ public class ConnectivityManager { * comes from {@link PendingIntent#getBroadcast}. Cannot be null. * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. * @throws SecurityException if missing the appropriate permissions. - * @throws RuntimeException if request limit per UID is exceeded. + * @throws RuntimeException if the app already has too many callbacks registered. */ public void requestNetwork(@NonNull NetworkRequest request, @NonNull PendingIntent operation) { @@ -4030,10 +4028,20 @@ public class ConnectivityManager { * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is * called. * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * * @param request {@link NetworkRequest} describing this request. * @param networkCallback The {@link NetworkCallback} that the system will call as suitable * networks change state. * The callback is invoked on the default internal Handler. + * @throws RuntimeException if the app already has too many callbacks registered. */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull NetworkRequest request, @@ -4047,10 +4055,21 @@ public class ConnectivityManager { * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is * called. * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * * @param request {@link NetworkRequest} describing this request. * @param networkCallback The {@link NetworkCallback} that the system will call as suitable * networks change state. * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull NetworkRequest request, @@ -4084,10 +4103,21 @@ public class ConnectivityManager { * <p> * The request may be released normally by calling * {@link #unregisterNetworkCallback(android.app.PendingIntent)}. + * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with {@link #unregisterNetworkCallback(PendingIntent)} + * or {@link #releaseNetworkRequest(PendingIntent)}. + * * @param request {@link NetworkRequest} describing this request. * @param operation Action to perform when the network is available (corresponds * to the {@link NetworkCallback#onAvailable} call. Typically * comes from {@link PendingIntent#getBroadcast}. Cannot be null. + * @throws RuntimeException if the app already has too many callbacks registered. */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull NetworkRequest request, @@ -4109,9 +4139,19 @@ public class ConnectivityManager { * will continue to be called until either the application exits or * {@link #unregisterNetworkCallback(NetworkCallback)} is called. * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * * @param networkCallback The {@link NetworkCallback} that the system will call as the * system default network changes. * The callback is invoked on the default internal Handler. + * @throws RuntimeException if the app already has too many callbacks registered. */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback) { @@ -4123,9 +4163,19 @@ public class ConnectivityManager { * will continue to be called until either the application exits or * {@link #unregisterNetworkCallback(NetworkCallback)} is called. * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * * @param networkCallback The {@link NetworkCallback} that the system will call as the * system default network changes. * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, @@ -4217,7 +4267,6 @@ public class ConnectivityManager { * Cannot be null. */ public void unregisterNetworkCallback(@NonNull PendingIntent operation) { - checkPendingIntentNotNull(operation); releaseNetworkRequest(operation); } diff --git a/core/java/android/net/DnsPacket.java b/core/java/android/net/DnsPacket.java deleted file mode 100644 index 83e57e0a047b..000000000000 --- a/core/java/android/net/DnsPacket.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.text.TextUtils; - -import com.android.internal.util.BitUtils; - -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.text.DecimalFormat; -import java.text.FieldPosition; -import java.util.ArrayList; -import java.util.List; - -/** - * Defines basic data for DNS protocol based on RFC 1035. - * Subclasses create the specific format used in DNS packet. - * - * @hide - */ -public abstract class DnsPacket { - public class DnsHeader { - private static final String TAG = "DnsHeader"; - public final int id; - public final int flags; - public final int rcode; - private final int[] mRecordCount; - - /** - * Create a new DnsHeader from a positioned ByteBuffer. - * - * The ByteBuffer must be in network byte order (which is the default). - * Reads the passed ByteBuffer from its current position and decodes a DNS header. - * When this constructor returns, the reading position of the ByteBuffer has been - * advanced to the end of the DNS header record. - * This is meant to chain with other methods reading a DNS response in sequence. - */ - DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { - id = BitUtils.uint16(buf.getShort()); - flags = BitUtils.uint16(buf.getShort()); - rcode = flags & 0xF; - mRecordCount = new int[NUM_SECTIONS]; - for (int i = 0; i < NUM_SECTIONS; ++i) { - mRecordCount[i] = BitUtils.uint16(buf.getShort()); - } - } - - /** - * Get record count by type. - */ - public int getRecordCount(int type) { - return mRecordCount[type]; - } - } - - /** - * Superclass for DNS questions and DNS resource records. - * - * DNS questions (No TTL/RDATA) - * DNS resource records (With TTL/RDATA) - */ - public class DnsRecord { - private static final int MAXNAMESIZE = 255; - private static final int MAXLABELSIZE = 63; - private static final int MAXLABELCOUNT = 128; - private static final int NAME_NORMAL = 0; - private static final int NAME_COMPRESSION = 0xC0; - private final DecimalFormat byteFormat = new DecimalFormat(); - private final FieldPosition pos = new FieldPosition(0); - - private static final String TAG = "DnsRecord"; - - public final String dName; - public final int nsType; - public final int nsClass; - public final long ttl; - private final byte[] mRdata; - - /** - * Create a new DnsRecord from a positioned ByteBuffer. - * - * Reads the passed ByteBuffer from its current position and decodes a DNS record. - * When this constructor returns, the reading position of the ByteBuffer has been - * advanced to the end of the DNS header record. - * This is meant to chain with other methods reading a DNS response in sequence. - * - * @param ByteBuffer input of record, must be in network byte order - * (which is the default). - */ - DnsRecord(int recordType, @NonNull ByteBuffer buf) - throws BufferUnderflowException, ParseException { - dName = parseName(buf, 0 /* Parse depth */); - if (dName.length() > MAXNAMESIZE) { - throw new ParseException( - "Parse name fail, name size is too long: " + dName.length()); - } - nsType = BitUtils.uint16(buf.getShort()); - nsClass = BitUtils.uint16(buf.getShort()); - - if (recordType != QDSECTION) { - ttl = BitUtils.uint32(buf.getInt()); - final int length = BitUtils.uint16(buf.getShort()); - mRdata = new byte[length]; - buf.get(mRdata); - } else { - ttl = 0; - mRdata = null; - } - } - - /** - * Get a copy of rdata. - */ - @Nullable - public byte[] getRR() { - return (mRdata == null) ? null : mRdata.clone(); - } - - /** - * Convert label from {@code byte[]} to {@code String} - * - * Follows the same conversion rules of the native code (ns_name.c in libc) - */ - private String labelToString(@NonNull byte[] label) { - final StringBuffer sb = new StringBuffer(); - for (int i = 0; i < label.length; ++i) { - int b = BitUtils.uint8(label[i]); - // Control characters and non-ASCII characters. - if (b <= 0x20 || b >= 0x7f) { - // Append the byte as an escaped decimal number, e.g., "\19" for 0x13. - sb.append('\\'); - byteFormat.format(b, sb, pos); - } else if (b == '"' || b == '.' || b == ';' || b == '\\' - || b == '(' || b == ')' || b == '@' || b == '$') { - // Append the byte as an escaped character, e.g., "\:" for 0x3a. - sb.append('\\'); - sb.append((char) b); - } else { - // Append the byte as a character, e.g., "a" for 0x61. - sb.append((char) b); - } - } - return sb.toString(); - } - - private String parseName(@NonNull ByteBuffer buf, int depth) throws - BufferUnderflowException, ParseException { - if (depth > MAXLABELCOUNT) { - throw new ParseException("Failed to parse name, too many labels"); - } - final int len = BitUtils.uint8(buf.get()); - final int mask = len & NAME_COMPRESSION; - if (0 == len) { - return ""; - } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) { - throw new ParseException("Parse name fail, bad label type"); - } else if (mask == NAME_COMPRESSION) { - // Name compression based on RFC 1035 - 4.1.4 Message compression - final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get()); - final int oldPos = buf.position(); - if (offset >= oldPos - 2) { - throw new ParseException("Parse compression name fail, invalid compression"); - } - buf.position(offset); - final String pointed = parseName(buf, depth + 1); - buf.position(oldPos); - return pointed; - } else { - final byte[] label = new byte[len]; - buf.get(label); - final String head = labelToString(label); - if (head.length() > MAXLABELSIZE) { - throw new ParseException("Parse name fail, invalid label length"); - } - final String tail = parseName(buf, depth + 1); - return TextUtils.isEmpty(tail) ? head : head + "." + tail; - } - } - } - - public static final int QDSECTION = 0; - public static final int ANSECTION = 1; - public static final int NSSECTION = 2; - public static final int ARSECTION = 3; - private static final int NUM_SECTIONS = ARSECTION + 1; - - private static final String TAG = DnsPacket.class.getSimpleName(); - - protected final DnsHeader mHeader; - protected final List<DnsRecord>[] mRecords; - - protected DnsPacket(@NonNull byte[] data) throws ParseException { - if (null == data) throw new ParseException("Parse header failed, null input data"); - final ByteBuffer buffer; - try { - buffer = ByteBuffer.wrap(data); - mHeader = new DnsHeader(buffer); - } catch (BufferUnderflowException e) { - throw new ParseException("Parse Header fail, bad input data", e); - } - - mRecords = new ArrayList[NUM_SECTIONS]; - - for (int i = 0; i < NUM_SECTIONS; ++i) { - final int count = mHeader.getRecordCount(i); - if (count > 0) { - mRecords[i] = new ArrayList(count); - } - for (int j = 0; j < count; ++j) { - try { - mRecords[i].add(new DnsRecord(i, buffer)); - } catch (BufferUnderflowException e) { - throw new ParseException("Parse record fail", e); - } - } - } - } -} diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java index 0b1a84534e38..3f7660f5709a 100644 --- a/core/java/android/net/DnsResolver.java +++ b/core/java/android/net/DnsResolver.java @@ -38,6 +38,8 @@ import android.os.MessageQueue; import android.system.ErrnoException; import android.util.Log; +import com.android.net.module.util.DnsPacket; + import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -97,7 +99,7 @@ public final class DnsResolver { @interface DnsError {} /** * Indicates that there was an error parsing the response the query. - * The cause of this error is available via getCause() and is a ParseException. + * The cause of this error is available via getCause() and is a {@link ParseException}. */ public static final int ERROR_PARSE = 0; /** @@ -290,8 +292,15 @@ public final class DnsResolver { } try { mAllAnswers.addAll(new DnsAddressAnswer(answer).getAddresses()); - } catch (ParseException e) { - mDnsException = new DnsException(ERROR_PARSE, e); + } catch (DnsPacket.ParseException e) { + // Convert the com.android.net.module.util.DnsPacket.ParseException to an + // android.net.ParseException. This is the type that was used in Q and is implied + // by the public documentation of ERROR_PARSE. + // + // DnsPacket cannot throw android.net.ParseException directly because it's @hide. + ParseException pe = new ParseException(e.reason, e.getCause()); + pe.setStackTrace(e.getStackTrace()); + mDnsException = new DnsException(ERROR_PARSE, pe); } maybeReportAnswer(); } diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index a3fd60e9d3b0..004f84422b44 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -900,9 +900,17 @@ public final class NetworkCapabilities implements Parcelable { * <p>For NetworkCapability instances being sent from ConnectivityService, this value MUST be * reset to Process.INVALID_UID unless all the following conditions are met: * + * <p>The caller is the network owner, AND one of the following sets of requirements is met: + * + * <ol> + * <li>The described Network is a VPN + * </ol> + * + * <p>OR: + * * <ol> - * <li>The destination app is the network owner - * <li>The destination app has the ACCESS_FINE_LOCATION permission granted + * <li>The calling app is the network owner + * <li>The calling app has the ACCESS_FINE_LOCATION permission granted * <li>The user's location toggle is on * </ol> * @@ -928,7 +936,16 @@ public final class NetworkCapabilities implements Parcelable { /** * Retrieves the UID of the app that owns this network. * - * <p>For user privacy reasons, this field will only be populated if: + * <p>For user privacy reasons, this field will only be populated if the following conditions + * are met: + * + * <p>The caller is the network owner, AND one of the following sets of requirements is met: + * + * <ol> + * <li>The described Network is a VPN + * </ol> + * + * <p>OR: * * <ol> * <li>The calling app is the network owner @@ -936,8 +953,8 @@ public final class NetworkCapabilities implements Parcelable { * <li>The user's location toggle is on * </ol> * - * Instances of NetworkCapabilities sent to apps without the appropriate permissions will - * have this field cleared out. + * Instances of NetworkCapabilities sent to apps without the appropriate permissions will have + * this field cleared out. */ public int getOwnerUid() { return mOwnerUid; diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 779f7bc91e8f..0b92b95128d3 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -155,6 +155,14 @@ public class NetworkUtils { public static native Network getDnsNetwork() throws ErrnoException; /** + * Allow/Disallow creating AF_INET/AF_INET6 sockets and DNS lookups for current process. + * + * @param allowNetworking whether to allow or disallow creating AF_INET/AF_INET6 sockets + * and DNS lookups. + */ + public static native void setAllowNetworkingForProcess(boolean allowNetworking); + + /** * Get the tcp repair window associated with the {@code fd}. * * @param fd the tcp socket's {@link FileDescriptor}. diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index e550f85e6b9a..98760761736d 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -26,7 +26,6 @@ import android.net.util.NetUtils; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import android.util.Pair; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -554,15 +553,45 @@ public final class RouteInfo implements Parcelable { } /** - * A helper class that contains the destination and the gateway in a {@code RouteInfo}, - * used by {@link ConnectivityService#updateRoutes} or + * A helper class that contains the destination, the gateway and the interface in a + * {@code RouteInfo}, used by {@link ConnectivityService#updateRoutes} or * {@link LinkProperties#addRoute} to calculate the list to be updated. + * {@code RouteInfo} objects with different interfaces are treated as different routes because + * *usually* on Android different interfaces use different routing tables, and moving a route + * to a new routing table never constitutes an update, but is always a remove and an add. * * @hide */ - public static class RouteKey extends Pair<IpPrefix, InetAddress> { - RouteKey(@NonNull IpPrefix destination, @Nullable InetAddress gateway) { - super(destination, gateway); + public static class RouteKey { + @NonNull private final IpPrefix mDestination; + @Nullable private final InetAddress mGateway; + @Nullable private final String mInterface; + + RouteKey(@NonNull IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface) { + mDestination = destination; + mGateway = gateway; + mInterface = iface; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof RouteKey)) { + return false; + } + RouteKey p = (RouteKey) o; + // No need to do anything special for scoped addresses. Inet6Address#equals does not + // consider the scope ID, but the netd route IPCs (e.g., INetd#networkAddRouteParcel) + // and the kernel ignore scoped addresses both in the prefix and in the nexthop and only + // look at RTA_OIF. + return Objects.equals(p.mDestination, mDestination) + && Objects.equals(p.mGateway, mGateway) + && Objects.equals(p.mInterface, mInterface); + } + + @Override + public int hashCode() { + return Objects.hash(mDestination, mGateway, mInterface); } } @@ -574,7 +603,7 @@ public final class RouteInfo implements Parcelable { */ @NonNull public RouteKey getRouteKey() { - return new RouteKey(mDestination, mGateway); + return new RouteKey(mDestination, mGateway, mInterface); } /** diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 5d2c9d18c00c..a4077fbee892 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -228,6 +228,13 @@ public class Process { */ public static final int EXT_OBB_RW_GID = 1079; + /** + * GID that corresponds to the INTERNET permission. + * Must match the value of AID_INET. + * @hide + */ + public static final int INET_GID = 3003; + /** {@hide} */ public static final int NOBODY_UID = 9999; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index a8391c2b5461..a415dc57e160 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -4095,14 +4095,14 @@ public class UserManager { } /** - * Returns true if the user switcher should be shown. - * I.e., returns whether the user switcher is enabled and there is something actionable to show. + * Returns true if the user switcher is enabled (regardless of whether there is anything + * interesting for it to show). * - * @return true if user switcher should be shown. + * @return true if user switcher is enabled * @hide */ public boolean isUserSwitcherEnabled() { - return isUserSwitcherEnabled(false); + return isUserSwitcherEnabled(true); } /** diff --git a/core/java/android/os/Users.md b/core/java/android/os/Users.md index 3bbbe5452fd3..b019b0dc178b 100644 --- a/core/java/android/os/Users.md +++ b/core/java/android/os/Users.md @@ -18,54 +18,80 @@ ## Concepts -### User +### Users and profiles -A user of a device e.g. usually a human being. Each user has its own home screen. +#### User -#### User Profile +A user is a representation of a person using a device, with their own distinct application data +and some unique settings. Throughout this document, the word 'user' will be used in this technical +sense, i.e. for this virtual environment, whereas the word 'person' will be used to denote an actual +human interacting with the device. -A user can have multiple profiles. E.g. one for the private life and one for work. Each profile -has a different set of apps and accounts but they share one home screen. All profiles of a -profile group can be active at the same time. - -Each profile has a separate [`userId`](#int-userid). Unless needed user profiles are treated as -completely separate users. +Each user has a separate [`userId`](#int-userid). #### Profile Group -All user profiles that share a home screen. You can list the profiles of a user via -`UserManager#getEnabledProfiles` (you usually don't deal with disabled profiles) +Often, there is a 1-to-1 mapping of people who use a device to 'users'; e.g. there may be two users +on a device - the owner and a guest, each with their own separate home screen. -#### Foreground user vs background user +However, Android also supports multiple profiles for a single person, e.g. one for their private +life and one for work, both sharing a single home screen. +Each profile in a profile group is a distinct user, with a unique [`userId`](#int-userid), and have +a different set of apps and accounts, +but they share a single UI, single launcher, and single wallpaper. +All profiles of a profile group can be active at the same time. -Only a single user profile group can be in the foreground. This is the user profile the user -currently interacts with. +You can list the profiles of a user via `UserManager#getEnabledProfiles` (you usually don't deal +with disabled profiles) -#### Parent user (profile) +#### Parent user -The main profile of a profile group, usually the personal (as opposed to work) profile. Get this via -`UserManager#getProfileParent` (returns `null` if the user does not have profiles) +The main user of a profile group, to which the other profiles of the group 'belong'. +This is usually the personal (as opposed to work) profile. Get this via +`UserManager#getProfileParent` (returns `null` if the user does not have profiles). -#### Managed user (profile) +#### Profile (Managed profile) -The other profiles of a profile group. The name comes from the fact that these profiles are usually +A profile of the parent user, i.e. a profile belonging to the same profile group as a parent user, +with whom they share a single home screen. +Currently, the only type of profile supported in AOSP is a 'Managed Profile'. +The name comes from the fact that these profiles are usually managed by a device policy controller app. You can create a managed profile from within the device policy controller app on your phone. +Note that, as a member of the profile group, the parent user may sometimes also be considered a +'profile', but generally speaking, the word 'profile' denotes a user that is subordinate to a +parent. + +#### Foreground user vs background user + +Only a single user can be in the foreground. +This is the user with whom the person using the device is currently interacting, or, in the case +of profiles, the parent profile of this user. +All other running users are background users. +Some users may not be running at all, neither in the foreground nor the background. + #### Account -An account of a user profile with a (usually internet based) service. E.g. aname@gmail.com or -aname@yahoo.com. Each profile can have multiple accounts. A profile does not have to have a +An account of a user with a (usually internet based) service. E.g. aname@gmail.com or +aname@yahoo.com. Each user can have multiple accounts. A user does not have to have a account. +#### System User + +The user with [`userId`](#int-userid) 0 denotes the system user, which is always required to be +running. + +On most devices, the system user is also used by the primary person using the device; however, +on certain types of devices, the system user may be a stand-alone user, not intended for direct +human interaction. + ## Data types ### int userId -... usually marked as `@UserIdInt` - -The id of a user profile. List all users via `adb shell dumpsys user`. There is no data type for a -user, all you can do is using the user id of the parent profile as a proxy for the user. +The id of a user. List all users via `adb shell dumpsys user`. +In code, these are sometimes marked as `@UserIdInt`. ### int uid @@ -97,10 +123,10 @@ mechanism should be access controlled by permissions. A system service should deal with users being started and stopped by overriding `SystemService.onSwitchUser` and `SystemService.onStopUser`. -If users profiles become inactive the system should stop all apps of this profile from interacting +If a user become inactive the system should stop all apps of this user from interacting with other apps or the system. -Another important lifecycle event is `onUnlockUser`. Only for unlocked user profiles you can access +Another important lifecycle event is `onUnlockUser`. Only for an unlocked user can you access all data, e.g. which packages are installed. You only want to deal with user profiles that diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index fd7cdda3808a..06254574401b 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -221,15 +221,16 @@ public abstract class VibrationEffect implements Parcelable { * * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For * each pair, the value in the amplitude array determines the strength of the vibration and the - * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no - * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored. + * value in the timing array determines how long it vibrates for, in milliseconds. Amplitude + * values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any + * pairs with a timing value of 0 will be ignored. * </p><p> * To cause the pattern to repeat, pass the index into the timings array at which to start the * repetition, or -1 to disable repeating. * </p> * - * @param timings The timing values of the timing / amplitude pairs. Timing values of 0 - * will cause the pair to be ignored. + * @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing + * values of 0 will cause the pair to be ignored. * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An * amplitude value of 0 implies the motor is off. diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index 220ce22ded5c..61e6a05fce37 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -111,6 +111,11 @@ interface IIncrementalService { void deleteStorage(int storageId); /** + * Permanently disable readlogs reporting for a storage given its ID. + */ + void disableReadLogs(int storageId); + + /** * Setting up native library directories and extract native libs onto a storage if needed. */ boolean configureNativeBinaries(int storageId, in @utf8InCpp String apkFullPath, in @utf8InCpp String libDirRelativePath, in @utf8InCpp String abi, boolean extractNativeLibs); diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java index 863d86ef88c9..31ccf95ba16f 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -153,6 +153,13 @@ public final class IncrementalFileStorages { } /** + * Permanently disables readlogs. + */ + public void disableReadLogs() { + mDefaultStorage.disableReadLogs(); + } + + /** * Resets the states and unbinds storage instances for an installation session. * TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept */ diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index 6200a38fe13c..ca6114f29b9c 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -418,6 +418,17 @@ public final class IncrementalStorage { private static final int INCFS_MAX_ADD_DATA_SIZE = 128; /** + * Permanently disable readlogs collection. + */ + public void disableReadLogs() { + try { + mService.disableReadLogs(mId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Deserialize and validate v4 signature bytes. */ private static void validateV4Signature(@Nullable byte[] v4signatureBytes) diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index e8806a03d00e..0abf8ae352af 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -16,9 +16,11 @@ package android.os.storage; +import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.OP_LEGACY_STORAGE; +import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE; import static android.app.AppOpsManager.OP_READ_MEDIA_AUDIO; import static android.app.AppOpsManager.OP_READ_MEDIA_IMAGES; @@ -1853,7 +1855,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionReadAudio(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) { return false; } @@ -1864,7 +1866,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionWriteAudio(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) { return false; } @@ -1875,7 +1877,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionReadVideo(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) { return false; } @@ -1886,7 +1888,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionWriteVideo(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) { return false; } @@ -1897,7 +1899,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionReadImages(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) { return false; } @@ -1908,7 +1910,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionWriteImages(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) { return false; } @@ -1916,6 +1918,24 @@ public class StorageManager { OP_WRITE_MEDIA_IMAGES); } + private boolean checkExternalStoragePermissionAndAppOp(boolean enforce, + int pid, int uid, String packageName, @Nullable String featureId, String permission, + int op) { + // First check if app has MANAGE_EXTERNAL_STORAGE. + final int mode = mAppOps.noteOpNoThrow(OP_MANAGE_EXTERNAL_STORAGE, uid, packageName, + featureId, null); + if (mode == AppOpsManager.MODE_ALLOWED) { + return true; + } + if (mode == AppOpsManager.MODE_DEFAULT && mContext.checkPermission( + MANAGE_EXTERNAL_STORAGE, pid, uid) == PERMISSION_GRANTED) { + return true; + } + // If app doesn't have MANAGE_EXTERNAL_STORAGE, then check if it has requested granular + // permission. + return checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, permission, op); + } + /** {@hide} */ @VisibleForTesting public @NonNull ParcelFileDescriptor openProxyFileDescriptor( diff --git a/core/java/android/permission/Permissions.md b/core/java/android/permission/Permissions.md index 2bf08e2ff2d4..1ef3ad211cee 100644 --- a/core/java/android/permission/Permissions.md +++ b/core/java/android/permission/Permissions.md @@ -706,9 +706,9 @@ App-op permissions are user-switchable permissions that are not runtime permissi be used for permissions that are really only meant to be ever granted to a very small amount of apps. Traditionally granting these permissions is intentionally very heavy weight so that the user really needs to understand the use case. For example one use case is the -`INTERACT_ACROSS_PROFILES` permission that allows apps of different -[user profiles](../os/Users.md#user-profile) to interact. Of course this is breaking a very basic -security container and hence should only every be granted with a lot of care. +`INTERACT_ACROSS_PROFILES` permission that allows apps of different users within the same +[profile group](../os/Users.md#profile-group) to interact. Of course this is breaking a very basic +security container and hence should only ever be granted with a lot of care. **Warning:** Most app-op permissions follow this logic, but most of them also have exceptions and special behavior. Hence this section is a guideline, not a rule. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e10fceaa5bc7..cd2467f9157d 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; @@ -1971,6 +1972,10 @@ public final class Settings { * Input: Nothing. * <p> * Output: Nothing. + * <p class="note"> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS"; @@ -6238,6 +6243,8 @@ public final class Settings { * determines if the IME should be shown when a hard keyboard is attached. * @hide */ + @TestApi + @SuppressLint("NoSettingsProvider") public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; /** diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index c2234bad3803..95cc64ae8aab 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -564,9 +564,9 @@ public abstract class AugmentedAutofillService extends Service { } void reportResult(@Nullable List<Dataset> inlineSuggestionsData, - @Nullable Bundle clientState) { + @Nullable Bundle clientState, boolean showingFillWindow) { try { - mCallback.onSuccess(inlineSuggestionsData, clientState); + mCallback.onSuccess(inlineSuggestionsData, clientState, showingFillWindow); } catch (RemoteException e) { Log.e(TAG, "Error calling back with the inline suggestions data: " + e); } diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index 8ba5c173890c..fc3baf1c9836 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -56,23 +56,24 @@ public final class FillCallback { if (response == null) { mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE); - mProxy.reportResult(/* inlineSuggestionsData */ null, /* clientState */ null); + mProxy.reportResult(/* inlineSuggestionsData */ null, /* clientState */ + null, /* showingFillWindow */ false); return; } - List<Dataset> inlineSuggestions = response.getInlineSuggestions(); - Bundle clientState = response.getClientState(); - // We need to report result regardless of whether inline suggestions are returned or not. - mProxy.reportResult(inlineSuggestions, clientState); + final List<Dataset> inlineSuggestions = response.getInlineSuggestions(); + final Bundle clientState = response.getClientState(); + final FillWindow fillWindow = response.getFillWindow(); + boolean showingFillWindow = false; if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) { mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE); - return; - } - - final FillWindow fillWindow = response.getFillWindow(); - if (fillWindow != null) { + } else if (fillWindow != null) { fillWindow.show(); + showingFillWindow = true; } + // We need to report result regardless of whether inline suggestions are returned or not. + mProxy.reportResult(inlineSuggestions, clientState, showingFillWindow); + // TODO(b/123099468): must notify the server so it can update the session state to avoid // showing conflicting UIs (for example, if a new request is made to the main autofill // service and it now wants to show something). diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl index 609e382e2b96..4dfdd4db27e5 100644 --- a/core/java/android/service/autofill/augmented/IFillCallback.aidl +++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl @@ -30,7 +30,9 @@ import java.util.List; */ interface IFillCallback { void onCancellable(in ICancellationSignal cancellation); - void onSuccess(in @nullable List<Dataset> inlineSuggestionsData, in @nullable Bundle clientState); + void onSuccess(in @nullable List<Dataset> inlineSuggestionsData, + in @nullable Bundle clientState, + boolean showingFillWindow); boolean isCompleted(); void cancel(); } diff --git a/core/java/android/service/controls/templates/ControlTemplate.java b/core/java/android/service/controls/templates/ControlTemplate.java index 1e16273c455b..e592fad394b8 100644 --- a/core/java/android/service/controls/templates/ControlTemplate.java +++ b/core/java/android/service/controls/templates/ControlTemplate.java @@ -214,10 +214,13 @@ public abstract class ControlTemplate { } /** - * Get a singleton {@link ControlTemplate} that has no features. + * Get a singleton {@link ControlTemplate}, which supports no direct user input. * - * This template has no distinctive field, not even an identifier. Used for a {@link Control} - * that accepts no type of input, or when there is no known state. + * Used by {@link Control.StatelessBuilder} when there is no known state. Can also be used + * in {@link Control.StatefulBuilder} for conveying information to a user about the + * {@link Control} but direct user interaction is not desired. Since this template has no + * corresponding {@link ControlAction}, any user interaction will launch the + * {@link Control#getAppIntent()}. * * @return a singleton {@link ControlTemplate} to indicate no specific template is used by * this {@link Control} diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index f944dd78dc3d..0d420c5936ae 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1184,7 +1184,8 @@ public abstract class WallpaperService extends Service { // may have been destroyed so now we need to make // sure it is re-created. doOffsetsChanged(false); - updateSurface(false, false, false); + // force relayout to get new surface + updateSurface(true, false, false); } onVisibilityChanged(visible); } diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index ef21900dc3e3..4adcd6948f85 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -219,6 +219,9 @@ public class PhoneStateListener { /** * Listen for changes to observed cell info. * + * Listening to this event requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} + * permission. + * * @see #onCellInfoChanged */ public static final int LISTEN_CELL_INFO = 0x00000400; @@ -461,6 +464,9 @@ public class PhoneStateListener { * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). * + * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless + * of whether the calling app has carrier privileges. + * * @see #onRegistrationFailed */ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) @@ -472,6 +478,9 @@ public class PhoneStateListener { * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). * + * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless + * of whether the calling app has carrier privileges. + * * @see #onBarringInfoChanged */ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) @@ -569,6 +578,11 @@ public class PhoneStateListener { * subId. Otherwise, this callback applies to * {@link SubscriptionManager#getDefaultSubscriptionId()}. * + * The instance of {@link ServiceState} passed as an argument here will have various levels of + * location information stripped from it depending on the location permissions that your app + * holds. Only apps holding the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission will + * receive all the information in {@link ServiceState}. + * * @see ServiceState#STATE_EMERGENCY_ONLY * @see ServiceState#STATE_IN_SERVICE * @see ServiceState#STATE_OUT_OF_SERVICE diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 8db1703a627f..0cc469a2d5eb 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -241,13 +241,26 @@ public final class Display { * This flag identifies secondary displays that should show system decorations, such as status * bar, navigation bar, home activity or IME. * </p> + * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p> * + * @see #getFlags() * @hide */ // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 6; /** + * Flag: The display is trusted to show system decorations and receive inputs without users' + * touch. + * @see #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + * + * @see #getFlags() + * @hide + */ + @TestApi + public static final int FLAG_TRUSTED = 1 << 7; + + /** * Display flag: Indicates that the contents of the display should not be scaled * to fit the physical screen dimensions. Used for development only to emulate * devices with smaller physicals screens while preserving density. @@ -564,6 +577,7 @@ public final class Display { * @see #FLAG_SUPPORTS_PROTECTED_BUFFERS * @see #FLAG_SECURE * @see #FLAG_PRIVATE + * @see #FLAG_ROUND */ public int getFlags() { return mFlags; @@ -1222,6 +1236,16 @@ public final class Display { Display.FLAG_PRESENTATION; } + /** + * @return {@code true} if the display is a trusted display. + * + * @see #FLAG_TRUSTED + * @hide + */ + public boolean isTrusted() { + return (mFlags & FLAG_TRUSTED) == FLAG_TRUSTED; + } + private void updateDisplayInfoLocked() { // Note: The display manager caches display info objects on our behalf. DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId); diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index d369883f3ac3..b1ede4102bec 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -717,6 +717,15 @@ public final class DisplayInfo implements Parcelable { if ((flags & Display.FLAG_ROUND) != 0) { result.append(", FLAG_ROUND"); } + if ((flags & Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) { + result.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD"); + } + if ((flags & Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) { + result.append(", FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS"); + } + if ((flags & Display.FLAG_TRUSTED) != 0) { + result.append(", FLAG_TRUSTED"); + } return result.toString(); } } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index e4d53c64a063..b4dae566ce2a 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -260,6 +260,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final boolean mHasAnimationCallbacks; private final @InsetsType int mRequestedTypes; private final long mDurationMs; + private final boolean mDisable; private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = new ThreadLocal<AnimationHandler>() { @@ -272,11 +273,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation }; public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks, - int requestedTypes) { + int requestedTypes, boolean disable) { mShow = show; mHasAnimationCallbacks = hasAnimationCallbacks; mRequestedTypes = requestedTypes; mDurationMs = calculateDurationMs(); + mDisable = disable; } @Override @@ -284,6 +286,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mController = controller; if (DEBUG) Log.d(TAG, "default animation onReady types: " + types); + if (mDisable) { + onAnimationFinish(); + return; + } mAnimator = ValueAnimator.ofFloat(0f, 1f); mAnimator.setDuration(mDurationMs); mAnimator.setInterpolator(new LinearInterpolator()); @@ -477,6 +483,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private DisplayCutout mLastDisplayCutout; private boolean mStartingAnimation; private int mCaptionInsetsHeight = 0; + private boolean mAnimationsDisabled; private Runnable mPendingControlTimeout = this::abortPendingImeControlRequest; private final ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners @@ -575,21 +582,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public boolean onStateChanged(InsetsState state) { - boolean localStateChanged = !mState.equals(state, true /* excludingCaptionInsets */) + boolean stateChanged = !mState.equals(state, true /* excludingCaptionInsets */, + false /* excludeInvisibleIme */) || !captionInsetsUnchanged(); - if (!localStateChanged && mLastDispatchedState.equals(state)) { + if (!stateChanged && mLastDispatchedState.equals(state)) { return false; } if (DEBUG) Log.d(TAG, "onStateChanged: " + state); updateState(state); + + boolean localStateChanged = !mState.equals(mLastDispatchedState, + true /* excludingCaptionInsets */, true /* excludeInvisibleIme */); mLastDispatchedState.set(state, true /* copySources */); + applyLocalVisibilityOverride(); if (localStateChanged) { - if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged"); + if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged, send state to WM: " + mState); mHost.notifyInsetsChanged(); - } - if (!mState.equals(mLastDispatchedState, true /* excludingCaptionInsets */)) { - if (DEBUG) Log.d(TAG, "onStateChanged, send state to WM: " + mState); updateRequestedState(); } return true; @@ -1161,8 +1170,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks(); - final InternalAnimationControlListener listener = - new InternalAnimationControlListener(show, hasAnimationCallbacks, types); + final InternalAnimationControlListener listener = new InternalAnimationControlListener( + show, hasAnimationCallbacks, types, mAnimationsDisabled); // Show/hide animations always need to be relative to the display frame, in order that shown // and hidden state insets are correct. @@ -1277,6 +1286,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return mHost.getSystemBarsBehavior(); } + @Override + public void setAnimationsDisabled(boolean disable) { + mAnimationsDisabled = disable; + } + private @InsetsType int calculateControllableTypes() { @InsetsType int result = 0; for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index b0158467a17b..15b9a9330392 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -191,6 +191,14 @@ public class InsetsSource implements Parcelable { @Override public boolean equals(Object o) { + return equals(o, false); + } + + /** + * @param excludeInvisibleImeFrames If {@link InsetsState#ITYPE_IME} frames should be ignored + * when IME is not visible. + */ + public boolean equals(Object o, boolean excludeInvisibleImeFrames) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -198,6 +206,7 @@ public class InsetsSource implements Parcelable { if (mType != that.mType) return false; if (mVisible != that.mVisible) return false; + if (excludeInvisibleImeFrames && !mVisible && mType == ITYPE_IME) return true; if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; return mFrame.equals(that.mFrame); } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index ae70a4971776..565846638acc 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -221,9 +221,10 @@ public class InsetsSourceConsumer { final boolean hasControl = mSourceControl != null; // We still need to let the legacy app know the visibility change even if we don't have the - // control. + // control. If we don't have the source, we don't change the requested visibility for making + // the callback behavior compatible. mController.updateCompatSysUiVisibility( - mType, hasControl ? mRequestedVisible : isVisible, hasControl); + mType, (hasControl || source == null) ? mRequestedVisible : isVisible, hasControl); // If we don't have control, we are not able to change the visibility. if (!hasControl) { diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index f0bca260be38..17620fa3bb4b 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -357,6 +357,19 @@ public class InsetsState implements Parcelable { return mSources.get(type); } + /** + * Returns the source visibility or the default visibility if the source doesn't exist. This is + * useful if when treating this object as a request. + * + * @param type The {@link InternalInsetsType} to query. + * @return {@code true} if the source is visible or the type is default visible and the source + * doesn't exist. + */ + public boolean getSourceOrDefaultVisibility(@InternalInsetsType int type) { + final InsetsSource source = mSources.get(type); + return source != null ? source.isVisible() : getDefaultVisibility(type); + } + public void setDisplayFrame(Rect frame) { mDisplayFrame.set(frame); } @@ -388,20 +401,6 @@ public class InsetsState implements Parcelable { } } - /** - * A shortcut for setting the visibility of the source. - * - * @param type The {@link InternalInsetsType} of the source to set the visibility - * @param referenceState The {@link InsetsState} for reference - */ - public void setSourceVisible(@InternalInsetsType int type, InsetsState referenceState) { - InsetsSource source = mSources.get(type); - InsetsSource referenceSource = referenceState.mSources.get(type); - if (source != null && referenceSource != null) { - source.setVisible(referenceSource.isVisible()); - } - } - public void set(InsetsState other) { set(other, false /* copySources */); } @@ -490,7 +489,7 @@ public class InsetsState implements Parcelable { } } - public static boolean getDefaultVisibility(@InsetsType int type) { + public static boolean getDefaultVisibility(@InternalInsetsType int type) { return type != ITYPE_IME; } @@ -555,7 +554,7 @@ public class InsetsState implements Parcelable { @Override public boolean equals(Object o) { - return equals(o, false); + return equals(o, false, false); } /** @@ -564,10 +563,13 @@ public class InsetsState implements Parcelable { * excluded. * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but * ignore the caption insets source value. + * @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is + * not visible. * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise. */ @VisibleForTesting - public boolean equals(Object o, boolean excludingCaptionInsets) { + public boolean equals(Object o, boolean excludingCaptionInsets, + boolean excludeInvisibleImeFrames) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } @@ -598,7 +600,7 @@ public class InsetsState implements Parcelable { if (otherSource == null) { return false; } - if (!otherSource.equals(source)) { + if (!otherSource.equals(source, excludeInvisibleImeFrames)) { return false; } } diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java index 0283ada0dd40..c018d1cf1782 100644 --- a/core/java/android/view/PendingInsetsController.java +++ b/core/java/android/view/PendingInsetsController.java @@ -38,6 +38,7 @@ public class PendingInsetsController implements WindowInsetsController { private @Appearance int mAppearance; private @Appearance int mAppearanceMask; private @Behavior int mBehavior = KEEP_BEHAVIOR; + private boolean mAnimationsDisabled; private final InsetsState mDummyState = new InsetsState(); private InsetsController mReplayedInsetsController; private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners @@ -103,6 +104,15 @@ public class PendingInsetsController implements WindowInsetsController { } @Override + public void setAnimationsDisabled(boolean disable) { + if (mReplayedInsetsController != null) { + mReplayedInsetsController.setAnimationsDisabled(disable); + } else { + mAnimationsDisabled = disable; + } + } + + @Override public InsetsState getState() { return mDummyState; } @@ -151,6 +161,9 @@ public class PendingInsetsController implements WindowInsetsController { if (mCaptionInsetsHeight != 0) { controller.setCaptionInsetsHeight(mCaptionInsetsHeight); } + if (mAnimationsDisabled) { + controller.setAnimationsDisabled(true); + } int size = mRequests.size(); for (int i = 0; i < size; i++) { mRequests.get(i).replay(controller); @@ -167,6 +180,7 @@ public class PendingInsetsController implements WindowInsetsController { mBehavior = KEEP_BEHAVIOR; mAppearance = 0; mAppearanceMask = 0; + mAnimationsDisabled = false; // After replaying, we forward everything directly to the replayed instance. mReplayedInsetsController = controller; diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 6f73e8985a8a..aac92709a177 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -102,6 +102,8 @@ public final class SurfaceControl implements Parcelable { long otherTransactionObj); private static native void nativeSetAnimationTransaction(long transactionObj); private static native void nativeSetEarlyWakeup(long transactionObj); + private static native void nativeSetEarlyWakeupStart(long transactionObj); + private static native void nativeSetEarlyWakeupEnd(long transactionObj); private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder); private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject, @@ -230,7 +232,6 @@ public final class SurfaceControl implements Parcelable { */ public long mNativeObject; private long mNativeHandle; - private Throwable mReleaseStack = null; // TODO: Move this to native. private final Object mSizeLock = new Object(); @@ -442,13 +443,6 @@ public final class SurfaceControl implements Parcelable { } mNativeObject = nativeObject; mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0; - if (mNativeObject == 0) { - if (Build.IS_DEBUGGABLE) { - mReleaseStack = new Throwable("assigned zero nativeObject here"); - } - } else { - mReleaseStack = null; - } } /** @@ -1024,22 +1018,11 @@ public final class SurfaceControl implements Parcelable { nativeRelease(mNativeObject); mNativeObject = 0; mNativeHandle = 0; - if (Build.IS_DEBUGGABLE) { - mReleaseStack = new Throwable("released here"); - } mCloseGuard.close(); } } /** - * Returns the call stack that assigned mNativeObject to zero. - * @hide - */ - public Throwable getReleaseStack() { - return mReleaseStack; - } - - /** * Disconnect any client still connected to the surface. * @hide */ @@ -1050,11 +1033,8 @@ public final class SurfaceControl implements Parcelable { } private void checkNotReleased() { - if (mNativeObject == 0) { - Log.wtf(TAG, "Invalid " + this + " caused by:", mReleaseStack); - throw new NullPointerException( - "mNativeObject of " + this + " is null. Have you called release() already?"); - } + if (mNativeObject == 0) throw new NullPointerException( + "Invalid " + this + ", mNativeObject is null. Have you called release() already?"); } /** @@ -2797,6 +2777,8 @@ public final class SurfaceControl implements Parcelable { } /** + * @deprecated use {@link Transaction#setEarlyWakeupStart()} + * * Indicate that SurfaceFlinger should wake up earlier than usual as a result of this * transaction. This should be used when the caller thinks that the scene is complex enough * that it's likely to hit GL composition, and thus, SurfaceFlinger needs to more time in @@ -2805,11 +2787,35 @@ public final class SurfaceControl implements Parcelable { * Corresponds to setting ISurfaceComposer::eEarlyWakeup * @hide */ + @Deprecated public Transaction setEarlyWakeup() { nativeSetEarlyWakeup(mNativeObject); return this; } + /** + * Provides a hint to SurfaceFlinger to change its offset so that SurfaceFlinger wakes up + * earlier to compose surfaces. The caller should use this as a hint to SurfaceFlinger + * when the scene is complex enough to use GPU composition. The hint will remain active + * until until the client calls {@link Transaction#setEarlyWakeupEnd}. + * + * @hide + */ + public Transaction setEarlyWakeupStart() { + nativeSetEarlyWakeupStart(mNativeObject); + return this; + } + + /** + * Removes the early wake up hint set by {@link Transaction#setEarlyWakeupStart}. + * + * @hide + */ + public Transaction setEarlyWakeupEnd() { + nativeSetEarlyWakeupEnd(mNativeObject); + return this; + } + /** * Sets an arbitrary piece of metadata on the surface. This is a helper for int data. * @hide diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 57f91ed3c0ae..90e1eab09fd6 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -640,7 +640,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mTmpRect.set(0, 0, mSurfaceWidth, mSurfaceHeight); } SyncRtSurfaceTransactionApplier applier = new SyncRtSurfaceTransactionApplier(this); - applier.scheduleApply(false /* earlyWakeup */, + applier.scheduleApply( new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(mSurfaceControl) .withWindowCrop(mTmpRect) .build()); @@ -893,12 +893,15 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } return; } - ViewRootImpl viewRoot = getViewRootImpl(); - if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) { - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + " updateSurface: no valid surface"); - } + final ViewRootImpl viewRoot = getViewRootImpl(); + + if (viewRoot == null) { + return; + } + + if (viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) { + notifySurfaceDestroyed(); + releaseSurfaces(); return; } @@ -1109,28 +1112,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall final boolean surfaceChanged = creating; if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) { mSurfaceCreated = false; - if (mSurface.isValid()) { - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "visibleChanged -- surfaceDestroyed"); - callbacks = getSurfaceCallbacks(); - for (SurfaceHolder.Callback c : callbacks) { - c.surfaceDestroyed(mSurfaceHolder); - } - // Since Android N the same surface may be reused and given to us - // again by the system server at a later point. However - // as we didn't do this in previous releases, clients weren't - // necessarily required to clean up properly in - // surfaceDestroyed. This leads to problems for example when - // clients don't destroy their EGL context, and try - // and create a new one on the same surface following reuse. - // Since there is no valid use of the surface in-between - // surfaceDestroyed and surfaceCreated, we force a disconnect, - // so the next connect will always work if we end up reusing - // the surface. - if (mSurface.isValid()) { - mSurface.forceScopedDisconnect(); - } - } + notifySurfaceDestroyed(); } if (creating) { @@ -1638,9 +1620,14 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private void updateRelativeZ(Transaction t) { - SurfaceControl viewRoot = getViewRootImpl().getSurfaceControl(); - t.setRelativeLayer(mBackgroundControl, viewRoot, Integer.MIN_VALUE); - t.setRelativeLayer(mSurfaceControl, viewRoot, mSubLayer); + final ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot == null) { + // We were just detached. + return; + } + final SurfaceControl viewRootControl = viewRoot.getSurfaceControl(); + t.setRelativeLayer(mBackgroundControl, viewRootControl, Integer.MIN_VALUE); + t.setRelativeLayer(mSurfaceControl, viewRootControl, mSubLayer); } /** @@ -1786,6 +1773,31 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } } + private void notifySurfaceDestroyed() { + if (mSurface.isValid()) { + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "surfaceDestroyed"); + SurfaceHolder.Callback[] callbacks = getSurfaceCallbacks(); + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceDestroyed(mSurfaceHolder); + } + // Since Android N the same surface may be reused and given to us + // again by the system server at a later point. However + // as we didn't do this in previous releases, clients weren't + // necessarily required to clean up properly in + // surfaceDestroyed. This leads to problems for example when + // clients don't destroy their EGL context, and try + // and create a new one on the same surface following reuse. + // Since there is no valid use of the surface in-between + // surfaceDestroyed and surfaceCreated, we force a disconnect, + // so the next connect will always work if we end up reusing + // the surface. + if (mSurface.isValid()) { + mSurface.forceScopedDisconnect(); + } + } + } + /** * Wrapper of accessibility embedded connection for embedded view hierarchy. */ diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java index 9c97f3e5b503..062285ff2f5d 100644 --- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java +++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java @@ -53,11 +53,10 @@ public class SyncRtSurfaceTransactionApplier { /** * Schedules applying surface parameters on the next frame. * - * @param earlyWakeup Whether to set {@link Transaction#setEarlyWakeup()} on transaction. * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into * this method to avoid synchronization issues. */ - public void scheduleApply(boolean earlyWakeup, final SurfaceParams... params) { + public void scheduleApply(final SurfaceParams... params) { if (mTargetViewRootImpl == null) { return; } @@ -67,7 +66,7 @@ public class SyncRtSurfaceTransactionApplier { return; } Transaction t = new Transaction(); - applyParams(t, frame, earlyWakeup, params); + applyParams(t, frame, params); }); // Make sure a frame gets scheduled. @@ -78,12 +77,10 @@ public class SyncRtSurfaceTransactionApplier { * Applies surface parameters on the next frame. * @param t transaction to apply all parameters in. * @param frame frame to synchronize to. Set -1 when sync is not required. - * @param earlyWakeup Whether to set {@link Transaction#setEarlyWakeup()} on transaction. * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into * this method to avoid synchronization issues. */ - void applyParams(Transaction t, long frame, boolean earlyWakeup, - final SurfaceParams... params) { + void applyParams(Transaction t, long frame, final SurfaceParams... params) { for (int i = params.length - 1; i >= 0; i--) { SurfaceParams surfaceParams = params[i]; SurfaceControl surface = surfaceParams.surface; @@ -92,9 +89,6 @@ public class SyncRtSurfaceTransactionApplier { } applyParams(t, surfaceParams, mTmpFloat9); } - if (earlyWakeup) { - t.setEarlyWakeup(); - } t.apply(); } diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java index 686d561a1a5a..31a44023b036 100644 --- a/core/java/android/view/ViewRootInsetsControllerHost.java +++ b/core/java/android/view/ViewRootInsetsControllerHost.java @@ -120,13 +120,12 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host { mApplier = new SyncRtSurfaceTransactionApplier(mViewRoot.mView); } if (mViewRoot.mView.isHardwareAccelerated()) { - mApplier.scheduleApply(false /* earlyWakeup */, params); + mApplier.scheduleApply(params); } else { // Window doesn't support hardware acceleration, no synchronization for now. // TODO(b/149342281): use mViewRoot.mSurface.getNextFrameNumber() to sync on every // frame instead. - mApplier.applyParams(new SurfaceControl.Transaction(), -1 /* frame */, - false /* earlyWakeup */, params); + mApplier.applyParams(new SurfaceControl.Transaction(), -1 /* frame */, params); } } diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 3d348efc7f0f..1a9003581078 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -222,6 +222,13 @@ public interface WindowInsetsController { @Behavior int getSystemBarsBehavior(); /** + * Disables or enables the animations. + * + * @hide + */ + void setAnimationsDisabled(boolean disable); + + /** * @hide */ InsetsState getState(); diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index a5ec4e900641..d2e506ea550c 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -41,7 +41,6 @@ public class WindowlessWindowManager implements IWindowSession { private final static String TAG = "WindowlessWindowManager"; private class State { - //TODO : b/150190730 we should create it when view show and release it when view invisible. SurfaceControl mSurfaceControl; WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); int mDisplayId; diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 301ce9f013e4..3f5ef5a2651d 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -39,8 +39,8 @@ import com.android.internal.util.Preconditions; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.security.SecureRandom; import java.util.ArrayList; -import java.util.Random; /** * Session used when the Android a system-provided content capture service @@ -50,7 +50,9 @@ public abstract class ContentCaptureSession implements AutoCloseable { private static final String TAG = ContentCaptureSession.class.getSimpleName(); - private static final Random sIdGenerator = new Random(); + // TODO(b/158778794): to make the session ids truly globally unique across + // processes, we may need to explore other options. + private static final SecureRandom ID_GENERATOR = new SecureRandom(); /** * Initial state, when there is no session. @@ -622,7 +624,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { private static int getRandomSessionId() { int id; do { - id = sIdGenerator.nextInt(); + id = ID_GENERATOR.nextInt(); } while (id == NO_SESSION_ID); return id; } diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index e07181abe19c..843700cef55e 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -971,7 +971,8 @@ public final class SelectionActionModeHelper { return new TextClassifierEvent.LanguageDetectionEvent.Builder(eventType) .setEventContext(classificationContext) .setResultId(classification.getId()) - .setEntityTypes(language) + // b/158481016: Disable language logging. + //.setEntityTypes(language) .setScores(score) .setActionIndices(classification.getActions().indexOf(translateAction)) .setModelName(model) diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java index 6a85de5ca757..8ca218c1d1a7 100644 --- a/core/java/android/widget/inline/InlineContentView.java +++ b/core/java/android/widget/inline/InlineContentView.java @@ -27,6 +27,7 @@ import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup; +import android.view.ViewTreeObserver.OnPreDrawListener; import java.util.function.Consumer; @@ -130,6 +131,16 @@ public class InlineContentView extends ViewGroup { @Nullable private SurfacePackageUpdater mSurfacePackageUpdater; + @NonNull + private final OnPreDrawListener mDrawListener = new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + int visibility = InlineContentView.this.isShown() ? VISIBLE : GONE; + mSurfaceView.setVisibility(visibility); + return true; + } + }; + /** * @inheritDoc * @hide @@ -202,6 +213,8 @@ public class InlineContentView extends ViewGroup { } }); } + mSurfaceView.setVisibility(VISIBLE); + getViewTreeObserver().addOnPreDrawListener(mDrawListener); } @Override @@ -211,6 +224,7 @@ public class InlineContentView extends ViewGroup { if (mSurfacePackageUpdater != null) { mSurfacePackageUpdater.onSurfacePackageReleased(); } + getViewTreeObserver().removeOnPreDrawListener(mDrawListener); } @Override diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 67c97ae29186..dc4c8fd76e8e 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -195,6 +195,7 @@ public class ChooserActivity extends ResolverActivity implements private boolean mIsAppPredictorComponentAvailable; private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache; private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache; + private Map<ComponentName, ComponentName> mChooserTargetComponentNameCache; public static final int TARGET_TYPE_DEFAULT = 0; public static final int TARGET_TYPE_CHOOSER_TARGET = 1; @@ -511,6 +512,11 @@ public class ChooserActivity extends ResolverActivity implements adapterForUserHandle.addServiceResults(sri.originalTarget, sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET, /* directShareShortcutInfoCache */ null, mServiceConnections); + if (!sri.resultTargets.isEmpty() && sri.originalTarget != null) { + mChooserTargetComponentNameCache.put( + sri.resultTargets.get(0).getComponentName(), + sri.originalTarget.getResolvedComponentName()); + } } } unbindService(sri.connection); @@ -772,6 +778,7 @@ public class ChooserActivity extends ResolverActivity implements target.getAction() ); mDirectShareShortcutInfoCache = new HashMap<>(); + mChooserTargetComponentNameCache = new HashMap<>(); } @Override @@ -1063,6 +1070,10 @@ public class ChooserActivity extends ResolverActivity implements @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + ViewPager viewPager = findViewById(R.id.profile_pager); + if (shouldShowTabs() && viewPager.isLayoutRtl()) { + mMultiProfilePagerAdapter.setupViewPager(viewPager); + } mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation); adjustPreviewWidth(newConfig.orientation, null); @@ -2238,15 +2249,18 @@ public class ChooserActivity extends ResolverActivity implements List<AppTargetId> targetIds = new ArrayList<>(); for (ChooserTargetInfo chooserTargetInfo : surfacedTargetInfo) { ChooserTarget chooserTarget = chooserTargetInfo.getChooserTarget(); - String componentName = chooserTarget.getComponentName().flattenToString(); + ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault( + chooserTarget.getComponentName(), chooserTarget.getComponentName()); if (mDirectShareShortcutInfoCache.containsKey(chooserTarget)) { String shortcutId = mDirectShareShortcutInfoCache.get(chooserTarget).getId(); targetIds.add(new AppTargetId( - String.format("%s/%s/%s", shortcutId, componentName, SHORTCUT_TARGET))); + String.format("%s/%s/%s", shortcutId, componentName.flattenToString(), + SHORTCUT_TARGET))); } else { String titleHash = ChooserUtil.md5(chooserTarget.getTitle().toString()); targetIds.add(new AppTargetId( - String.format("%s/%s/%s", titleHash, componentName, CHOOSER_TARGET))); + String.format("%s/%s/%s", titleHash, componentName.flattenToString(), + CHOOSER_TARGET))); } } directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds); @@ -2268,7 +2282,8 @@ public class ChooserActivity extends ResolverActivity implements } if (mChooserTargetRankingEnabled && appTarget == null) { // Send ChooserTarget sharing info to AppPredictor. - ComponentName componentName = chooserTarget.getComponentName(); + ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault( + chooserTarget.getComponentName(), chooserTarget.getComponentName()); try { appTarget = new AppTarget.Builder( new AppTargetId(componentName.flattenToString()), @@ -2796,17 +2811,7 @@ public class ChooserActivity extends ResolverActivity implements || chooserListAdapter.mDisplayList.isEmpty()) { chooserListAdapter.notifyDataSetChanged(); } else { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... voids) { - chooserListAdapter.updateAlphabeticalList(); - return null; - } - @Override - protected void onPostExecute(Void aVoid) { - chooserListAdapter.notifyDataSetChanged(); - } - }.execute(); + chooserListAdapter.updateAlphabeticalList(); } // don't support direct share on low ram devices @@ -2816,8 +2821,7 @@ public class ChooserActivity extends ResolverActivity implements } // no need to query direct share for work profile when its turned off - UserManager userManager = getSystemService(UserManager.class); - if (userManager.isQuietModeEnabled(chooserListAdapter.getUserHandle())) { + if (isQuietModeEnabled(chooserListAdapter.getUserHandle())) { getChooserActivityLogger().logSharesheetAppLoadComplete(); return; } @@ -2841,6 +2845,12 @@ public class ChooserActivity extends ResolverActivity implements getChooserActivityLogger().logSharesheetAppLoadComplete(); } + @VisibleForTesting + protected boolean isQuietModeEnabled(UserHandle userHandle) { + UserManager userManager = getSystemService(UserManager.class); + return userManager.isQuietModeEnabled(userHandle); + } + private void setupScrollListener() { if (mResolverDrawerLayout == null) { return; @@ -3095,7 +3105,7 @@ public class ChooserActivity extends ResolverActivity implements setVerticalScrollEnabled(false); } } else if (state == ViewPager.SCROLL_STATE_IDLE) { - if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) { + if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) { mScrollStatus = SCROLL_STATUS_IDLE; setVerticalScrollEnabled(true); } diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index d6ff7b13c934..05169023000f 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -275,33 +275,43 @@ public class ChooserListAdapter extends ResolverListAdapter { } void updateAlphabeticalList() { - mSortedList.clear(); - List<DisplayResolveInfo> tempList = new ArrayList<>(); - tempList.addAll(mDisplayList); - tempList.addAll(mCallerTargets); - if (mEnableStackedApps) { - // Consolidate multiple targets from same app. - Map<String, DisplayResolveInfo> consolidated = new HashMap<>(); - for (DisplayResolveInfo info : tempList) { - String packageName = info.getResolvedComponentName().getPackageName(); - DisplayResolveInfo multiDri = consolidated.get(packageName); - if (multiDri == null) { - consolidated.put(packageName, info); - } else if (multiDri instanceof MultiDisplayResolveInfo) { - ((MultiDisplayResolveInfo) multiDri).addTarget(info); - } else { - // create consolidated target from the single DisplayResolveInfo - MultiDisplayResolveInfo multiDisplayResolveInfo = + new AsyncTask<Void, Void, List<DisplayResolveInfo>>() { + @Override + protected List<DisplayResolveInfo> doInBackground(Void... voids) { + List<DisplayResolveInfo> allTargets = new ArrayList<>(); + allTargets.addAll(mDisplayList); + allTargets.addAll(mCallerTargets); + if (!mEnableStackedApps) { + return allTargets; + } + // Consolidate multiple targets from same app. + Map<String, DisplayResolveInfo> consolidated = new HashMap<>(); + for (DisplayResolveInfo info : allTargets) { + String packageName = info.getResolvedComponentName().getPackageName(); + DisplayResolveInfo multiDri = consolidated.get(packageName); + if (multiDri == null) { + consolidated.put(packageName, info); + } else if (multiDri instanceof MultiDisplayResolveInfo) { + ((MultiDisplayResolveInfo) multiDri).addTarget(info); + } else { + // create consolidated target from the single DisplayResolveInfo + MultiDisplayResolveInfo multiDisplayResolveInfo = new MultiDisplayResolveInfo(packageName, multiDri); - multiDisplayResolveInfo.addTarget(info); - consolidated.put(packageName, multiDisplayResolveInfo); + multiDisplayResolveInfo.addTarget(info); + consolidated.put(packageName, multiDisplayResolveInfo); + } } + List<DisplayResolveInfo> groupedTargets = new ArrayList<>(); + groupedTargets.addAll(consolidated.values()); + Collections.sort(groupedTargets, new ChooserActivity.AzInfoComparator(mContext)); + return groupedTargets; } - mSortedList.addAll(consolidated.values()); - } else { - mSortedList.addAll(tempList); - } - Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext)); + @Override + protected void onPostExecute(List<DisplayResolveInfo> newList) { + mSortedList = newList; + notifyDataSetChanged(); + } + }.execute(); } @Override diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java index 157e0a74712b..2a7eae626795 100644 --- a/core/java/com/android/internal/app/PlatLogoActivity.java +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * 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. @@ -17,29 +17,32 @@ package com.android.internal.app; import android.animation.ObjectAnimator; -import android.animation.TimeAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActionBar; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; -import android.content.res.ColorStateList; -import android.graphics.Bitmap; -import android.graphics.BitmapShader; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.Matrix; +import android.graphics.LinearGradient; import android.graphics.Paint; -import android.graphics.Path; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.provider.Settings; +import android.util.AttributeSet; import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; +import android.view.animation.PathInterpolator; +import android.widget.FrameLayout; import android.widget.ImageView; import com.android.internal.R; @@ -50,23 +53,12 @@ import org.json.JSONObject; * @hide */ public class PlatLogoActivity extends Activity { - ImageView mZeroView, mOneView; - BackslashDrawable mBackslash; - int mClicks; - - static final Paint sPaint = new Paint(); - static { - sPaint.setStyle(Paint.Style.STROKE); - sPaint.setStrokeWidth(4f); - sPaint.setStrokeCap(Paint.Cap.SQUARE); - } + private static final boolean WRITE_SETTINGS = true; + + BigDialView mDialView; @Override protected void onPause() { - if (mBackslash != null) { - mBackslash.stopAnimating(); - } - mClicks = 0; super.onPause(); } @@ -80,101 +72,33 @@ public class PlatLogoActivity extends Activity { getWindow().setNavigationBarColor(0); getWindow().setStatusBarColor(0); - getActionBar().hide(); - - setContentView(R.layout.platlogo_layout); - - mBackslash = new BackslashDrawable((int) (50 * dp)); - - mOneView = findViewById(R.id.one); - mOneView.setImageDrawable(new OneDrawable()); - mZeroView = findViewById(R.id.zero); - mZeroView.setImageDrawable(new ZeroDrawable()); + final ActionBar ab = getActionBar(); + if (ab != null) ab.hide(); - final ViewGroup root = (ViewGroup) mOneView.getParent(); - root.setClipChildren(false); - root.setBackground(mBackslash); - root.getBackground().setAlpha(0x20); - - View.OnTouchListener tl = new View.OnTouchListener() { - float mOffsetX, mOffsetY; - long mClickTime; - ObjectAnimator mRotAnim; - @Override - public boolean onTouch(View v, MotionEvent event) { - measureTouchPressure(event); - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - v.animate().scaleX(1.1f).scaleY(1.1f); - v.getParent().bringChildToFront(v); - mOffsetX = event.getRawX() - v.getX(); - mOffsetY = event.getRawY() - v.getY(); - long now = System.currentTimeMillis(); - if (now - mClickTime < 350) { - mRotAnim = ObjectAnimator.ofFloat(v, View.ROTATION, - v.getRotation(), v.getRotation() + 3600); - mRotAnim.setDuration(10000); - mRotAnim.start(); - mClickTime = 0; - } else { - mClickTime = now; - } - break; - case MotionEvent.ACTION_MOVE: - v.setX(event.getRawX() - mOffsetX); - v.setY(event.getRawY() - mOffsetY); - v.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE); - break; - case MotionEvent.ACTION_UP: - v.performClick(); - // fall through - case MotionEvent.ACTION_CANCEL: - v.animate().scaleX(1f).scaleY(1f); - if (mRotAnim != null) mRotAnim.cancel(); - testOverlap(); - break; - } - return true; - } - }; - - findViewById(R.id.one).setOnTouchListener(tl); - findViewById(R.id.zero).setOnTouchListener(tl); - findViewById(R.id.text).setOnTouchListener(tl); - } - - private void testOverlap() { - final float width = mZeroView.getWidth(); - final float targetX = mZeroView.getX() + width * .2f; - final float targetY = mZeroView.getY() + width * .3f; - if (Math.hypot(targetX - mOneView.getX(), targetY - mOneView.getY()) < width * .2f - && Math.abs(mOneView.getRotation() % 360 - 315) < 15) { - mOneView.animate().x(mZeroView.getX() + width * .2f); - mOneView.animate().y(mZeroView.getY() + width * .3f); - mOneView.setRotation(mOneView.getRotation() % 360); - mOneView.animate().rotation(315); - mOneView.performHapticFeedback(HapticFeedbackConstants.CONFIRM); - - mBackslash.startAnimating(); - - mClicks++; - if (mClicks >= 7) { - launchNextStage(); - } - } else { - mBackslash.stopAnimating(); + mDialView = new BigDialView(this, null); + if (Settings.System.getLong(getContentResolver(), + "egg_mode" /* Settings.System.EGG_MODE */, 0) == 0) { + mDialView.setUnlockTries(3); } + + final FrameLayout layout = new FrameLayout(this); + layout.setBackgroundColor(0xFFFF0000); + layout.addView(mDialView, FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT); + setContentView(layout); } - private void launchNextStage() { + private void launchNextStage(boolean locked) { final ContentResolver cr = getContentResolver(); if (Settings.System.getLong(cr, "egg_mode" /* Settings.System.EGG_MODE */, 0) == 0) { // For posterity: the moment this user unlocked the easter egg try { - Settings.System.putLong(cr, - "egg_mode", // Settings.System.EGG_MODE, - System.currentTimeMillis()); + if (WRITE_SETTINGS) { + Settings.System.putLong(cr, + "egg_mode", // Settings.System.EGG_MODE, + locked ? 0 : System.currentTimeMillis()); + } } catch (RuntimeException e) { Log.e("com.android.internal.app.PlatLogoActivity", "Can't write settings", e); } @@ -182,12 +106,12 @@ public class PlatLogoActivity extends Activity { try { startActivity(new Intent(Intent.ACTION_MAIN) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK) + | Intent.FLAG_ACTIVITY_CLEAR_TASK) .addCategory("com.android.internal.category.PLATLOGO")); } catch (ActivityNotFoundException ex) { Log.e("com.android.internal.app.PlatLogoActivity", "No more eggs."); } - finish(); + //finish(); // no longer finish upon unlock; it's fun to frob the dial } static final String TOUCH_STATS = "touch.stats"; @@ -223,7 +147,10 @@ public class PlatLogoActivity extends Activity { if (mPressureMax >= 0) { touchData.put("min", mPressureMin); touchData.put("max", mPressureMax); - Settings.System.putString(getContentResolver(), TOUCH_STATS, touchData.toString()); + if (WRITE_SETTINGS) { + Settings.System.putString(getContentResolver(), TOUCH_STATS, + touchData.toString()); + } } } catch (Exception e) { Log.e("com.android.internal.app.PlatLogoActivity", "Can't write touch settings", e); @@ -242,149 +169,272 @@ public class PlatLogoActivity extends Activity { super.onStop(); } - static class ZeroDrawable extends Drawable { - int mTintColor; + class BigDialView extends ImageView { + private static final int COLOR_GREEN = 0xff3ddc84; + private static final int COLOR_BLUE = 0xff4285f4; + private static final int COLOR_NAVY = 0xff073042; + private static final int COLOR_ORANGE = 0xfff86734; + private static final int COLOR_CHARTREUSE = 0xffeff7cf; + private static final int COLOR_LIGHTBLUE = 0xffd7effe; - @Override - public void draw(Canvas canvas) { - sPaint.setColor(mTintColor | 0xFF000000); + private static final int STEPS = 11; + private static final float VALUE_CHANGE_MAX = 1f / STEPS; - canvas.save(); - canvas.scale(canvas.getWidth() / 24f, canvas.getHeight() / 24f); + private BigDialDrawable mDialDrawable; + private boolean mWasLocked; - canvas.drawCircle(12f, 12f, 10f, sPaint); - canvas.restore(); + BigDialView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); } - @Override - public void setAlpha(int alpha) { } - - @Override - public void setColorFilter(ColorFilter colorFilter) { } - - @Override - public void setTintList(ColorStateList tint) { - mTintColor = tint.getDefaultColor(); + BigDialView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); } - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; + BigDialView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); } - } - static class OneDrawable extends Drawable { - int mTintColor; + private void init() { + mDialDrawable = new BigDialDrawable(); + setImageDrawable(mDialDrawable); + } @Override - public void draw(Canvas canvas) { - sPaint.setColor(mTintColor | 0xFF000000); - - canvas.save(); - canvas.scale(canvas.getWidth() / 24f, canvas.getHeight() / 24f); - - final Path p = new Path(); - p.moveTo(12f, 21.83f); - p.rLineTo(0f, -19.67f); - p.rLineTo(-5f, 0f); - canvas.drawPath(p, sPaint); - canvas.restore(); + public void onDraw(Canvas c) { + super.onDraw(c); } - @Override - public void setAlpha(int alpha) { } + double toPositiveDegrees(double rad) { + return (Math.toDegrees(rad) + 360 - 90) % 360; + } @Override - public void setColorFilter(ColorFilter colorFilter) { } + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mWasLocked = mDialDrawable.isLocked(); + // pass through + case MotionEvent.ACTION_MOVE: + float x = ev.getX(); + float y = ev.getY(); + float cx = (getLeft() + getRight()) / 2f; + float cy = (getTop() + getBottom()) / 2f; + float angle = (float) toPositiveDegrees(Math.atan2(x - cx, y - cy)); + final int oldLevel = mDialDrawable.getUserLevel(); + mDialDrawable.touchAngle(angle); + final int newLevel = mDialDrawable.getUserLevel(); + if (oldLevel != newLevel) { + performHapticFeedback(newLevel == STEPS + ? HapticFeedbackConstants.CONFIRM + : HapticFeedbackConstants.CLOCK_TICK); + } + return true; + case MotionEvent.ACTION_UP: + if (mWasLocked && !mDialDrawable.isLocked()) { + launchNextStage(false); + } + return true; + } + return false; + } @Override - public void setTintList(ColorStateList tint) { - mTintColor = tint.getDefaultColor(); + public boolean performClick() { + if (mDialDrawable.getUserLevel() < STEPS - 1) { + mDialDrawable.setUserLevel(mDialDrawable.getUserLevel() + 1); + performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); + } + return true; } - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; + void setUnlockTries(int tries) { + mDialDrawable.setUnlockTries(tries); } - } - private static class BackslashDrawable extends Drawable implements TimeAnimator.TimeListener { - Bitmap mTile; - Paint mPaint = new Paint(); - BitmapShader mShader; - TimeAnimator mAnimator = new TimeAnimator(); - Matrix mMatrix = new Matrix(); + private class BigDialDrawable extends Drawable { + public final int STEPS = 10; + private int mUnlockTries = 0; + final Paint mPaint = new Paint(); + final Drawable mEleven; + private boolean mNightMode; + private float mValue = 0f; + float mElevenAnim = 0f; + ObjectAnimator mElevenShowAnimator = ObjectAnimator.ofFloat(this, "elevenAnim", 0f, + 1f).setDuration(300); + ObjectAnimator mElevenHideAnimator = ObjectAnimator.ofFloat(this, "elevenAnim", 1f, + 0f).setDuration(500); + + BigDialDrawable() { + mNightMode = getContext().getResources().getConfiguration().isNightModeActive(); + mEleven = getContext().getDrawable(R.drawable.ic_number11); + mElevenShowAnimator.setInterpolator(new PathInterpolator(0.4f, 0f, 0.2f, 1f)); + mElevenHideAnimator.setInterpolator(new PathInterpolator(0.8f, 0.2f, 0.6f, 1f)); + } - public void draw(Canvas canvas) { - canvas.drawPaint(mPaint); - } + public void setUnlockTries(int count) { + if (mUnlockTries != count) { + mUnlockTries = count; + setValue(getValue()); + invalidateSelf(); + } + } - BackslashDrawable(int width) { - mTile = Bitmap.createBitmap(width, width, Bitmap.Config.ALPHA_8); - mAnimator.setTimeListener(this); - - final Canvas tileCanvas = new Canvas(mTile); - final float w = tileCanvas.getWidth(); - final float h = tileCanvas.getHeight(); - - final Path path = new Path(); - path.moveTo(0, 0); - path.lineTo(w / 2, 0); - path.lineTo(w, h / 2); - path.lineTo(w, h); - path.close(); - - path.moveTo(0, h / 2); - path.lineTo(w / 2, h); - path.lineTo(0, h); - path.close(); - - final Paint slashPaint = new Paint(); - slashPaint.setAntiAlias(true); - slashPaint.setStyle(Paint.Style.FILL); - slashPaint.setColor(0xFF000000); - tileCanvas.drawPath(path, slashPaint); - - //mPaint.setColor(0xFF0000FF); - mShader = new BitmapShader(mTile, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); - mPaint.setShader(mShader); - } + boolean isLocked() { + return mUnlockTries > 0; + } - public void startAnimating() { - if (!mAnimator.isStarted()) { - mAnimator.start(); + public void setValue(float v) { + // until the dial is "unlocked", you can't turn it all the way to 11 + final float max = isLocked() ? 1f - 1f / STEPS : 1f; + mValue = v < 0f ? 0f : v > max ? max : v; + invalidateSelf(); } - } - public void stopAnimating() { - if (mAnimator.isStarted()) { - mAnimator.cancel(); + public float getValue() { + return mValue; } - } - @Override - public void setAlpha(int alpha) { - mPaint.setAlpha(alpha); - } + public int getUserLevel() { + return Math.round(getValue() * STEPS - 0.25f); + } - @Override - public void setColorFilter(ColorFilter colorFilter) { - mPaint.setColorFilter(colorFilter); - } + public void setUserLevel(int i) { + setValue(getValue() + ((float) i) / STEPS); + } - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } + public float getElevenAnim() { + return mElevenAnim; + } - @Override - public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { - if (mShader != null) { - mMatrix.postTranslate(deltaTime / 4f, 0); - mShader.setLocalMatrix(mMatrix); - invalidateSelf(); + public void setElevenAnim(float f) { + if (mElevenAnim != f) { + mElevenAnim = f; + invalidateSelf(); + } + } + + @Override + public void draw(@NonNull Canvas canvas) { + final Rect bounds = getBounds(); + final int w = bounds.width(); + final int h = bounds.height(); + final float w2 = w / 2f; + final float h2 = h / 2f; + final float radius = w / 4f; + + canvas.drawColor(mNightMode ? COLOR_NAVY : COLOR_LIGHTBLUE); + + canvas.save(); + canvas.rotate(45, w2, h2); + canvas.clipRect(w2, h2 - radius, Math.min(w, h), h2 + radius); + final int gradientColor = mNightMode ? 0x60000020 : (0x10FFFFFF & COLOR_NAVY); + mPaint.setShader( + new LinearGradient(w2, h2, Math.min(w, h), h2, gradientColor, + 0x00FFFFFF & gradientColor, Shader.TileMode.CLAMP)); + mPaint.setColor(Color.BLACK); + canvas.drawPaint(mPaint); + mPaint.setShader(null); + canvas.restore(); + + mPaint.setStyle(Paint.Style.FILL); + mPaint.setColor(COLOR_GREEN); + + canvas.drawCircle(w2, h2, radius, mPaint); + + mPaint.setColor(mNightMode ? COLOR_LIGHTBLUE : COLOR_NAVY); + final float cx = w * 0.85f; + for (int i = 0; i < STEPS; i++) { + final float f = (float) i / STEPS; + canvas.save(); + final float angle = valueToAngle(f); + canvas.rotate(-angle, w2, h2); + canvas.drawCircle(cx, h2, (i <= getUserLevel()) ? 20 : 5, mPaint); + canvas.restore(); + } + + if (mElevenAnim > 0f) { + final int color = COLOR_ORANGE; + final int size2 = (int) ((0.5 + 0.5f * mElevenAnim) * w / 14); + final float cx11 = cx + size2 / 4f; + mEleven.setBounds((int) cx11 - size2, (int) h2 - size2, + (int) cx11 + size2, (int) h2 + size2); + final int alpha = 0xFFFFFF | ((int) clamp(0xFF * 2 * mElevenAnim, 0, 0xFF) + << 24); + mEleven.setTint(alpha & color); + mEleven.draw(canvas); + } + + // don't want to use the rounded value here since the quantization will be visible + final float angle = valueToAngle(mValue); + + // it's easier to draw at far-right and rotate backwards + canvas.rotate(-angle, w2, h2); + mPaint.setColor(Color.WHITE); + final float dimple = w2 / 12f; + canvas.drawCircle(w - radius - dimple * 2, h2, dimple, mPaint); + } + + float clamp(float x, float a, float b) { + return x < a ? a : x > b ? b : x; + } + + float angleToValue(float a) { + return 1f - clamp(a / (360 - 45), 0f, 1f); + } + + // rotation: min is at 4:30, max is at 3:00 + float valueToAngle(float v) { + return (1f - v) * (360 - 45); + } + + public void touchAngle(float a) { + final int oldUserLevel = getUserLevel(); + final float newValue = angleToValue(a); + // this is how we prevent the knob from snapping from max back to min, or from + // jumping around wherever the user presses. The new value must be pretty close + // to the + // previous one. + if (Math.abs(newValue - getValue()) < VALUE_CHANGE_MAX) { + setValue(newValue); + + if (isLocked() && oldUserLevel != STEPS - 1 && getUserLevel() == STEPS - 1) { + mUnlockTries--; + } + + if (!isLocked()) { + if (getUserLevel() == STEPS && mElevenAnim != 1f + && !mElevenShowAnimator.isRunning()) { + mElevenHideAnimator.cancel(); + mElevenShowAnimator.start(); + } else if (getUserLevel() != STEPS && mElevenAnim == 1f + && !mElevenHideAnimator.isRunning()) { + mElevenShowAnimator.cancel(); + mElevenHideAnimator.start(); + } + } + } + } + + @Override + public void setAlpha(int i) { + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; } } } } + + diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index daacd459a1a7..86c13a0581c2 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1938,7 +1938,7 @@ public class ResolverActivity extends Activity implements ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); activeListAdapter.notifyDataSetChanged(); - if (activeListAdapter.getCount() == 0) { + if (activeListAdapter.getCount() == 0 && !inactiveListAdapterHasItems()) { // We no longer have any items... just finish the activity. finish(); } @@ -1948,6 +1948,13 @@ public class ResolverActivity extends Activity implements } } + private boolean inactiveListAdapterHasItems() { + if (!shouldShowTabs()) { + return false; + } + return mMultiProfilePagerAdapter.getInactiveListAdapter().getCount() > 0; + } + private BroadcastReceiver createWorkProfileStateReceiver() { return new BroadcastReceiver() { @Override diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index d63ebda5117e..fc8cafdc526c 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -427,6 +427,8 @@ public class ResolverListAdapter extends BaseAdapter { && dri.getResolveInfo().targetUserId == UserHandle.USER_CURRENT) { if (shouldAddResolveInfo(dri)) { mDisplayList.add(dri); + Log.i(TAG, "Add DisplayResolveInfo component: " + dri.getResolvedComponentName() + + ", intent component: " + dri.getResolvedIntent().getComponent()); } } } diff --git a/core/java/com/android/internal/app/ResolverViewPager.java b/core/java/com/android/internal/app/ResolverViewPager.java index 9cdfc2f5c763..478cc18f13ee 100644 --- a/core/java/com/android/internal/app/ResolverViewPager.java +++ b/core/java/com/android/internal/app/ResolverViewPager.java @@ -74,12 +74,16 @@ public class ResolverViewPager extends ViewPager { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + /** + * Sets whether swiping sideways should happen. + * <p>Note that swiping is always disabled for RTL layouts (b/159110029 for context). + */ void setSwipingEnabled(boolean swipingEnabled) { mSwipingEnabled = swipingEnabled; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - return mSwipingEnabled && super.onInterceptTouchEvent(ev); + return !isLayoutRtl() && mSwipingEnabled && super.onInterceptTouchEvent(ev); } } diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 7455ad009873..11e55b852516 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -2232,24 +2232,43 @@ public final class ProcessStats implements Parcelable { } /** Similar to {@code #dumpDebug}, but with a reduced/aggregated subset of states. */ - public void dumpAggregatedProtoForStatsd(ProtoOutputStream proto) { - dumpProtoPreamble(proto); + public void dumpAggregatedProtoForStatsd(ProtoOutputStream[] protoStreams, + long maxRawShardSizeBytes) { + int shardIndex = 0; + dumpProtoPreamble(protoStreams[shardIndex]); + final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); final ProcessMap<ArraySet<PackageState>> procToPkgMap = new ProcessMap<>(); final SparseArray<ArraySet<String>> uidToPkgMap = new SparseArray<>(); collectProcessPackageMaps(null, false, procToPkgMap, uidToPkgMap); + for (int ip = 0; ip < procMap.size(); ip++) { final String procName = procMap.keyAt(ip); + if (protoStreams[shardIndex].getRawSize() > maxRawShardSizeBytes) { + shardIndex++; + if (shardIndex >= protoStreams.length) { + // We have run out of space; we'll drop the rest of the processes. + Slog.d(TAG, String.format("Dropping process indices from %d to %d from " + + "statsd proto (too large)", ip, procMap.size())); + break; + } + dumpProtoPreamble(protoStreams[shardIndex]); + } + final SparseArray<ProcessState> uids = procMap.valueAt(ip); for (int iu = 0; iu < uids.size(); iu++) { final int uid = uids.keyAt(iu); final ProcessState procState = uids.valueAt(iu); - procState.dumpAggregatedProtoForStatsd(proto, + procState.dumpAggregatedProtoForStatsd(protoStreams[shardIndex], ProcessStatsSectionProto.PROCESS_STATS, procName, uid, mTimePeriodEndRealtime, procToPkgMap, uidToPkgMap); } } + + for (int i = 0; i <= shardIndex; i++) { + protoStreams[i].flush(); + } } private void dumpProtoPreamble(ProtoOutputStream proto) { @@ -2403,10 +2422,11 @@ public final class ProcessStats implements Parcelable { final SourceKey key = assocVals.keyAt(i); final long[] vals = assocVals.valueAt(i); final long token = proto.start(fieldId); + final int idx = uidToPkgMap.indexOfKey(key.mUid); ProcessState.writeCompressedProcessName(proto, ProcessStatsAssociationProto.ASSOC_PROCESS_NAME, key.mProcess, key.mPackage, - uidToPkgMap.get(key.mUid).size() > 1); + idx >= 0 && uidToPkgMap.valueAt(idx).size() > 1); proto.write(ProcessStatsAssociationProto.ASSOC_UID, key.mUid); proto.write(ProcessStatsAssociationProto.TOTAL_COUNT, (int) vals[1]); proto.write(ProcessStatsAssociationProto.TOTAL_DURATION_SECS, diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java index e9d7d05fe68d..167d128a76f8 100644 --- a/core/java/com/android/internal/infra/ServiceConnector.java +++ b/core/java/com/android/internal/infra/ServiceConnector.java @@ -709,7 +709,7 @@ public interface ServiceConnector<I extends IInterface> { if (DEBUG) { return mDebugName; } - return mDelegate.toString() + " wrapped into " + super.toString(); + return mDelegate + " wrapped into " + super.toString(); } @Override diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java index ab0cc3093b6d..2393036b5a38 100644 --- a/core/java/com/android/internal/os/FuseAppLoop.java +++ b/core/java/com/android/internal/os/FuseAppLoop.java @@ -210,7 +210,7 @@ public class FuseAppLoop implements Handler.Callback { if (mInstance != 0) { native_replySimple(mInstance, unique, FUSE_OK); } - mBytesMap.stopUsing(entry.getThreadId()); + mBytesMap.stopUsing(inode); recycleLocked(args); } break; @@ -270,7 +270,7 @@ public class FuseAppLoop implements Handler.Callback { if (mInstance != 0) { native_replyOpen(mInstance, unique, /* fh */ inode); entry.opened = true; - return mBytesMap.startUsing(entry.getThreadId()); + return mBytesMap.startUsing(inode); } } catch (ErrnoException error) { replySimpleLocked(unique, getError(error)); @@ -354,27 +354,27 @@ public class FuseAppLoop implements Handler.Callback { } /** - * Map between Thread ID and byte buffer. + * Map between inode and byte buffer. */ private static class BytesMap { final Map<Long, BytesMapEntry> mEntries = new HashMap<>(); - byte[] startUsing(long threadId) { - BytesMapEntry entry = mEntries.get(threadId); + byte[] startUsing(long inode) { + BytesMapEntry entry = mEntries.get(inode); if (entry == null) { entry = new BytesMapEntry(); - mEntries.put(threadId, entry); + mEntries.put(inode, entry); } entry.counter++; return entry.bytes; } - void stopUsing(long threadId) { - final BytesMapEntry entry = mEntries.get(threadId); + void stopUsing(long inode) { + final BytesMapEntry entry = mEntries.get(inode); Objects.requireNonNull(entry); entry.counter--; if (entry.counter <= 0) { - mEntries.remove(threadId); + mEntries.remove(inode); } } diff --git a/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java b/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java index c11b939098c4..69ca9922e81f 100644 --- a/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java +++ b/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java @@ -59,7 +59,7 @@ import java.util.Objects; * #getProcessCpuUsageDiffed()} result. * * <p>Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of - * WestWorld, because the thresholding should be done after diffing, not before. This is because of + * statsd, because the thresholding should be done after diffing, not before. This is because of * two issues with thresholding before diffing: * * <ul> diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 505a05eb9c23..a7d9827855a2 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo; import android.net.Credentials; import android.net.LocalServerSocket; import android.net.LocalSocket; +import android.net.NetworkUtils; import android.os.FactoryTest; import android.os.IVold; import android.os.Process; @@ -286,6 +287,13 @@ public final class Zygote { private Zygote() {} + private static boolean containsInetGid(int[] gids) { + for (int i = 0; i < gids.length; i++) { + if (gids[i] == android.os.Process.INET_GID) return true; + } + return false; + } + /** * Forks a new VM instance. The current VM must have been started * with the -Xzygote flag. <b>NOTE: new instance keeps all @@ -341,6 +349,11 @@ public final class Zygote { if (pid == 0) { // Note that this event ends at the end of handleChildProc, Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); + + // If no GIDs were specified, don't make any permissions changes based on groups. + if (gids != null && gids.length > 0) { + NetworkUtils.setAllowNetworkingForProcess(containsInetGid(gids)); + } } // Set the Java Language thread priority to the default value for new apps. diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java index 7bfed91c42b9..6fe1d8140371 100644 --- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java +++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java @@ -16,6 +16,7 @@ package com.android.internal.policy; +import android.graphics.Insets; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; @@ -69,16 +70,14 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame private ColorDrawable mNavigationBarColor; private boolean mOldFullscreen; private boolean mFullscreen; - private final Rect mOldSystemInsets = new Rect(); - private final Rect mOldStableInsets = new Rect(); - private final Rect mSystemInsets = new Rect(); - private final Rect mStableInsets = new Rect(); + private final Rect mOldSystemBarInsets = new Rect(); + private final Rect mSystemBarInsets = new Rect(); private final Rect mTmpRect = new Rect(); public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor, - boolean fullscreen, Rect systemInsets, Rect stableInsets) { + boolean fullscreen, Insets systemBarInsets) { setName("ResizeFrame"); mRenderer = renderer; @@ -95,10 +94,8 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame mTargetRect.set(initialBounds); mFullscreen = fullscreen; mOldFullscreen = fullscreen; - mSystemInsets.set(systemInsets); - mStableInsets.set(stableInsets); - mOldSystemInsets.set(systemInsets); - mOldStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets.toRect()); + mOldSystemBarInsets.set(systemBarInsets.toRect()); // Kick off our draw thread. start(); @@ -154,16 +151,13 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame * * @param newTargetBounds The new target bounds. * @param fullscreen Whether the window is currently drawing in fullscreen. - * @param systemInsets The current visible system insets for the window. - * @param stableInsets The stable insets for the window. + * @param systemBarInsets The current visible system insets for the window. */ - public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemInsets, - Rect stableInsets) { + public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemBarInsets) { synchronized (this) { mFullscreen = fullscreen; mTargetRect.set(newTargetBounds); - mSystemInsets.set(systemInsets); - mStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets); // Notify of a bounds change. pingRenderLocked(false /* drawImmediate */); } @@ -247,14 +241,12 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame mNewTargetRect.set(mTargetRect); if (!mNewTargetRect.equals(mOldTargetRect) || mOldFullscreen != mFullscreen - || !mStableInsets.equals(mOldStableInsets) - || !mSystemInsets.equals(mOldSystemInsets) + || !mSystemBarInsets.equals(mOldSystemBarInsets) || mReportNextDraw) { mOldFullscreen = mFullscreen; mOldTargetRect.set(mNewTargetRect); - mOldSystemInsets.set(mSystemInsets); - mOldStableInsets.set(mStableInsets); - redrawLocked(mNewTargetRect, mFullscreen, mSystemInsets, mStableInsets); + mOldSystemBarInsets.set(mSystemBarInsets); + redrawLocked(mNewTargetRect, mFullscreen); } } @@ -304,11 +296,8 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame * * @param newBounds The window bounds which needs to be drawn. * @param fullscreen Whether the window is currently drawing in fullscreen. - * @param systemInsets The current visible system insets for the window. - * @param stableInsets The stable insets for the window. */ - private void redrawLocked(Rect newBounds, boolean fullscreen, Rect systemInsets, - Rect stableInsets) { + private void redrawLocked(Rect newBounds, boolean fullscreen) { // While a configuration change is taking place the view hierarchy might become // inaccessible. For that case we remember the previous metrics to avoid flashes. @@ -355,7 +344,7 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame } mFrameAndBackdropNode.endRecording(); - drawColorViews(left, top, width, height, fullscreen, systemInsets, stableInsets); + drawColorViews(left, top, width, height, fullscreen); // We need to render the node explicitly mRenderer.drawRenderNode(mFrameAndBackdropNode); @@ -363,14 +352,13 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame reportDrawIfNeeded(); } - private void drawColorViews(int left, int top, int width, int height, - boolean fullscreen, Rect systemInsets, Rect stableInsets) { + private void drawColorViews(int left, int top, int width, int height, boolean fullscreen) { if (mSystemBarBackgroundNode == null) { return; } RecordingCanvas canvas = mSystemBarBackgroundNode.beginRecording(width, height); mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height); - final int topInset = DecorView.getColorViewTopInset(mStableInsets.top, mSystemInsets.top); + final int topInset = mSystemBarInsets.top; if (mStatusBarColor != null) { mStatusBarColor.setBounds(0, 0, left + width, topInset); mStatusBarColor.draw(canvas); @@ -380,7 +368,7 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame // don't want the navigation bar background be moving around when resizing in docked mode. // However, we need it for the transitions into/out of docked mode. if (mNavigationBarColor != null && fullscreen) { - DecorView.getNavigationBarRect(width, height, stableInsets, systemInsets, mTmpRect, 1f); + DecorView.getNavigationBarRect(width, height, mSystemBarInsets, mTmpRect, 1f); mNavigationBarColor.setBounds(mTmpRect); mNavigationBarColor.draw(canvas); } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index c6135f2c81d3..b12c5e9ba5b0 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1050,22 +1050,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return false; } - public static int getColorViewTopInset(int stableTop, int systemTop) { - return Math.min(stableTop, systemTop); - } - - public static int getColorViewBottomInset(int stableBottom, int systemBottom) { - return Math.min(stableBottom, systemBottom); - } - - public static int getColorViewRightInset(int stableRight, int systemRight) { - return Math.min(stableRight, systemRight); - } - - public static int getColorViewLeftInset(int stableLeft, int systemLeft) { - return Math.min(stableLeft, systemLeft); - } - public static boolean isNavBarToRightEdge(int bottomInset, int rightInset) { return bottomInset == 0 && rightInset > 0; } @@ -1079,14 +1063,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind : isNavBarToLeftEdge(bottomInset, leftInset) ? leftInset : bottomInset; } - public static void getNavigationBarRect(int canvasWidth, int canvasHeight, Rect stableInsets, - Rect contentInsets, Rect outRect, float scale) { - final int bottomInset = - (int) (getColorViewBottomInset(stableInsets.bottom, contentInsets.bottom) * scale); - final int leftInset = - (int) (getColorViewLeftInset(stableInsets.left, contentInsets.left) * scale); - final int rightInset = - (int) (getColorViewLeftInset(stableInsets.right, contentInsets.right) * scale); + public static void getNavigationBarRect(int canvasWidth, int canvasHeight, Rect systemBarInsets, + Rect outRect, float scale) { + final int bottomInset = (int) (systemBarInsets.bottom * scale); + final int leftInset = (int) (systemBarInsets.left * scale); + final int rightInset = (int) (systemBarInsets.right * scale); final int size = getNavBarSize(bottomInset, rightInset, leftInset); if (isNavBarToRightEdge(bottomInset, rightInset)) { outRect.set(canvasWidth - size, 0, canvasWidth, canvasHeight); @@ -1113,31 +1094,30 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mLastWindowFlags = attrs.flags; if (insets != null) { - mLastTopInset = getColorViewTopInset(insets.getStableInsetTop(), - insets.getSystemWindowInsetTop()); - mLastBottomInset = getColorViewBottomInset(insets.getStableInsetBottom(), - insets.getSystemWindowInsetBottom()); - mLastRightInset = getColorViewRightInset(insets.getStableInsetRight(), - insets.getSystemWindowInsetRight()); - mLastLeftInset = getColorViewRightInset(insets.getStableInsetLeft(), - insets.getSystemWindowInsetLeft()); + final Insets systemBarInsets = insets.getInsets(WindowInsets.Type.systemBars()); + final Insets stableBarInsets = insets.getInsetsIgnoringVisibility( + WindowInsets.Type.systemBars()); + mLastTopInset = systemBarInsets.top; + mLastBottomInset = systemBarInsets.bottom; + mLastRightInset = systemBarInsets.right; + mLastLeftInset = systemBarInsets.left; // Don't animate if the presence of stable insets has changed, because that // indicates that the window was either just added and received them for the // first time, or the window size or position has changed. - boolean hasTopStableInset = insets.getStableInsetTop() != 0; + boolean hasTopStableInset = stableBarInsets.top != 0; disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset); mLastHasTopStableInset = hasTopStableInset; - boolean hasBottomStableInset = insets.getStableInsetBottom() != 0; + boolean hasBottomStableInset = stableBarInsets.bottom != 0; disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset); mLastHasBottomStableInset = hasBottomStableInset; - boolean hasRightStableInset = insets.getStableInsetRight() != 0; + boolean hasRightStableInset = stableBarInsets.right != 0; disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset); mLastHasRightStableInset = hasRightStableInset; - boolean hasLeftStableInset = insets.getStableInsetLeft() != 0; + boolean hasLeftStableInset = stableBarInsets.left != 0; disallowAnimate |= (hasLeftStableInset != mLastHasLeftStableInset); mLastHasLeftStableInset = hasLeftStableInset; @@ -2296,7 +2276,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets) { if (mBackdropFrameRenderer != null) { - mBackdropFrameRenderer.setTargetRect(newBounds, fullscreen, systemInsets, stableInsets); + mBackdropFrameRenderer.setTargetRect(newBounds, fullscreen, systemInsets); } } @@ -2314,11 +2294,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final ThreadedRenderer renderer = getThreadedRenderer(); if (renderer != null) { loadBackgroundDrawablesIfNeeded(); + WindowInsets rootInsets = getRootWindowInsets(); mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer, initialBounds, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), - getCurrentColor(mNavigationColorViewState), fullscreen, systemInsets, - stableInsets); + getCurrentColor(mNavigationColorViewState), fullscreen, + rootInsets.getInsets(WindowInsets.Type.systemBars())); // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time. // If we want to get the shadow shown while resizing, we would need to elevate a new diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index d0052ab01331..9bf05135c4c5 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -316,7 +316,7 @@ public class ScreenshotHelper { }; msg.replyTo = new Messenger(h); - if (mScreenshotConnection == null) { + if (mScreenshotConnection == null || mScreenshotService == null) { final ComponentName serviceComponent = ComponentName.unflattenFromString( mContext.getResources().getString( com.android.internal.R.string.config_screenshotServiceComponent)); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 3f03f2a3e754..d22f94213338 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -71,4 +71,6 @@ interface IInputMethodManager { void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, in float[] matrixValues); + + void removeImeSurface(); } diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 075159671b17..5d4407bf8370 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -72,6 +72,7 @@ import com.android.internal.util.ContrastColorUtil; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.function.Consumer; import java.util.regex.Pattern; @@ -232,13 +233,20 @@ public class ConversationLayout extends FrameLayout oldVisibility = mImportanceRingView.getVisibility(); wasGone = oldVisibility == GONE; visibility = !mImportantConversation ? GONE : visibility; - isGone = visibility == GONE; - if (wasGone != isGone) { + boolean isRingGone = visibility == GONE; + if (wasGone != isRingGone) { // Keep the badge visibility in sync with the icon. This is necessary in cases // Where the icon is being hidden externally like in group children. mImportanceRingView.animate().cancel(); mImportanceRingView.setVisibility(visibility); } + + oldVisibility = mConversationIconBadge.getVisibility(); + wasGone = oldVisibility == GONE; + if (wasGone != isGone) { + mConversationIconBadge.animate().cancel(); + mConversationIconBadge.setVisibility(visibility); + } }); // When the small icon is gone, hide the rest of the badge mIcon.setOnForceHiddenChangedListener((forceHidden) -> { @@ -784,8 +792,8 @@ public class ConversationLayout extends FrameLayout } @RemotableViewMethod - public void setShortcutIcon(Icon conversationIcon) { - mConversationIcon = conversationIcon; + public void setShortcutIcon(Icon shortcutIcon) { + mShortcutIcon = shortcutIcon; } /** @@ -795,7 +803,8 @@ public class ConversationLayout extends FrameLayout */ @RemotableViewMethod public void setConversationTitle(CharSequence conversationTitle) { - mConversationTitle = conversationTitle; + // Remove formatting from the title. + mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null; } public CharSequence getConversationTitle() { @@ -1052,6 +1061,9 @@ public class ConversationLayout extends FrameLayout groups.add(currentGroup); if (sender == null) { sender = mUser; + } else { + // Remove all formatting from the sender name + sender = sender.toBuilder().setName(Objects.toString(sender.getName())).build(); } senders.add(sender); currentSenderKey = key; diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 03a7b3da6251..93690cdfc811 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1430,6 +1430,32 @@ public class LockPatternUtils { == StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; } + private static class WrappedCallback extends ICheckCredentialProgressCallback.Stub { + + private Handler mHandler; + private CheckCredentialProgressCallback mCallback; + + WrappedCallback(Handler handler, CheckCredentialProgressCallback callback) { + mHandler = handler; + mCallback = callback; + } + + @Override + public void onCredentialVerified() throws RemoteException { + if (mHandler == null) { + Log.e(TAG, "Handler is null during callback"); + } + // Kill reference immediately to allow early GC at client side independent of + // when system_server decides to lose its reference to the + // ICheckCredentialProgressCallback binder object. + mHandler.post(() -> { + mCallback.onEarlyMatched(); + mCallback = null; + }); + mHandler = null; + } + } + private ICheckCredentialProgressCallback wrapCallback( final CheckCredentialProgressCallback callback) { if (callback == null) { @@ -1439,13 +1465,7 @@ public class LockPatternUtils { throw new IllegalStateException("Must construct LockPatternUtils on a looper thread" + " to use progress callbacks."); } - return new ICheckCredentialProgressCallback.Stub() { - - @Override - public void onCredentialVerified() throws RemoteException { - mHandler.post(callback::onEarlyMatched); - } - }; + return new WrappedCallback(mHandler, callback); } } diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 53272f7eebf9..f312d1d4f25d 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -42,6 +42,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RemoteViews; +import android.widget.TextView; import com.android.internal.R; @@ -109,6 +110,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou private ViewGroup mMessagingIconContainer; private int mConversationContentStart; private int mNonConversationMarginEnd; + private int mNotificationTextMarginTop; public MessagingGroup(@NonNull Context context) { super(context); @@ -148,6 +150,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou R.dimen.conversation_content_start); mNonConversationMarginEnd = getResources().getDimensionPixelSize( R.dimen.messaging_layout_margin_end); + mNotificationTextMarginTop = getResources().getDimensionPixelSize( + R.dimen.notification_text_margin_top); } public void updateClipRect() { @@ -612,7 +616,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return 0; } - public View getSenderView() { + public TextView getSenderView() { return mSenderView; } @@ -664,10 +668,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public void setSingleLine(boolean singleLine) { if (singleLine != mSingleLine) { mSingleLine = singleLine; + MarginLayoutParams p = (MarginLayoutParams) mMessageContainer.getLayoutParams(); + p.topMargin = singleLine ? 0 : mNotificationTextMarginTop; + mMessageContainer.setLayoutParams(p); mContentContainer.setOrientation( singleLine ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); MarginLayoutParams layoutParams = (MarginLayoutParams) mSenderView.getLayoutParams(); layoutParams.setMarginEnd(singleLine ? mSenderTextPaddingSingleLine : 0); + mSenderView.setSingleLine(singleLine); updateMaxDisplayedLines(); updateClipRect(); updateSenderVisibility(); diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index ac04862d9a7d..4ee647a42116 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -300,6 +300,27 @@ public class MessagingLinearLayout extends ViewGroup { return mMessagingLayout; } + @Override + public int getBaseline() { + // When placed in a horizontal linear layout (as is the case in a single-line MessageGroup), + // align with the last visible child (which is the one that will be displayed in the single- + // line group. + int childCount = getChildCount(); + for (int i = childCount - 1; i >= 0; i--) { + final View child = getChildAt(i); + if (isGone(child)) { + continue; + } + final int childBaseline = child.getBaseline(); + if (childBaseline == -1) { + return -1; + } + MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + return lp.topMargin + childBaseline; + } + return super.getBaseline(); + } + public interface MessagingChild { int MEASURED_NORMAL = 0; int MEASURED_SHORTENED = 1; diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 21ca948fa89c..d9ca9c2f87f5 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -1197,6 +1197,10 @@ public class SystemConfig { addFeature(PackageManager.FEATURE_APP_ENUMERATION, 0); } + if (Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.Q) { + addFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0); + } + for (String featureName : mUnavailableFeatures) { removeFeature(featureName); } diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h index a3c455bfc111..b1b39f3e36ff 100644 --- a/core/jni/android_media_AudioFormat.h +++ b/core/jni/android_media_AudioFormat.h @@ -47,6 +47,7 @@ #define CHANNEL_INVALID 0 #define CHANNEL_OUT_DEFAULT 1 +#define CHANNEL_IN_DEFAULT 1 static inline audio_format_t audioFormatToNative(int audioFormat) { @@ -196,12 +197,22 @@ static inline int outChannelMaskFromNative(audio_channel_mask_t nativeMask) static inline audio_channel_mask_t inChannelMaskToNative(int channelMask) { - return (audio_channel_mask_t)channelMask; + switch (channelMask) { + case CHANNEL_IN_DEFAULT: + return AUDIO_CHANNEL_NONE; + default: + return (audio_channel_mask_t)channelMask; + } } static inline int inChannelMaskFromNative(audio_channel_mask_t nativeMask) { - return (int)nativeMask; + switch (nativeMask) { + case AUDIO_CHANNEL_NONE: + return CHANNEL_IN_DEFAULT; + default: + return (int)nativeMask; + } } #endif // ANDROID_MEDIA_AUDIOFORMAT_H diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 03b9793ccba8..d4805acb06d0 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -226,6 +226,11 @@ static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) { class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass); } +static void android_net_utils_setAllowNetworkingForProcess(JNIEnv *env, jobject thiz, + jboolean hasConnectivity) { + setAllowNetworkingForProcess(hasConnectivity == JNI_TRUE); +} + static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { if (javaFd == NULL) { jniThrowNullPointerException(env, NULL); @@ -266,6 +271,7 @@ static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, j /* * JNI registration. */ +// clang-format off static const JNINativeMethod gNetworkUtilMethods[] = { /* name, signature, funcPtr */ { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork }, @@ -282,7 +288,9 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult }, { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel }, { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork }, + { "setAllowNetworkingForProcess", "(Z)V", (void *)android_net_utils_setAllowNetworkingForProcess }, }; +// clang-format on int register_android_net_NetworkUtils(JNIEnv* env) { diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index e553a786da96..ae36f8a7b30b 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -395,6 +395,16 @@ static void nativeSetEarlyWakeup(JNIEnv* env, jclass clazz, jlong transactionObj transaction->setEarlyWakeup(); } +static void nativeSetEarlyWakeupStart(JNIEnv* env, jclass clazz, jlong transactionObj) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + transaction->setExplicitEarlyWakeupStart(); +} + +static void nativeSetEarlyWakeupEnd(JNIEnv* env, jclass clazz, jlong transactionObj) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + transaction->setExplicitEarlyWakeupEnd(); +} + static void nativeSetLayer(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jint zorder) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -1501,6 +1511,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetAnimationTransaction }, {"nativeSetEarlyWakeup", "(J)V", (void*)nativeSetEarlyWakeup }, + {"nativeSetEarlyWakeupStart", "(J)V", + (void*)nativeSetEarlyWakeupStart }, + {"nativeSetEarlyWakeupEnd", "(J)V", + (void*)nativeSetEarlyWakeupEnd }, {"nativeSetLayer", "(JJI)V", (void*)nativeSetLayer }, {"nativeSetRelativeLayer", "(JJJI)V", diff --git a/core/proto/android/app/enums.proto b/core/proto/android/app/enums.proto index 563ef145b79c..bd5cb62f7fde 100644 --- a/core/proto/android/app/enums.proto +++ b/core/proto/android/app/enums.proto @@ -206,4 +206,5 @@ enum AppOpEnum { APP_OP_DEPRECATED_1 = 96 [deprecated = true]; APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED = 97; APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER = 98; + APP_OP_NO_ISOLATED_STORAGE = 99; } diff --git a/core/proto/android/server/blobstoremanagerservice.proto b/core/proto/android/server/blobstoremanagerservice.proto new file mode 100644 index 000000000000..583b646eb9c7 --- /dev/null +++ b/core/proto/android/server/blobstoremanagerservice.proto @@ -0,0 +1,70 @@ +/* + * 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. + */ + +syntax = "proto2"; +package com.android.server.blob; + +option java_multiple_files = true; + +// The nested messages are used for statsd logging and should be kept in sync with the messages +// of the same name in frameworks/base/cmds/statsd/src/atoms.proto +message BlobStatsEventProto { + // Blob Committer stats + // Keep in sync between: + // frameworks/base/core/proto/android/server/blobstoremanagerservice.proto + // frameworks/base/cmds/statsd/src/atoms.proto + message BlobCommitterProto { + // Committer app's uid + optional int32 uid = 1; + + // Unix epoch timestamp of the commit in milliseconds + optional int64 commit_timestamp_millis = 2; + + // Flags of what access types the committer has set for the Blob + optional int32 access_mode = 3; + + // Number of packages that have been whitelisted for ACCESS_TYPE_WHITELIST + optional int32 num_whitelisted_package = 4; + } + + // Blob Leasee stats + // Keep in sync between: + // frameworks/base/core/proto/android/server/blobstoremanagerservice.proto + // frameworks/base/cmds/statsd/src/atoms.proto + message BlobLeaseeProto { + // Leasee app's uid + optional int32 uid = 1; + + // Unix epoch timestamp for lease expiration in milliseconds + optional int64 lease_expiry_timestamp_millis = 2; + } + + // List of Blob Committers + // Keep in sync between: + // frameworks/base/core/proto/android/server/blobstoremanagerservice.proto + // frameworks/base/cmds/statsd/src/atoms.proto + message BlobCommitterListProto { + repeated BlobCommitterProto committer = 1; + } + + // List of Blob Leasees + // Keep in sync between: + // frameworks/base/core/proto/android/server/blobstoremanagerservice.proto + // frameworks/base/cmds/statsd/src/atoms.proto + message BlobLeaseeListProto { + repeated BlobLeaseeProto leasee = 1; + } +}
\ No newline at end of file diff --git a/core/proto/android/stats/connectivity/Android.bp b/core/proto/android/stats/connectivity/Android.bp index 5d642d3845fe..5e6ac3cd3ca1 100644 --- a/core/proto/android/stats/connectivity/Android.bp +++ b/core/proto/android/stats/connectivity/Android.bp @@ -13,12 +13,26 @@ // limitations under the License. java_library_static { - name: "networkstackprotosnano", + name: "networkstackprotos", proto: { - type: "nano", + type: "lite", }, srcs: [ "network_stack.proto", ], + sdk_version: "system_29", +} + +java_library_static { + name: "tetheringprotos", + proto: { + type: "lite", + }, + srcs: [ + "tethering.proto", + ], + apex_available: [ + "com.android.tethering", + ], sdk_version: "system_current", } diff --git a/core/proto/android/stats/connectivity/network_stack.proto b/core/proto/android/stats/connectivity/network_stack.proto index 7d9aa1c6eb23..e9726d7ce195 100644 --- a/core/proto/android/stats/connectivity/network_stack.proto +++ b/core/proto/android/stats/connectivity/network_stack.proto @@ -20,6 +20,160 @@ package android.stats.connectivity; option java_multiple_files = true; option java_outer_classname = "NetworkStackProto"; +enum DhcpRenewResult { + RR_UNKNOWN = 0; + RR_SUCCESS = 1; + RR_ERROR_NAK = 2; + RR_ERROR_IP_MISMATCH = 3; + RR_ERROR_IP_EXPIRE = 4; +} + +enum DisconnectCode { + DC_NONE = 0; + DC_NORMAL_TERMINATION = 1; + DC_PROVISIONING_FAIL = 2; + DC_ERROR_STARTING_IPV4 = 4; + DC_ERROR_STARTING_IPV6 = 5; + DC_ERROR_STARTING_IPREACHABILITYMONITOR = 6; + DC_INVALID_PROVISIONING = 7; + DC_INTERFACE_NOT_FOUND = 8; + DC_PROVISIONING_TIMEOUT = 9; +} + +enum TransportType { + TT_UNKNOWN = 0; + // Indicates this network uses a Cellular transport + TT_CELLULAR = 1; + // Indicates this network uses a Wi-Fi transport + TT_WIFI = 2; + // Indicates this network uses a Bluetooth transport + TT_BLUETOOTH = 3; + // Indicates this network uses an Ethernet transport + TT_ETHERNET = 4; + // Indicates this network uses a Wi-Fi Aware transport + TT_WIFI_AWARE = 5; + // Indicates this network uses a LoWPAN transport + TT_LOWPAN = 6; + // Indicates this network uses a Cellular+VPN transport + TT_CELLULAR_VPN = 7; + // Indicates this network uses a Wi-Fi+VPN transport + TT_WIFI_VPN = 8; + // Indicates this network uses a Bluetooth+VPN transport + TT_BLUETOOTH_VPN = 9; + // Indicates this network uses an Ethernet+VPN transport + TT_ETHERNET_VPN = 10; + // Indicates this network uses a Wi-Fi+Cellular+VPN transport + TT_WIFI_CELLULAR_VPN = 11; + // Indicates this network uses for test only + TT_TEST = 12; +} + +enum DhcpFeature { + DF_UNKNOWN = 0; + // DHCP INIT-REBOOT state + DF_INITREBOOT = 1; + // DHCP rapid commit option + DF_RAPIDCOMMIT = 2; + // Duplicate address detection + DF_DAD = 3; + // Fast initial Link setup + DF_FILS = 4; +} + +enum HostnameTransResult { + HTR_UNKNOWN = 0; + HTR_SUCCESS = 1; + HTR_FAILURE = 2; + HTR_DISABLE = 3; +} + +enum ProbeResult { + PR_UNKNOWN = 0; + PR_SUCCESS = 1; + PR_FAILURE = 2; + PR_PORTAL = 3; + // DNS query for the probe host returned a private IP address + PR_PRIVATE_IP_DNS = 4; +} + +enum ValidationResult { + VR_UNKNOWN = 0; + VR_SUCCESS = 1; + VR_FAILURE = 2; + VR_PORTAL = 3; + VR_PARTIAL = 4; +} + +enum ProbeType { + PT_UNKNOWN = 0; + PT_DNS = 1; + PT_HTTP = 2; + PT_HTTPS = 3; + PT_PAC = 4; + PT_FALLBACK = 5; + PT_PRIVDNS = 6; + PT_CAPPORT_API = 7; +} + +// The Dhcp error code is defined in android.net.metrics.DhcpErrorEvent +enum DhcpErrorCode { + ET_UNKNOWN = 0; + ET_L2_ERROR = 1; + ET_L3_ERROR = 2; + ET_L4_ERROR = 3; + ET_DHCP_ERROR = 4; + ET_MISC_ERROR = 5; + /* Reserve for error type + // ET_L2_ERROR_TYPE = ET_L2_ERROR << 8; + ET_L2_ERROR_TYPE = 256; + // ET_L3_ERROR_TYPE = ET_L3_ERROR << 8; + ET_L3_ERROR_TYPE = 512; + // ET_L4_ERROR_TYPE = ET_L4_ERROR << 8; + ET_L4_ERROR_TYPE = 768; + // ET_DHCP_ERROR_TYPE = ET_DHCP_ERROR << 8; + ET_DHCP_ERROR_TYPE = 1024; + // ET_MISC_ERROR_TYPE = ET_MISC_ERROR << 8; + ET_MISC_ERROR_TYPE = 1280; + */ + // ET_L2_TOO_SHORT = (ET_L2_ERROR_TYPE | 0x1) << 16; + ET_L2_TOO_SHORT = 16842752; + // ET_L2_WRONG_ETH_TYPE = (ET_L2_ERROR_TYPE | 0x2) << 16; + ET_L2_WRONG_ETH_TYPE = 16908288; + // ET_L3_TOO_SHORT = (ET_L3_ERROR_TYPE | 0x1) << 16; + ET_L3_TOO_SHORT = 33619968; + // ET_L3_NOT_IPV4 = (ET_L3_ERROR_TYPE | 0x2) << 16; + ET_L3_NOT_IPV4 = 33685504; + // ET_L3_INVALID_IP = (ET_L3_ERROR_TYPE | 0x3) << 16; + ET_L3_INVALID_IP = 33751040; + // ET_L4_NOT_UDP = (ET_L4_ERROR_TYPE | 0x1) << 16; + ET_L4_NOT_UDP = 50397184; + // ET_L4_WRONG_PORT = (ET_L4_ERROR_TYPE | 0x2) << 16; + ET_L4_WRONG_PORT = 50462720; + // ET_BOOTP_TOO_SHORT = (ET_DHCP_ERROR_TYPE | 0x1) << 16; + ET_BOOTP_TOO_SHORT = 67174400; + // ET_DHCP_BAD_MAGIC_COOKIE = (ET_DHCP_ERROR_TYPE | 0x2) << 16; + ET_DHCP_BAD_MAGIC_COOKIE = 67239936; + // ET_DHCP_INVALID_OPTION_LENGTH = (ET_DHCP_ERROR_TYPE | 0x3) << 16; + ET_DHCP_INVALID_OPTION_LENGTH = 67305472; + // ET_DHCP_NO_MSG_TYPE = (ET_DHCP_ERROR_TYPE | 0x4) << 16; + ET_DHCP_NO_MSG_TYPE = 67371008; + // ET_DHCP_UNKNOWN_MSG_TYPE = (ET_DHCP_ERROR_TYPE | 0x5) << 16; + ET_DHCP_UNKNOWN_MSG_TYPE = 67436544; + // ET_DHCP_NO_COOKIE = (ET_DHCP_ERROR_TYPE | 0x6) << 16; + ET_DHCP_NO_COOKIE = 67502080; + // ET_BUFFER_UNDERFLOW = (ET_MISC_ERROR_TYPE | 0x1) << 16; + ET_BUFFER_UNDERFLOW = 83951616; + // ET_RECEIVE_ERROR = (ET_MISC_ERROR_TYPE | 0x2) << 16; + ET_RECEIVE_ERROR = 84017152; + // ET_PARSING_ERROR = (ET_MISC_ERROR_TYPE | 0x3) << 16; + ET_PARSING_ERROR = 84082688; +} + +enum NetworkQuirkEvent { + QE_UNKNOWN = 0; + QE_IPV6_PROVISIONING_ROUTER_LOST = 1; +} + message NetworkStackEventData { } diff --git a/core/proto/android/stats/connectivity/tethering.proto b/core/proto/android/stats/connectivity/tethering.proto new file mode 100644 index 000000000000..6303b7d1870b --- /dev/null +++ b/core/proto/android/stats/connectivity/tethering.proto @@ -0,0 +1,97 @@ +/* + * 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. + */ +syntax = "proto2"; +package android.stats.connectivity; +option java_multiple_files = true; +option java_outer_classname = "TetheringProto"; + +enum ErrorCode { + EC_NO_ERROR = 0; + EC_UNKNOWN_IFACE = 1; + EC_SERVICE_UNAVAIL = 2; + EC_UNSUPPORTED = 3; + EC_UNAVAIL_IFACE = 4; + EC_INTERNAL_ERROR = 5; + EC_TETHER_IFACE_ERROR = 6; + EC_UNTETHER_IFACE_ERROR = 7; + EC_ENABLE_FORWARDING_ERROR = 8; + EC_DISABLE_FORWARDING_ERROR = 9; + EC_IFACE_CFG_ERROR = 10; + EC_PROVISIONING_FAILED = 11; + EC_DHCPSERVER_ERROR = 12; + EC_ENTITLEMENT_UNKNOWN = 13; + EC_NO_CHANGE_TETHERING_PERMISSION = 14; + EC_NO_ACCESS_TETHERING_PERMISSION = 15; + EC_UNKNOWN_TYPE = 16; +} + +enum DownstreamType { + // Unspecific tethering type. + DS_UNSPECIFIED = 0; + // Wifi tethering type. + DS_TETHERING_WIFI = 1; + // USB tethering type. + DS_TETHERING_USB = 2; + // Bluetooth tethering type. + DS_TETHERING_BLUETOOTH = 3; + // Wifi P2p tethering type. + DS_TETHERING_WIFI_P2P = 4; + // NCM (Network Control Model) local tethering type. + DS_TETHERING_NCM = 5; + // Ethernet tethering type. + DS_TETHERING_ETHERNET = 6; +} + +enum UpstreamType { + UT_UNKNOWN = 0; + // Indicates upstream using a Cellular transport. + UT_CELLULAR = 1; + // Indicates upstream using a Wi-Fi transport. + UT_WIFI = 2; + // Indicates upstream using a Bluetooth transport. + UT_BLUETOOTH = 3; + // Indicates upstream using an Ethernet transport. + UT_ETHERNET = 4; + // Indicates upstream using a Wi-Fi Aware transport. + UT_WIFI_AWARE = 5; + // Indicates upstream using a LoWPAN transport. + UT_LOWPAN = 6; + // Indicates upstream using a Cellular+VPN transport. + UT_CELLULAR_VPN = 7; + // Indicates upstream using a Wi-Fi+VPN transport. + UT_WIFI_VPN = 8; + // Indicates upstream using a Bluetooth+VPN transport. + UT_BLUETOOTH_VPN = 9; + // Indicates upstream using an Ethernet+VPN transport. + UT_ETHERNET_VPN = 10; + // Indicates upstream using a Wi-Fi+Cellular+VPN transport. + UT_WIFI_CELLULAR_VPN = 11; + // Indicates upstream using for test only. + UT_TEST = 12; + // Indicates upstream using DUN capability + Cellular transport. + UT_DUN_CELLULAR = 13; +} + +enum UserType { + // Unknown. + USER_UNKOWNN = 0; + // Settings. + USER_SETTINGS = 1; + // System UI. + USER_SYSTEMUI = 2; + // Google mobile service. + USER_GMS = 3; +} diff --git a/core/proto/android/stats/launcher/launcher.proto b/core/proto/android/stats/launcher/launcher.proto index dbd0e038c40c..fc177d57b193 100644 --- a/core/proto/android/stats/launcher/launcher.proto +++ b/core/proto/android/stats/launcher/launcher.proto @@ -32,10 +32,12 @@ enum LauncherAction { } enum LauncherState { - BACKGROUND = 0; - HOME = 1; - OVERVIEW = 2; - ALLAPPS = 3; + LAUNCHER_STATE_UNSPECIFIED = 0; + BACKGROUND = 1; + HOME = 2; + OVERVIEW = 3; + ALLAPPS = 4; + UNCHANGED = 5; } message LauncherTarget { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f71d4063b847..9c1ecf2e48e0 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3534,6 +3534,8 @@ @hide --> <permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" android:protectionLevel="signature" /> + <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" /> + <!-- Must be required by a {@link android.media.routing.MediaRouteService} to ensure that only the system can interact with it. @@ -5026,6 +5028,10 @@ <permission android:name="android.permission.ACCESS_TV_DESCRAMBLER" android:protectionLevel="signature|privileged|vendorPrivileged" /> + <!-- Allows an application to create trusted displays. @hide --> + <permission android:name="android.permission.ADD_TRUSTED_DISPLAY" + android:protectionLevel="signature" /> + <!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. --> <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS" android:protectionLevel="signature|appPredictor" /> diff --git a/core/res/res/drawable-nodpi/ic_number11.xml b/core/res/res/drawable-nodpi/ic_number11.xml new file mode 100644 index 000000000000..daad61148c80 --- /dev/null +++ b/core/res/res/drawable-nodpi/ic_number11.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M5.14,5H1.59a0.88,0.88 0,0 1,-0.88 -0.89V0.88A0.87,0.87 0,0 1,1.59 0H9.36a0.87,0.87 0,0 1,0.88 0.88V23.12a0.88,0.88 0,0 1,-0.88 0.88H6a0.88,0.88 0,0 1,-0.88 -0.88Z" + android:fillColor="#000000"/> + <path + android:pathData="M18.19,5H14.64a0.89,0.89 0,0 1,-0.88 -0.89V0.88A0.88,0.88 0,0 1,14.64 0h7.78a0.87,0.87 0,0 1,0.87 0.88V23.12a0.88,0.88 0,0 1,-0.87 0.88H19.08a0.89,0.89 0,0 1,-0.89 -0.88Z" + android:fillColor="#000000"/> +</vector> diff --git a/core/res/res/layout-car/car_alert_dialog.xml b/core/res/res/layout-car/car_alert_dialog.xml new file mode 100644 index 000000000000..569e5948e2e0 --- /dev/null +++ b/core/res/res/layout-car/car_alert_dialog.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<com.android.internal.widget.AlertDialogLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/parentPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start|top" + android:orientation="vertical"> + + <include layout="@layout/car_alert_dialog_title" /> + + <FrameLayout + android:id="@+id/contentPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="48dp"> + + <ScrollView + android:id="@+id/scrollView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipToPadding="false"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <Space + android:id="@+id/textSpacerNoTitle" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="@dimen/dialog_no_title_padding_top" /> + + <TextView + android:id="@+id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/text_view_start_margin" + android:layout_marginEnd="@dimen/text_view_end_margin" + style="@style/CarBody2"/> + + <!-- we don't need this spacer, but the id needs to be here for compatibility --> + <Space + android:id="@+id/textSpacerNoButtons" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="0dp" /> + </LinearLayout> + </ScrollView> + </FrameLayout> + + <FrameLayout + android:id="@+id/customPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="48dp"> + + <FrameLayout + android:id="@+id/custom" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </FrameLayout> + + <include + android:layout_width="match_parent" + android:layout_height="wrap_content" + layout="@layout/car_alert_dialog_button_bar" /> +</com.android.internal.widget.AlertDialogLayout> diff --git a/core/res/res/layout-car/car_alert_dialog_button_bar.xml b/core/res/res/layout-car/car_alert_dialog_button_bar.xml new file mode 100644 index 000000000000..277b0dcca657 --- /dev/null +++ b/core/res/res/layout-car/car_alert_dialog_button_bar.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/buttonPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scrollbarAlwaysDrawVerticalTrack="true" + android:scrollIndicators="top|bottom" + android:fillViewport="true" + style="?attr/buttonBarStyle"> + <com.android.internal.widget.ButtonBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="@dimen/button_bar_layout_start_padding" + android:paddingEnd="@dimen/button_bar_layout_end_padding" + android:paddingTop="@dimen/button_bar_layout_top_padding" + android:layoutDirection="locale" + android:orientation="horizontal" + android:gravity="left|center_vertical"> + + <Button + android:id="@+id/button3" + style="@style/CarAction1" + android:background="@drawable/car_dialog_button_background" + android:layout_marginRight="@dimen/button_end_margin" + android:layout_width="wrap_content" + android:layout_height="@dimen/button_layout_height" /> + + <Button + android:id="@+id/button2" + style="@style/CarAction1" + android:background="@drawable/car_dialog_button_background" + android:layout_marginRight="@dimen/button_end_margin" + android:layout_width="wrap_content" + android:layout_height="@dimen/button_layout_height" /> + + <Button + android:id="@+id/button1" + style="@style/CarAction1" + android:background="@drawable/car_dialog_button_background" + android:layout_width="wrap_content" + android:layout_height="@dimen/button_layout_height" /> + <Space + android:id="@+id/spacer" + android:layout_width="0dp" + android:layout_height="0dp" + android:visibility="invisible" /> + </com.android.internal.widget.ButtonBarLayout> +</ScrollView> diff --git a/core/res/res/layout-car/car_alert_dialog_title.xml b/core/res/res/layout-car/car_alert_dialog_title.xml new file mode 100644 index 000000000000..ba735a649dc2 --- /dev/null +++ b/core/res/res/layout-car/car_alert_dialog_title.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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" + android:id="@+id/topPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="vertical"> + + <!-- If the client uses a customTitle, it will be added here. --> + + <RelativeLayout + android:id="@+id/title_template" + android:layout_width="match_parent" + android:layout_height="@dimen/car_card_header_height" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/icon" + android:layout_width="@dimen/image_size" + android:layout_height="@dimen/image_size" + android:layout_marginStart="@dimen/image_margin_start" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:scaleType="fitCenter" + android:src="@null" /> + + <com.android.internal.widget.DialogTitle + android:id="@+id/alertTitle" + android:maxLines="1" + android:ellipsize="none" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toEndOf="@+id/icon" + android:textAlignment="viewStart" + android:layout_centerVertical="true" + android:layout_marginStart="@dimen/text_view_start_margin" + android:layout_marginEnd="@dimen/text_view_end_margin" + style="?attr/windowTitleStyle" /> + </RelativeLayout> + + <Space + android:id="@+id/titleDividerNoCustom" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="0dp" /> +</LinearLayout> diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml index 3188861a52a5..5e3b657353b6 100644 --- a/core/res/res/layout/notification_template_messaging_group.xml +++ b/core/res/res/layout/notification_template_messaging_group.xml @@ -37,6 +37,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" + android:baselineAligned="true" android:orientation="vertical"> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/message_name" diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index c2d501355d5f..56e7a2703d5e 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -1665,7 +1665,7 @@ <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Odbij"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Dodirnite neku funkciju da biste počeli da je koristite:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Odaberite funkcije koje ćete koristiti sa dugmetom Pristupačnost"</string> - <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Odaberite funkcije koje ćete koristiti sa tasterom jačine zvuka kao prečicom"</string> + <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Odaberite funkcije za prečicu tasterom jačine zvuka"</string> <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"Usluga <xliff:g id="SERVICE_NAME">%s</xliff:g> je isključena"</string> <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Izmenite prečice"</string> <string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"Gotovo"</string> @@ -1673,8 +1673,8 @@ <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Koristi prečicu"</string> <string name="color_inversion_feature_name" msgid="326050048927789012">"Inverzija boja"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"Korekcija boja"</string> - <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Zadržali ste tastere za jačinu zvuka. Usluga <xliff:g id="SERVICE_NAME">%1$s</xliff:g> je uključena."</string> - <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Zadržali ste tastere za jačinu zvuka. Usluga <xliff:g id="SERVICE_NAME">%1$s</xliff:g> je isključena."</string> + <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Držali ste tastere za jačinu zvuka. Usluga <xliff:g id="SERVICE_NAME">%1$s</xliff:g> je uključena."</string> + <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Držali ste tastere za jačinu zvuka. Usluga <xliff:g id="SERVICE_NAME">%1$s</xliff:g> je isključena."</string> <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Pritisnite i zadržite oba tastera za jačinu zvuka tri sekunde da biste koristili <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string> <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Izaberite funkciju koja će se koristiti kada dodirnete dugme Pristupačnost:"</string> <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Odaberite funkciju koja će se koristiti pomoću pokreta za pristupačnost (pomoću dva prsta prevucite nagore od dna ekrana):"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 2bdd4f7ad230..84136225de38 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1687,7 +1687,7 @@ <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Адмовіць"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Каб пачаць выкарыстоўваць функцыю, націсніце на яе:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Выберыце функцыі, якія будзеце выкарыстоўваць з кнопкай спецыяльных магчымасцей"</string> - <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Выберыце функцыі, якія будзеце выкарыстоўваць са спалучэннем клавішы гучнасці"</string> + <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Выберыце функцыі для выкарыстання з клавішай гучнасці"</string> <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"Сэрвіс \"<xliff:g id="SERVICE_NAME">%s</xliff:g>\" выключаны"</string> <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Змяніць ярлыкі"</string> <string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"Гатова"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index e18fc11bc7c3..8e182a24c7e3 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1311,7 +1311,7 @@ <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Seleccionar para inhabilitar la depuración USB"</string> <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Depuración inalámbrica conectada"</string> <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Toca para desactivar la depuración inalámbrica"</string> - <string name="adbwifi_active_notification_message" product="tv" msgid="8633421848366915478">"Selecciona para inhabilitar la depuración inalámbrica."</string> + <string name="adbwifi_active_notification_message" product="tv" msgid="8633421848366915478">"Toca para desactivar la depuración inalámbrica."</string> <string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Modo de agente de prueba habilitado"</string> <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Restablece los ajustes de fábrica para inhabilitar el modo de agente de prueba."</string> <string name="console_running_notification_title" msgid="6087888939261635904">"Se ha habilitado la consola en serie"</string> @@ -1623,8 +1623,8 @@ <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"¿Quieres subir el volumen por encima del nivel recomendado?\n\nEscuchar sonidos fuertes durante mucho tiempo puede dañar los oídos."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"¿Utilizar acceso directo de accesibilidad?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Si el acceso directo está activado, pulsa los dos botones de volumen durante 3 segundos para iniciar una función de accesibilidad."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"¿Quieres activar las funciones de accesibilidad?"</string> - <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Al mantener pulsadas ambas teclas de volumen durante unos segundos, se activan las funciones de accesibilidad, que pueden cambiar el funcionamiento del dispositivo.\n\nFunciones actuales:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuedes cambiar las funciones seleccionadas en Ajustes > Accesibilidad."</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"¿Activar funciones de accesibilidad?"</string> + <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Al mantener pulsadas las dos teclas de volumen durante unos segundos, se activan las funciones de accesibilidad, que pueden cambiar el funcionamiento del dispositivo.\n\nFunciones actuales:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuedes cambiar las funciones seleccionadas en Ajustes > Accesibilidad."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"¿Quieres activar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Al mantener pulsadas ambas teclas de volumen durante unos segundos se activa <xliff:g id="SERVICE">%1$s</xliff:g>, una función de accesibilidad. Esta función puede modificar el funcionamiento del dispositivo.\n\nPuedes asignar este acceso directo a otra función en Ajustes > Accesibilidad."</string> @@ -1642,8 +1642,8 @@ <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Permitir"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Denegar"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Toca una función para empezar a usarla:"</string> - <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Seleccionar qué funciones usar con el botón Accesibilidad"</string> - <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Seleccionar qué funciones usar con la tecla de volumen"</string> + <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Selecciona qué funciones usar con el botón Accesibilidad"</string> + <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Selecciona qué funciones usar con la tecla de volumen"</string> <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"Se ha desactivado <xliff:g id="SERVICE_NAME">%s</xliff:g>"</string> <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Editar accesos directos"</string> <string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"Listo"</string> @@ -1651,8 +1651,8 @@ <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utilizar acceso directo"</string> <string name="color_inversion_feature_name" msgid="326050048927789012">"Inversión de color"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"Corrección de color"</string> - <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Al mantener pulsadas las teclas de volumen, <xliff:g id="SERVICE_NAME">%1$s</xliff:g> se ha activado."</string> - <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Al mantener pulsadas las teclas de volumen, <xliff:g id="SERVICE_NAME">%1$s</xliff:g> se ha desactivado."</string> + <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Al mantener pulsadas las teclas de volumen, se ha activado <xliff:g id="SERVICE_NAME">%1$s</xliff:g>."</string> + <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Se han mantenido pulsadas las teclas de volumen. Se ha desactivado <xliff:g id="SERVICE_NAME">%1$s</xliff:g>."</string> <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Para utilizar <xliff:g id="SERVICE_NAME">%1$s</xliff:g>, mantén pulsadas ambas teclas de volumen durante 3 segundos"</string> <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Selecciona la función que se utilizará cuando toques el botón Accesibilidad:"</string> <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Elige la función que se utilizará con el gesto de accesibilidad (deslizar dos dedos hacia arriba desde la parte inferior de la pantalla):"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 56887d822483..e7239eb46e41 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1624,10 +1624,10 @@ <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Kas kasutada juurdepääsetavuse otseteed?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kui otsetee on sisse lülitatud, käivitab mõlema helitugevuse nupu kolm sekundit all hoidmine juurdepääsetavuse funktsiooni."</string> <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Kas lülitada juurdepääsufunktsioonid sisse?"</string> - <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hoidke mõlemat helitugevuse nuppu mõni sekund all, et juurdepääsufunktsioonid sisse lülitada. See võib teie seadme tööviisi muuta.\n\nPraegused funktsioonid:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nValitud funktsioone saab muuta jaotises Seaded > Juurdepääsetavus."</string> + <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hoidke juurdepääsufunktsioonide sisselülitamiseks mõlemat helitugevuse klahvi mõni sekund all. See võib teie seadme tööviisi muuta.\n\nPraegused funktsioonid:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nValitud funktsioone saab muuta jaotises Seaded > Juurdepääsetavus."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Kas lülitada <xliff:g id="SERVICE">%1$s</xliff:g> sisse?"</string> - <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Kui hoiate mõlemat helitugevuse nuppu mõni sekund all, lülitatakse sisse juurdepääsufunktsioon <xliff:g id="SERVICE">%1$s</xliff:g>. See võib teie seadme tööviisi muuta.\n\nSelle otsetee saab asendada muu otseteega jaotises Seaded > Juurdepääsetavus."</string> + <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Kui hoiate mõlemat helitugevuse klahvi mõni sekund all, lülitatakse juurdepääsufunktsioon <xliff:g id="SERVICE">%1$s</xliff:g> sisse. See võib teie seadme tööviisi muuta.\n\nSelle otsetee saab asendada muu otseteega jaotises Seaded > Juurdepääsetavus."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Lülita sisse"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ära lülita sisse"</string> <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"SEES"</string> @@ -1643,7 +1643,7 @@ <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Keela"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Puudutage funktsiooni, et selle kasutamist alustada."</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Valige funktsioonid, mida juurdepääsetavuse nupuga kasutada"</string> - <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Valige funktsioonid, mida helitugevuse nupu otseteega kasutada"</string> + <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Valige helitugevuse nupu otsetee funktsioonid"</string> <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"<xliff:g id="SERVICE_NAME">%s</xliff:g> on välja lülitatud"</string> <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Muuda otseteid"</string> <string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"Valmis"</string> @@ -1651,13 +1651,13 @@ <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Kasuta otseteed"</string> <string name="color_inversion_feature_name" msgid="326050048927789012">"Värvide ümberpööramine"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"Värvide korrigeerimine"</string> - <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Helitugevuse nuppe hoiti all. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> lülitati sisse."</string> - <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Helitugevuse nuppe hoiti all. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> lülitati välja."</string> + <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Helitugevuse klahve hoiti all. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> lülitati sisse."</string> + <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Helitugevuse klahve hoiti all. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> lülitati välja."</string> <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Teenuse <xliff:g id="SERVICE_NAME">%1$s</xliff:g> kasutamiseks hoidke kolm sekundit all mõlemat helitugevuse klahvi"</string> <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Valige, millist funktsiooni kasutada, kui vajutate juurdepääsetavuse nuppu:"</string> <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Valige, millist funktsiooni juurdepääsetavuse liigutusega (kahe sõrmega ekraanikuval alt üles pühkimine) kasutada:"</string> <string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"Valige, millist funktsiooni juurdepääsetavuse liigutusega (kolme sõrmega ekraanikuval alt üles pühkimine) kasutada:"</string> - <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"Funktsioonide vahel vahetamiseks vajutage pikalt juurdepääsetavuse nuppu."</string> + <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"Funktsioonide vahel vahetamiseks vajutage juurdepääsetavuse nuppu pikalt."</string> <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"Funktsioonide vahel vahetamiseks pühkige kahe sõrmega üles ja hoidke."</string> <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Funktsioonide vahel vahetamiseks pühkige kolme sõrmega üles ja hoidke."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Suurendus"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 0890ae13e421..9f618d5accbd 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -184,7 +184,7 @@ <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"किसी अज्ञात तृतीय पक्ष के द्वारा"</string> <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"आपकी वर्क प्रोफ़ाइल का व्यवस्थापक करता है"</string> <string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"<xliff:g id="MANAGING_DOMAIN">%s</xliff:g> के द्वारा"</string> - <string name="work_profile_deleted" msgid="5891181538182009328">"वर्क प्रोफ़ाइल हटाई गई"</string> + <string name="work_profile_deleted" msgid="5891181538182009328">"वर्क प्रोफ़ाइल मिटाई गई"</string> <string name="work_profile_deleted_details" msgid="3773706828364418016">"वर्क प्रोफ़ाइल व्यवस्थापक ऐप्लिकेशन या तो मौजूद नहीं है या वह खराब हो गया है. परिणामस्वरूप, आपकी वर्क प्रोफ़ाइल और उससे जुड़े डेटा को हटा दिया गया है. सहायता के लिए अपने व्यवस्थापक से संपर्क करें."</string> <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"आपकी वर्क प्रोफ़ाइल अब इस डिवाइस पर उपलब्ध नहीं है"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"कई बार गलत पासवर्ड डाला गया"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 1fb9a62025bb..ec56952ea4a2 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -1841,7 +1841,7 @@ <string name="zen_mode_default_weeknights_name" msgid="7902108149994062847">"Աշխատանքային օր"</string> <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Շաբաթ-կիրակի"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Միջոցառում"</string> - <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Քնելիս"</string> + <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Քնի ժամանակ"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>-ն անջատում է որոշ ձայներ"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Սարքում ներքին խնդիր է առաջացել և այն կարող է կրկնվել, մինչև չվերականգնեք գործարանային կարգավորումները:"</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Սարքում ներքին խնդիր է առաջացել: Մանրամասների համար կապվեք արտադրողի հետ:"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 9fbd73ea1269..3fb2a7732f0e 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -339,7 +339,7 @@ <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Mengizinkan aplikasi menambahkan pintasan Layar Utama tanpa tindakan dari pengguna."</string> <string name="permlab_uninstall_shortcut" msgid="295263654781900390">"meng-uninstal pintasan"</string> <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Mengizinkan aplikasi menghapus pintasan Layar Utama tanpa tindakan dari pengguna."</string> - <string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"ubah rute panggilan keluar"</string> + <string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"alihkan panggilan keluar"</string> <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Memungkinkan aplikasi melihat nomor yang dihubungi saat melakukan panggilan keluar dengan opsi untuk mengalihkan panggilan ke nomor lain atau membatalkan panggilan sepenuhnya."</string> <string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"jawab panggilan telepon"</string> <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"Mengizinkan aplikasi menjawab panggilan telepon masuk."</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index b6a850d10fef..80e34ef648cf 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -222,7 +222,7 @@ <string name="reboot_to_update_reboot" msgid="4474726009984452312">"Riavvio in corso…"</string> <string name="reboot_to_reset_title" msgid="2226229680017882787">"Ripristino dati di fabbrica"</string> <string name="reboot_to_reset_message" msgid="3347690497972074356">"Riavvio in corso…"</string> - <string name="shutdown_progress" msgid="5017145516412657345">"Spegnimento..."</string> + <string name="shutdown_progress" msgid="5017145516412657345">"Spegnimento…"</string> <string name="shutdown_confirm" product="tablet" msgid="2872769463279602432">"Il tablet verrà spento."</string> <string name="shutdown_confirm" product="tv" msgid="7975942887313518330">"Il dispositivo Android TV verrà spento."</string> <string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"L\'orologio verrà spento."</string> @@ -856,7 +856,7 @@ <string name="lockscreen_sim_puk_locked_message" msgid="6618356415831082174">"La SIM è bloccata tramite PUK."</string> <string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"Consulta la Guida dell\'utente o contatta il servizio clienti."</string> <string name="lockscreen_sim_locked_message" msgid="3160196135801185938">"La SIM è bloccata."</string> - <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"Sblocco SIM..."</string> + <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="2286497117428409709">"Sblocco SIM…"</string> <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"<xliff:g id="NUMBER_0">%1$d</xliff:g> tentativi errati di inserimento della sequenza di sblocco. \n\nRiprova tra <xliff:g id="NUMBER_1">%2$d</xliff:g> secondi."</string> <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"Hai digitato la tua password <xliff:g id="NUMBER_0">%1$d</xliff:g> volte in modo errato. \n\nRiprova tra <xliff:g id="NUMBER_1">%2$d</xliff:g> secondi."</string> <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"Hai digitato il tuo PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> volte in modo errato. \n\nRiprova tra <xliff:g id="NUMBER_1">%2$d</xliff:g> secondi."</string> @@ -879,7 +879,7 @@ <string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"Accedi"</string> <string name="lockscreen_glogin_invalid_input" msgid="4369219936865697679">"Password o nome utente non valido."</string> <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Hai dimenticato il nome utente o la password?\nVisita "<b>"google.com/accounts/recovery"</b>"."</string> - <string name="lockscreen_glogin_checking_password" msgid="2607271802803381645">"Verifica..."</string> + <string name="lockscreen_glogin_checking_password" msgid="2607271802803381645">"Verifica…"</string> <string name="lockscreen_unlock_label" msgid="4648257878373307582">"Sblocca"</string> <string name="lockscreen_sound_on_label" msgid="1660281470535492430">"Audio attivato"</string> <string name="lockscreen_sound_off_label" msgid="2331496559245450053">"Audio disattivato"</string> @@ -1095,7 +1095,7 @@ <string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Impossibile copiare negli appunti"</string> <string name="paste" msgid="461843306215520225">"Incolla"</string> <string name="paste_as_plain_text" msgid="7664800665823182587">"Incolla come testo normale"</string> - <string name="replace" msgid="7842675434546657444">"Sostituisci..."</string> + <string name="replace" msgid="7842675434546657444">"Sostituisci…"</string> <string name="delete" msgid="1514113991712129054">"Elimina"</string> <string name="copyUrl" msgid="6229645005987260230">"Copia URL"</string> <string name="selectTextMode" msgid="3225108910999318778">"Seleziona testo"</string> @@ -1117,7 +1117,7 @@ <string name="yes" msgid="9069828999585032361">"OK"</string> <string name="no" msgid="5122037903299899715">"Annulla"</string> <string name="dialog_alert_title" msgid="651856561974090712">"Attenzione"</string> - <string name="loading" msgid="3138021523725055037">"Caricamento..."</string> + <string name="loading" msgid="3138021523725055037">"Caricamento…"</string> <string name="capital_on" msgid="2770685323900821829">"ON"</string> <string name="capital_off" msgid="7443704171014626777">"OFF"</string> <string name="checked" msgid="9179896827054513119">"selezionato"</string> @@ -1544,7 +1544,7 @@ <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"Mostra tutto"</string> <string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Scegli attività"</string> <string name="share_action_provider_share_with" msgid="1904096863622941880">"Condividi con"</string> - <string name="sending" msgid="206925243621664438">"Invio..."</string> + <string name="sending" msgid="206925243621664438">"Invio…"</string> <string name="launchBrowserDefault" msgid="6328349989932924119">"Avviare l\'applicazione Browser?"</string> <string name="SetupCallDefault" msgid="5581740063237175247">"Accettare la chiamata?"</string> <string name="activity_resolver_use_always" msgid="5575222334666843269">"Sempre"</string> @@ -1591,7 +1591,7 @@ <string name="kg_puk_enter_puk_hint" msgid="6696187482616360994">"La scheda SIM è disattivata. Inserisci il codice PUK per continuare. Contatta l\'operatore per avere informazioni dettagliate."</string> <string name="kg_puk_enter_pin_hint" msgid="8190982314659429770">"Inserisci il codice PIN desiderato"</string> <string name="kg_enter_confirm_pin_hint" msgid="6372557107414074580">"Conferma il codice PIN desiderato"</string> - <string name="kg_sim_unlock_progress_dialog_message" msgid="8871937892678885545">"Sblocco scheda SIM..."</string> + <string name="kg_sim_unlock_progress_dialog_message" msgid="8871937892678885545">"Sblocco scheda SIM…"</string> <string name="kg_password_wrong_pin_code" msgid="9013856346870572451">"Codice PIN errato."</string> <string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Il PIN deve essere di 4-8 numeri."</string> <string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"Il codice PUK deve essere di 8 cifre."</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 07579ad0ae0f..9ada4376e50d 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -1311,7 +1311,7 @@ <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"USB арқылы түзетуді өшіру үшін таңдаңыз."</string> <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Сымсыз түзету байланыстырылды"</string> <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Сымсыз түзетуді өшіру үшін түртіңіз."</string> - <string name="adbwifi_active_notification_message" product="tv" msgid="8633421848366915478">"Сымсыз түзетуді өшіріңіз."</string> + <string name="adbwifi_active_notification_message" product="tv" msgid="8633421848366915478">"Сымсыз түзетуді өшіру үшін басыңыз."</string> <string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Сынақ бағдарламасы режимі қосылды"</string> <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Сынақ бағдарламасы режимін өшіру үшін зауыттық күйіне қайтарыңыз."</string> <string name="console_running_notification_title" msgid="6087888939261635904">"Сериялық консоль қосылды"</string> @@ -1624,7 +1624,7 @@ <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Арнайы мүмкіндік төте жолын пайдалану керек пе?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Түймелер тіркесімі қосулы кезде, екі дыбыс түймесін 3 секунд басып тұрсаңыз, \"Арнайы мүмкіндіктер\" функциясы іске қосылады."</string> <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Арнайы мүмкіндіктер іске қосылсын ба?"</string> - <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Дыбыс деңгейі пернелерін бірнеше секунд басып тұрсаңыз, арнайы мүмкіндіктер іске қосылады. Бұл – құрылғының жұмысына әсер етуі мүмкін.\n\nФункциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТаңдалған функцияларды \"Параметрлер > Арнайы мүмкіндіктер\" бөлімінен өзгерте аласыз."</string> + <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Дыбыс деңгейі пернелерін бірнеше секунд басып тұрсаңыз, арнайы мүмкіндіктер іске қосылады. Бұл – құрылғының жұмысына әсер етуі мүмкін.\n\nҚазіргі функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТаңдалған функцияларды \"Параметрлер > Арнайы мүмкіндіктер\" бөлімінен өзгерте аласыз."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> қосылсын ба?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Дыбыс деңгейі пернелерін бірнеше секунд басып тұрсаңыз, <xliff:g id="SERVICE">%1$s</xliff:g> арнайы қызметі іске қосылады. Бұл – құрылғының жүмысына әсер етуі мүмкін.\n\nБұл таңбашаны басқа функцияға \"Параметрлер > Арнайы мүмкіндіктер\" бөлімінен өзгерте аласыз."</string> @@ -1643,16 +1643,16 @@ <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Қабылдамау"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Функцияны пайдалана бастау үшін түртіңіз:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"\"Арнайы мүмкіндіктер\" түймесімен қолданылатын функцияларды таңдаңыз"</string> - <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Дыбыс деңгейі пернелері таңбашасымен қолданылатын функцияларды таңдаңыз"</string> + <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Дыбыс деңгейі пернелері тіркесімімен қолданылатын функцияларды таңдаңыз"</string> <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"<xliff:g id="SERVICE_NAME">%s</xliff:g> қызметі өшірулі."</string> - <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Таңбашаларды өзгерту"</string> + <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Жылдам пәрмендерді өзгерту"</string> <string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"Дайын"</string> <string name="disable_accessibility_shortcut" msgid="5806091378745232383">"Төте жолды өшіру"</string> <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Төте жолды пайдалану"</string> <string name="color_inversion_feature_name" msgid="326050048927789012">"Түстер инверсиясы"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"Түсті түзету"</string> <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Пайдаланушы дыбыс деңгейі пернелерін басып ұстап тұрды. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> қосулы."</string> - <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Пайдаланушы дыбыс деңгейі пернелерін басып ұстап тұрды. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> өшірулі."</string> + <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Дыбыс деңгейі пернелерін басып тұрған соң, <xliff:g id="SERVICE_NAME">%1$s</xliff:g> өшірілді."</string> <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"<xliff:g id="SERVICE_NAME">%1$s</xliff:g> қызметін пайдалану үшін дыбыс деңгейін реттейтін екі түймені де 3 секунд басып тұрыңыз"</string> <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"\"Арнайы мүмкіндіктер\" түймесін түрткенде пайдаланатын функцияны таңдаңыз:"</string> <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Арнайы мүмкіндіктер қимылымен (екі саусақпен экранның төменгі жағынан жоғары қарай сырғытыңыз) пайдаланатын функцияны таңдаңыз:"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 0fe5925e3e25..7ea8b4313f81 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1624,11 +1624,11 @@ <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"접근성 단축키를 사용하시겠습니까?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"단축키가 사용 설정된 경우 볼륨 버튼 두 개를 동시에 3초간 누르면 접근성 기능이 시작됩니다."</string> <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"접근성 기능을 사용하시겠습니까?"</string> - <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"볼륨 키 2개를 몇 초 동안 길게 누르면 접근성 기능이 사용 설정됩니다. 이렇게 되면 기기 작동 방식이 달라질 수 있습니다.\n\n현재 기능:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n설정 > 접근성에서 선택한 기능을 변경할 수 있습니다."</string> + <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"볼륨 키 2개를 몇 초 동안 길게 누르면 접근성 기능이 사용 설정됩니다. 이때 기기 작동 방식이 달라질 수 있습니다.\n\n현재 기능:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n설정 > 접근성에서 선택한 기능을 변경할 수 있습니다."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g>을(를) 사용하시겠습니까?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"볼륨 키 2개를 몇 초 동안 길게 누르면 <xliff:g id="SERVICE">%1$s</xliff:g> 접근성 기능이 사용 설정됩니다. 이렇게 되면 기기 작동 방식이 달라질 수 있습니다.\n\n설정 > 접근성에서 이 단축키를 다른 기능으로 변경할 수 있습니다."</string> - <string name="accessibility_shortcut_on" msgid="5463618449556111344">"사용 설정"</string> + <string name="accessibility_shortcut_on" msgid="5463618449556111344">"사용"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"사용 안 함"</string> <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"사용"</string> <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"사용 안함"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index d910fc03e165..c21a1d78db45 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -142,7 +142,7 @@ <string name="wfcSpnFormat_wifi" msgid="1376356951297043426">"Wi-Fi"</string> <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"WiFi ခေါ်ဆိုမှု"</string> <string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string> - <string name="wifi_calling_off_summary" msgid="5626710010766902560">"ပိတ်ထားရသည်"</string> + <string name="wifi_calling_off_summary" msgid="5626710010766902560">"ပိတ်ထားသည်"</string> <string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Wi-Fi သုံး၍ ခေါ်ဆိုသည်"</string> <string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"မိုဘိုင်းကွန်ရက်သုံး၍ ခေါ်ဆိုသည်"</string> <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ကြိုးမဲ့အင်တာနက် သာလျှင်"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 25cc45b9e964..a36035e817a3 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -175,7 +175,7 @@ <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"अति धेरै <xliff:g id="CONTENT_TYPE">%s</xliff:g> मेटाउने प्रयास गरियो।"</string> <string name="low_memory" product="tablet" msgid="5557552311566179924">"ट्याब्लेट भण्डारण खाली छैन! ठाउँ खाली गर्नको लागि केही फाइलहरू मेटाउनुहोस्।"</string> <string name="low_memory" product="watch" msgid="3479447988234030194">"भण्डारण भरिएको छ हेर्नुहोस्। ठाउँ खाली गर्न केही फाइलहरू मेटाउनुहोस्।"</string> - <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV यन्त्रको भण्डारण भरिएको छ। ठाउँ खाली गर्न केही फाइलहरू मेट्नुहोस्।"</string> + <string name="low_memory" product="tv" msgid="6663680413790323318">"Android टिभी यन्त्रको भण्डारण भरिएको छ। ठाउँ खाली गर्न केही फाइलहरू मेट्नुहोस्।"</string> <string name="low_memory" product="default" msgid="2539532364144025569">"फोन भण्डारण भरिएको छ! ठाउँ खाली गर्नको लागि केही फाइलहरू मेटाउनुहोस्।"</string> <plurals name="ssl_ca_cert_warning" formatted="false" msgid="2288194355006173029"> <item quantity="other">प्रमाणपत्रका अख्तियारीहरूलाई स्थापना गरियो</item> @@ -206,7 +206,7 @@ <string name="personal_apps_suspended_turn_profile_on" msgid="2758012869627513689">"सक्रिय गर्नुहोस्"</string> <string name="me" msgid="6207584824693813140">"मलाई"</string> <string name="power_dialog" product="tablet" msgid="8333207765671417261">"ट्याब्लेट विकल्पहरू"</string> - <string name="power_dialog" product="tv" msgid="7792839006640933763">"Android TV सम्बन्धी विकल्पहरू"</string> + <string name="power_dialog" product="tv" msgid="7792839006640933763">"Android टिभी सम्बन्धी विकल्पहरू"</string> <string name="power_dialog" product="default" msgid="1107775420270203046">"फोन विकल्पहरू"</string> <string name="silent_mode" msgid="8796112363642579333">"मौन मोड"</string> <string name="turn_on_radio" msgid="2961717788170634233">"वायरलेस अन गर्नुहोस्"</string> @@ -224,7 +224,7 @@ <string name="reboot_to_reset_message" msgid="3347690497972074356">"पुनःसुरु हुँदै ..."</string> <string name="shutdown_progress" msgid="5017145516412657345">"बन्द गर्दै..."</string> <string name="shutdown_confirm" product="tablet" msgid="2872769463279602432">"तपाईँको ट्याब्लेट बन्द हुने छ।"</string> - <string name="shutdown_confirm" product="tv" msgid="7975942887313518330">"तपाईंको Android TV यन्त्र बन्द हुने छ।"</string> + <string name="shutdown_confirm" product="tv" msgid="7975942887313518330">"तपाईंको Android टिभी यन्त्र बन्द हुने छ।"</string> <string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"तपाईँको घडी बन्द गरिने छ।"</string> <string name="shutdown_confirm" product="default" msgid="136816458966692315">"तपाईँको फोन बन्द हुने छ।"</string> <string name="shutdown_confirm_question" msgid="796151167261608447">"के तपाईं बन्द गर्न चाहनुहुन्छ?"</string> @@ -233,7 +233,7 @@ <string name="recent_tasks_title" msgid="8183172372995396653">"नयाँ"</string> <string name="no_recent_tasks" msgid="9063946524312275906">"कुनै नयाँ एपहरू छैनन्।"</string> <string name="global_actions" product="tablet" msgid="4412132498517933867">"ट्याब्लेट विकल्पहरू"</string> - <string name="global_actions" product="tv" msgid="3871763739487450369">"Android TV सम्बन्धी विकल्पहरू"</string> + <string name="global_actions" product="tv" msgid="3871763739487450369">"Android टिभी सम्बन्धी विकल्पहरू"</string> <string name="global_actions" product="default" msgid="6410072189971495460">"फोन विकल्पहरू"</string> <string name="global_action_lock" msgid="6949357274257655383">"स्क्रिन बन्द"</string> <string name="global_action_power_off" msgid="4404936470711393203">"बन्द गर्नुहोस्"</string> @@ -357,7 +357,7 @@ <string name="permdesc_sendSms" msgid="6757089798435130769">"एपलाई SMS सन्देशहरू पठाउन अनुमति दिन्छ। यसले अप्रत्यासित चार्जहरूको परिणाम दिन सक्दछ। खराब एपहरूले तपाईंको पुष्टि बिना सन्देशहरू पठाएर तपाईंको पैसा खर्च गराउन सक्दछ।"</string> <string name="permlab_readSms" msgid="5164176626258800297">"तपाईंका पाठ सन्देशहरू (SMS वा MMS) पढ्नुहोस्"</string> <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"यस अनुप्रयोगले तपाईंको ट्याब्लेटमा भण्डारण गरिएका सबै SMS (पाठ) सन्देशहरू पढ्न सक्छ।"</string> - <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"यस अनुप्रयोगले तपाईंको Android TV यन्त्रमा भण्डार गरिएका सबै SMS.(पाठ) सन्देशहरू पढ्न सक्छ।"</string> + <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"यस अनुप्रयोगले तपाईंको Android टिभी यन्त्रमा भण्डार गरिएका सबै SMS.(पाठ) सन्देशहरू पढ्न सक्छ।"</string> <string name="permdesc_readSms" product="default" msgid="774753371111699782">"यस अनुप्रयोगले तपाईंको फोनमा भण्डारण गरिएका सबै SMS (पाठ) सन्देशहरू पढ्न सक्छ।"</string> <string name="permlab_receiveWapPush" msgid="4223747702856929056">"पाठ सन्देशहरू (WAP) प्राप्त गर्नुहोस्"</string> <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"WAP सन्देशहरू प्राप्त गर्न र प्रशोधन गर्न एपलाई अनुमति दिन्छ। यो अनुमतिमा मोनिटर गर्ने वा तपाईँलाई पठाइएका सन्देशहरू तपाईँलाई नदेखाई मेट्ने क्षमता समावेश हुन्छ।"</string> @@ -379,7 +379,7 @@ <string name="permdesc_useDataInBackground" msgid="1230753883865891987">"यो अनुप्रयोगले पृष्ठभूमिमा डेटा प्रयोग गर्नसक्छ। यसले गर्दा धेरै डेटा प्रयोग हुनसक्छ।"</string> <string name="permlab_persistentActivity" msgid="464970041740567970">"एपहरू जहिले पनि चल्ने बनाउनुहोस्"</string> <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"यसको आफ्नै मेमोरीमा दृढ भएकोको अंश बनाउनको लागि एपलाई अनुमति दिन्छ। ट्याब्लेटलाई ढिलो गराउँदै गरेका अन्य अनुप्रयोगहरूलाई सीमित मात्रामा यसले मेमोरी उपलब्ध गराउन सक्छ।"</string> - <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"एपलाई आफ्ना केही अंशहरू मेमोरीमा स्थायी रूपमा राख्ने अनुमति दिन्छ। यसले गर्दा अन्य अनुप्रयोगहरूका लागि मेमोरीको अभाव हुन सक्ने भएकाले तपाईंको Android TV यन्त्र सुस्त हुन सक्छ।"</string> + <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"एपलाई आफ्ना केही अंशहरू मेमोरीमा स्थायी रूपमा राख्ने अनुमति दिन्छ। यसले गर्दा अन्य अनुप्रयोगहरूका लागि मेमोरीको अभाव हुन सक्ने भएकाले तपाईंको Android टिभी यन्त्र सुस्त हुन सक्छ।"</string> <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"एपलाई मेमोरीमा आफैंको निरन्तरको अंश बनाउन अनुमति दिन्छ। यसले फोनलाई ढिला बनाएर अन्य एपहरूमा मेमोरी SIMित गर्न सक्दछन्।"</string> <string name="permlab_foregroundService" msgid="1768855976818467491">"अग्रभूमिको सेवा सञ्चालन गर्नुहोस्"</string> <string name="permdesc_foregroundService" msgid="8720071450020922795">"एपलाई अग्रभूमिका सेवाहरू प्रयोग गर्ने अनुमति दिन्छ।"</string> @@ -389,35 +389,35 @@ <string name="permdesc_writeSettings" msgid="8293047411196067188">"प्रणालीका सेटिङ डेटालाई परिवर्तन गर्नको लागि एपलाई अनुमति दिन्छ। खराब एपहरूले सायद तपाईँको प्रणालीको कन्फिगरेसनलाई क्षति पुर्याउन सक्छन्।"</string> <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"स्टार्टअपमा चलाउनुहोस्"</string> <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"आनुप्रयोगलाई प्रणाली बुट प्रक्रिया पूर्ण हुने बितिकै आफैलाई सुरु गर्ने अनुमति दिन्छ। यसले ट्याब्लेट सुरु गर्नमा ढिला गर्न सक्दछ र एपलाई समग्रमा ट्याब्लेट सधैँ चालु गरेर ढिला बनाउँदछ।"</string> - <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"एपलाई प्रणाली बुट हुने बित्तिकै स्वत: सुरु हुने अनुमति दिन्छ। यसो गर्दा एप सधैँ चलिरहने भएकाले तपाईंको Android TV यन्त्र सुरु हुन बढी समय लाग्नुका साथै यन्त्रको समग्र कार्यसम्पादन सुस्त हुन सक्छ।"</string> + <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"एपलाई प्रणाली बुट हुने बित्तिकै स्वत: सुरु हुने अनुमति दिन्छ। यसो गर्दा एप सधैँ चलिरहने भएकाले तपाईंको Android टिभी यन्त्र सुरु हुन बढी समय लाग्नुका साथै यन्त्रको समग्र कार्यसम्पादन सुस्त हुन सक्छ।"</string> <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"एपलाई प्रणाली बुट गरी सकेपछि जति सक्दो चाँडो आफैंमा सुरु गर्न अनुमति दिन्छ। यसले फोन सुरु गर्नमा ढिला गर्न सक्दछ र अनप्रयोगलाई समग्रमा फोन सधैँ चालु गरेर ढिला बनाउँदछ।"</string> <string name="permlab_broadcastSticky" msgid="4552241916400572230">"स्टिकि प्रसारण पठाउनुहोस्"</string> <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"औपचारिक प्रसारणलाई पठाउनको लागि एउटा एपलाई अनुमति दिन्छ, जुन प्रसारण समाप्त भएपछि बाँकी रहन्छ। अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग गरेको कारणले ट्याब्लेटलाई ढिलो र अस्थिर बनाउन सक्छ।"</string> - <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"एपलाई प्रसारण समाप्त भइसकेपछि पनि रहिरहने स्टिकी प्रसारणहरू पठाउने अनुमति दिन्छ। यो सुविधाको अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग हुने भएकाले तपाईंको Android TV यन्त्र सुस्त वा अस्थिर हुन सक्छ।"</string> + <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"एपलाई प्रसारण समाप्त भइसकेपछि पनि रहिरहने स्टिकी प्रसारणहरू पठाउने अनुमति दिन्छ। यो सुविधाको अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग हुने भएकाले तपाईंको Android टिभी यन्त्र सुस्त वा अस्थिर हुन सक्छ।"</string> <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"औपचारिक प्रसारणलाई पठाउनको लागि एक एपलाई अनुमति दिन्छ, जुन प्रसारण समाप्त भएपछि बाँकी रहन्छ। अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग गरेको कारणले फोनलाई ढिलो र अस्थिर बनाउन सक्छ।"</string> <string name="permlab_readContacts" msgid="8776395111787429099">"तपाईँका सम्पर्कहरू पढ्नुहोस्"</string> <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"एपलाई तपाईंको ट्याब्लेटमा भण्डार गरिएका सम्पर्क ठेगानाहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। एपहरूले सम्पर्क ठेगानाहरू बनाउने तपाईंको ट्याब्लेटमा भण्डार गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले अनुप्रयोगहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सुरक्षित गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string> - <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"एपलाई तपाईंको Android TV यन्त्रमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा पढ्न अनुमति दिन्छ। एपहरूले सम्पर्क ठेगानाहरू बनाउने तपाईंको Android TV यन्त्रमा भण्डार गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले अनुप्रयोगहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सुरक्षित गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string> + <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"एपलाई तपाईंको Android टिभी यन्त्रमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा पढ्न अनुमति दिन्छ। एपहरूले सम्पर्क ठेगानाहरू बनाउने तपाईंको Android टिभी यन्त्रमा भण्डार गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले अनुप्रयोगहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सुरक्षित गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string> <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"एपलाई तपाईंको फोनमा भण्डार गरिएका सम्पर्क ठेगानाहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। एपहरूले सम्पर्क ठेगानाहरू बनाउने तपाईंको फोनमा भण्डार गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले अनुप्रयोगहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सुरक्षित गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string> <string name="permlab_writeContacts" msgid="8919430536404830430">"तपाईँका सम्पर्कहरू परिवर्तन गर्नुहोस्"</string> <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"एपलाई तपाईंको ट्याब्लेटमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string> - <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"एपलाई तपाईंको Android TV यन्त्रमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string> + <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"एपलाई तपाईंको Android टिभी यन्त्रमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string> <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"एपलाई तपाईंको फोनमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string> <string name="permlab_readCallLog" msgid="1739990210293505948">"कल लग पढ्नुहोस्"</string> <string name="permdesc_readCallLog" msgid="8964770895425873433">"यस अनुप्रयोगले तपाईंको फोन सम्पर्कको इतिहास पढ्न सक्छ।"</string> <string name="permlab_writeCallLog" msgid="670292975137658895">"कल लग लेख्नुहोस्"</string> <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"आगमन तथा बहर्गमन डेटासहित तपाईँको ट्याब्लेटको कल लगको परिमार्जन गर्न एपलाई अनुमति दिन्छ। खराब एपहरूले यसलाई तपाईँको कल लग परिमार्जन गर्न वा मेटाउन प्रयोग गर्न सक्छन्।"</string> - <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"एपलाई तपाईंको Android TV यन्त्रको आगमन र बहिर्गमन कलसम्बन्धी डेटासहित कल लग परिमार्जन गर्ने अनुमति दिन्छ। हानिकारक एपहरूले यसलाई तपाईंको कल लग मेटाउन वा परिमार्जन गर्न प्रयोग गर्न सक्छन्।"</string> + <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"एपलाई तपाईंको Android टिभी यन्त्रको आगमन र बहिर्गमन कलसम्बन्धी डेटासहित कल लग परिमार्जन गर्ने अनुमति दिन्छ। हानिकारक एपहरूले यसलाई तपाईंको कल लग मेटाउन वा परिमार्जन गर्न प्रयोग गर्न सक्छन्।"</string> <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"एपलाई तपाईंको फोनको आउने र बाहिर जाने कलहरूको बारेको डेटा सहित कल लग परिमार्जन गर्न अनुमति दिन्छ। खराब एपहरूले यसलाई तपाईंको कल लग मेटाउन वा परिमार्जन गर्न प्रयोग गर्न सक्दछ।"</string> <string name="permlab_bodySensors" msgid="3411035315357380862">"शरीरका सेन्सरहरूमा पहुँच गराउनुहोस् (जस्तै हृदय धड्कन निगरानीहरू)"</string> <string name="permdesc_bodySensors" product="default" msgid="2365357960407973997">"तपाईँको हृदय गति जस्तो सेंसर बाट डेटा पहुँचको लागि एप अनुमति दिन्छ जसले तपाईँको भौतिक अवस्था अनुगमन गर्छ।"</string> <string name="permlab_readCalendar" msgid="6408654259475396200">"पात्रोका कार्यक्रम र विवरणहरू पढ्ने"</string> <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"यस अनुप्रयोगले तपाईंको ट्याब्लेटमा भण्डारण गरिएका पात्रो सम्बन्धी सबै कार्यक्रमहरू पढ्न र तपाईंको पात्रोको डेटा आदान प्रदान वा सुरक्षित गर्न सक्छ।"</string> - <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"यस अनुप्रयोगले तपाईंको Android TV यन्त्रमा भण्डार गरिएका पात्रोसम्बन्धी सबै कार्यक्रमहरू पढ्न र तपाईंको पात्रोको डेटा आदान प्रदान वा सुरक्षित गर्न सक्छ।"</string> + <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"यस अनुप्रयोगले तपाईंको Android टिभी यन्त्रमा भण्डार गरिएका पात्रोसम्बन्धी सबै कार्यक्रमहरू पढ्न र तपाईंको पात्रोको डेटा आदान प्रदान वा सुरक्षित गर्न सक्छ।"</string> <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"यस अनुप्रयोगले तपाईंको फोनमा भण्डारण गरिएका पात्रो सम्बन्धी सबै कार्यक्रमहरू पढ्न र तपाईंको पात्रोको डेटा आदान प्रदान वा सुरक्षित गर्न सक्छ।"</string> <string name="permlab_writeCalendar" msgid="6422137308329578076">"पात्रो घटनाहरू थप्नुहोस् वा परिमार्जन गर्नुहोस् र मालिकको ज्ञान बिना नै पाहुनाहरूलाई इमेल पठाउनुहोस्"</string> <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"यस अनुप्रयोगले तपाईंको ट्याब्लेटमा पात्रोका कार्यक्रमहरू थप्न, हटाउन वा परिवर्तन गर्न सक्छ। यस अनुप्रयोगले पात्रोका मालिकहरू मार्फत आएको जस्तो लाग्ने सन्देशहरू पठाउन वा तिनीहरूका मालिकहरूलाई सूचित नगरिकन कार्यक्रमहरू परिवर्तन गर्न सक्छ।"</string> - <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"यस अनुप्रयोगले तपाईंको Android TV यन्त्रमा पात्रोका कार्यक्रमहरू थप्न, हटाउन वा परिवर्तन गर्न सक्छ। यस अनुप्रयोगले पात्रोका मालिकहरूले पठाएको जस्तै देखिने सन्देशहरू पठाउन वा कार्यक्रमका मालिकहरूलाई सूचित नगरिकन कार्यक्रमहरू परिवर्तन गर्न सक्छ।"</string> + <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"यस अनुप्रयोगले तपाईंको Android टिभी यन्त्रमा पात्रोका कार्यक्रमहरू थप्न, हटाउन वा परिवर्तन गर्न सक्छ। यस अनुप्रयोगले पात्रोका मालिकहरूले पठाएको जस्तै देखिने सन्देशहरू पठाउन वा कार्यक्रमका मालिकहरूलाई सूचित नगरिकन कार्यक्रमहरू परिवर्तन गर्न सक्छ।"</string> <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"यस अनुप्रयोगले तपाईंको फोनमा पात्रोका कार्यक्रमहरू थप्न, हटाउन वा परिवर्तन गर्न सक्छ। यस अनुप्रयोगले पात्रोका मालिकहरू मार्फत आएको जस्तो लाग्ने सन्देशहरू पठाउन वा तिनीहरूका मालिकहरूलाई सूचित नगरिकन कार्यक्रमहरू परिवर्तन गर्न सक्छ।"</string> <string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"अधिक स्थान प्रदायक आदेशहरू पहुँच गर्नुहोस्"</string> <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"एपलाई अतिरिक्त स्थान प्रदायक आदेशहरू पहुँच गर्न अनुमति दिन्छ। यो एपलाई GPS वा अन्य स्थान स्रोतहरूको संचालन साथै हस्तक्षेप गर्न अनुमति दिन सक्छ।"</string> @@ -462,15 +462,15 @@ <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"उक्त एपलाई यस यन्त्रको फोन नम्बरहरूमाथि पहुँच राख्न दिनुहोस्।"</string> <string name="permlab_wakeLock" product="automotive" msgid="1904736682319375676">"कारको स्क्रिन सक्रिय राख्नुहोस्"</string> <string name="permlab_wakeLock" product="tablet" msgid="1527660973931694000">"ट्याब्लेटलाई निन्द्रामा जानबाट रोक्नुहोस्"</string> - <string name="permlab_wakeLock" product="tv" msgid="2856941418123343518">"आफ्नो Android TV यन्त्रलाई शयन अवस्थामा जान नदिनुहोस्"</string> + <string name="permlab_wakeLock" product="tv" msgid="2856941418123343518">"आफ्नो Android टिभी यन्त्रलाई शयन अवस्थामा जान नदिनुहोस्"</string> <string name="permlab_wakeLock" product="default" msgid="569409726861695115">"फोनलाई निदाउनबाट रोक्नुहोस्"</string> <string name="permdesc_wakeLock" product="automotive" msgid="5995045369683254571">"यो अनुमतिले यस एपलाई कारको स्क्रिन सक्रिय राख्न दिन्छ।"</string> <string name="permdesc_wakeLock" product="tablet" msgid="2441742939101526277">"ट्याब्लेटलाई निस्क्रिय हुनबाट रोक्नको लागि एपलाई अनुमति दिन्छ।"</string> - <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"एपलाई तपाईंको Android TV यन्त्रलाई शयन अवस्थामा जानबाट रोक्ने अनुमति दिन्छ।"</string> + <string name="permdesc_wakeLock" product="tv" msgid="2329298966735118796">"एपलाई तपाईंको Android टिभी यन्त्रलाई शयन अवस्थामा जानबाट रोक्ने अनुमति दिन्छ।"</string> <string name="permdesc_wakeLock" product="default" msgid="3689523792074007163">"फोनलाई निस्क्रिय हुनबाट रोक्नको लागि एपलाई अनुमति दिन्छ।"</string> <string name="permlab_transmitIr" msgid="8077196086358004010">"infrared ट्रान्समिट गर्नुहोस्"</string> <string name="permdesc_transmitIr" product="tablet" msgid="5884738958581810253">"ट्याबलेटको infrared transmitter प्रयोगको लागि एप अनुमति दिन्छ।"</string> - <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"एपलाई तपाईंको Android TV यन्त्रको इन्फ्रारेड ट्रान्समिटर प्रयोग गर्ने अनुमति दिन्छ।"</string> + <string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"एपलाई तपाईंको Android टिभी यन्त्रको इन्फ्रारेड ट्रान्समिटर प्रयोग गर्ने अनुमति दिन्छ।"</string> <string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"फोनको infrared transmitter प्रयोगको लागि एप अनुमति दिन्छ।"</string> <string name="permlab_setWallpaper" msgid="6959514622698794511">"वालपेपर सेट गर्नुहोस्"</string> <string name="permdesc_setWallpaper" msgid="2973996714129021397">"एपलाई प्रणाली वालपेपर सेट गर्न अनुमति दिन्छ।"</string> @@ -478,11 +478,11 @@ <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"प्रणाली वालपेपरको आकार सङ्केतहरू मिलाउन एपलाई अनुमति दिन्छ।"</string> <string name="permlab_setTimeZone" msgid="7922618798611542432">"समय क्षेत्र सेट गर्नुहोस्"</string> <string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"एपलाई ट्याब्लेटको समय क्षेत्र परिवर्तन गर्न अनुमति दिन्छ।"</string> - <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"एपलाई तपाईंको Android TV यन्त्रको समय क्षेत्र परिवर्तन गर्ने अनुमति दिन्छ।"</string> + <string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"एपलाई तपाईंको Android टिभी यन्त्रको समय क्षेत्र परिवर्तन गर्ने अनुमति दिन्छ।"</string> <string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"एपलाई फोनको समय क्षेत्र परिवर्तन गर्न अनुमति दिन्छ।"</string> <string name="permlab_getAccounts" msgid="5304317160463582791">"उपकरणमा खाताहरू भेट्टाउनुहोस्"</string> <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"एपलाई ट्याब्लेटद्वारा ज्ञात खाताहरूको सूची पाउन अनुमति दिन्छ। यसले अनुप्रयोगद्वारा तपाईंले स्थापित गर्नुभएको कुनै पनि खाताहरू समावेश गर्न सक्दछ।"</string> - <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"एपलाई तपाईंको Android TV यन्त्रले चिनेका खाताहरूको सूची प्राप्त गर्ने अनुमति दिन्छ। उक्त सूचीमा तपाईंले स्थापना गर्नुभएका एपहरूले बनाएका कुनै पनि खाताहरू पर्न सक्छन्।"</string> + <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"एपलाई तपाईंको Android टिभी यन्त्रले चिनेका खाताहरूको सूची प्राप्त गर्ने अनुमति दिन्छ। उक्त सूचीमा तपाईंले स्थापना गर्नुभएका एपहरूले बनाएका कुनै पनि खाताहरू पर्न सक्छन्।"</string> <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"फोनलाई थाहा भएका खाताहरूको सूची प्राप्त गर्न एपलाई अनुमति दिन्छ। यसले तपाईँले स्थापना गर्नु भएका अनुप्रयोगहरूबाट सृजित कुनै खाताहरू समावेश हुन सक्छ।"</string> <string name="permlab_accessNetworkState" msgid="2349126720783633918">"नेटवर्क जडानहरू हेर्नहोस्"</string> <string name="permdesc_accessNetworkState" msgid="4394564702881662849">"एपलाई नेटवर्क जडानहरू जस्तै कुन नेटवर्कहरू अवस्थित हुन्छन् र जडित छन् जसले हेर्नलाई अनुमति दिन्छ।"</string> @@ -498,21 +498,21 @@ <string name="permdesc_changeWifiState" msgid="7170350070554505384">"एपलाई Wi-Fi पहुँच बिन्दुबाट जडान गर्न र विच्छेदन गर्न र Wi-Fi नेटवर्कहरूको लागि उपकरण कन्फिगरेसनमा परिवर्तनहरू गर्न अनुमति दिन्छ।"</string> <string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"Wi-Fi Multicast स्विकृतिलाई अनुमति दिनुहोस्"</string> <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"एपलाई मल्टिकाष्ट ठेगानाहरू प्रयोग गरेर Wi-Fi नेटवर्कमा पठाइएको प्याकेटहरू प्राप्त गर्न अनुमति दिन्छ, केवल तपाईंको ट्याब्लेट मात्र होइन। यसले गैर-मल्टिकाष्ट मोड भन्दा बढी उर्जा प्रयोग गर्दछ।"</string> - <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"एपलाई मल्टिकास्ट ठेगानाहरू प्रयोग गरी तपाईंको Android TV यन्त्रमा मात्र नभई कुनै Wi-Fi नेटवर्कमा जोडिएका सबै यन्त्रहरूमा पठाइएका प्याकेटहरू प्राप्त गर्ने अनुमति दिन्छ। यसले गैर मल्टिकास्ट मोडभन्दा बढी पावर खपत गर्छ।"</string> + <string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"एपलाई मल्टिकास्ट ठेगानाहरू प्रयोग गरी तपाईंको Android टिभी यन्त्रमा मात्र नभई कुनै Wi-Fi नेटवर्कमा जोडिएका सबै यन्त्रहरूमा पठाइएका प्याकेटहरू प्राप्त गर्ने अनुमति दिन्छ। यसले गैर मल्टिकास्ट मोडभन्दा बढी पावर खपत गर्छ।"</string> <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"तपाईँको फोन मात्र होइन, मल्टिकास्ट ठेगानाहरूको प्रयोग गरे Wi-Fi नेटवर्कका सबै उपकरणहरूमा पठाइएका प्याकेटहरू प्राप्त गर्न एपलाई अनुमति दिन्छ। यसले गैर-मल्टिकास्ट मोडभन्दा बढी उर्जा प्रयोग गर्छ।"</string> <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"ब्लुटुथ सेटिङहरूमा पहुँच गर्नुहोस्"</string> <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"स्थानीय ब्लुटुथ ट्याब्लेटलाई कन्फिगर गर्नको लागि र टाढाका उपकरणहरूलाई पत्ता लगाउन र जोड्नको लागि एपलाई अनुमति दिन्छ।"</string> - <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"एपलाई तपाईंको Android TV यन्त्रको ब्लुटुथ कन्फिगर गर्ने तथा टाढा रहेका यन्त्रहरू पत्ता लगाई ती यन्त्रहरूसँग जोडा बनाउने अनुमति दिन्छ।"</string> + <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"एपलाई तपाईंको Android टिभी यन्त्रको ब्लुटुथ कन्फिगर गर्ने तथा टाढा रहेका यन्त्रहरू पत्ता लगाई ती यन्त्रहरूसँग जोडा बनाउने अनुमति दिन्छ।"</string> <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"एपलाई स्थानीय ब्लुटुथ फोन कन्फिगर गर्न र टाढाका उपकरणहरूसँग खोज गर्न र जोडी गर्न अनुमति दिन्छ।"</string> <string name="permlab_accessWimaxState" msgid="7029563339012437434">"WiMAXसँग जोड्नुहोस् वा छुटाउनुहोस्"</string> <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"एपलाई वाइम्याक्स सक्षम छ कि छैन र जडान भएको कुनै पनि वाइम्याक्स नेटवर्कहरूको बारेमा जानकारी निर्धारिण गर्न अनुमति दिन्छ।"</string> <string name="permlab_changeWimaxState" msgid="6223305780806267462">"वाइम्याक्स अवस्था परिवर्तन गर्नुहोस्"</string> <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"एपलाई वाइम्याक्स नेटवर्कहरूबाट ट्याब्लेट जडान गर्न र ट्याब्लेट विच्छेदन गर्न अनुमति दिन्छ।"</string> - <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"एपलाई तपाईंको Android TV यन्त्र WiMAX नेटवर्कहरूमा जोड्ने वा ती नेटवर्कहरूबाट विच्छेद गर्ने अनुमति दिन्छ।"</string> + <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"एपलाई तपाईंको Android टिभी यन्त्र WiMAX नेटवर्कहरूमा जोड्ने वा ती नेटवर्कहरूबाट विच्छेद गर्ने अनुमति दिन्छ।"</string> <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"वाइम्याक्स नेटवर्कहरूसँग फोन जोड्न र छुटाउन एपलाई अनुमति दिन्छ।"</string> <string name="permlab_bluetooth" msgid="586333280736937209">"ब्लुटुथ उपकरणहरूसँग जोडी मिलाउनुहोस्"</string> <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ट्याब्लेटमा ब्लुटुथको कन्फिगुरेसनलाई हेर्न र बनाउन र जोडी उपकरणहरूसँग जडानहरूलाई स्वीकार गर्न एपलाई अनुमति दिन्छ।"</string> - <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"एपलाई तपाईंको Android TV यन्त्रको ब्लुटुथको कन्फिगुरेसन हेर्ने तथा जोडा बनाइएका यन्त्रहरूसँग जोडिने वा ती यन्त्रहरूले पठाएका जोडिने अनुरोध स्वीकार्ने अनुमति दिन्छ।"</string> + <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"एपलाई तपाईंको Android टिभी यन्त्रको ब्लुटुथको कन्फिगुरेसन हेर्ने तथा जोडा बनाइएका यन्त्रहरूसँग जोडिने वा ती यन्त्रहरूले पठाएका जोडिने अनुरोध स्वीकार्ने अनुमति दिन्छ।"</string> <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"एपलाई फोनमा ब्लुटुथको कन्फिगरेसन हेर्न र जोडी भएका उपकरणहरूसँग जडानहरू बनाउन र स्वीकार गर्न अनुमति दिन्छ।"</string> <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"NFC भुक्तानी सेवासम्बन्धी रुचाइएको जानकारी"</string> <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"यसले एपलाई दर्ता गरिएका सहायता तथा मार्गको गन्तव्य जस्ता रुचाइएका NFC भुक्तानी सेवासम्बन्धी जानकारी प्राप्त गर्न दिन्छ।"</string> @@ -673,10 +673,10 @@ <string name="policydesc_limitPassword" msgid="4105491021115793793">"स्क्रिन लक पासवर्ड र PIN हरूमा अनुमति दिइएको लम्बाइ र वर्णहरूको नियन्त्रण गर्नुहोस्।"</string> <string name="policylab_watchLogin" msgid="7599669460083719504">"मनिटरको स्क्रिन अनलक गर्ने प्रयासहरू"</string> <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"स्क्रिन अनलक गर्दा गलत पासवर्ड टाइप भएको संख्या निरीक्षण गर्नुहोस् र यदि निकै धेरै गलत पासवर्डहरू टाइप भएका छन भने ट्याब्लेट लक गर्नुहोस् वा ट्याब्लेटका सबै डेटा मेट्नुहोस्।"</string> - <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"स्क्रिन अनलक गर्दा गलत पासवर्ड टाइप गरेको सङ्ख्या निरीक्षण गर्नुहोस्, र धेरै पटक गलत पासवर्डहरू टाइप गरिएको खण्डमा आफ्नो Android TV यन्त्र लक गर्नुहोस् वा यन्त्रमा भएको सम्पूर्ण डेटा मेटाउनुहोस्।"</string> + <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"स्क्रिन अनलक गर्दा गलत पासवर्ड टाइप गरेको सङ्ख्या निरीक्षण गर्नुहोस्, र धेरै पटक गलत पासवर्डहरू टाइप गरिएको खण्डमा आफ्नो Android टिभी यन्त्र लक गर्नुहोस् वा यन्त्रमा भएको सम्पूर्ण डेटा मेटाउनुहोस्।"</string> <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"स्क्रिनअनलक गर्दा गलत पासवर्ड टाइप भएको संख्या निरीक्षण गर्नुहोस् र यदि निकै धेरै गलत पासवर्डहरू टाइप भएका छन भने फोन लक गर्नुहोस् वा फोनका सबै डेटा मेट्नुहोस्।"</string> <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"स्क्रिन अनलक गर्दा गलत पासवर्ड टाइप संख्या अनुगमन गर्नुहोस्, र यदि निकै धेरै गलत पासवर्डहरू टाइप गरिएमा ट्याब्लेट लक गर्नुहोस् वा प्रयोगकर्ताको डेटा मेटाउनुहोस्।"</string> - <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"स्क्रिन अनलक गर्दा गलत पासवर्ड टाइप गरेको सङ्ख्या निरीक्षण गर्नुहोस्, र धेरै पटक गलत पासवर्डहरू टाइप गरिएको खण्डमा आफ्नो Android TV यन्त्र लक गर्नुहोस् वा यो प्रयोगकर्ताको सम्पूर्ण डेटा मेटाउनुहोस्।"</string> + <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"स्क्रिन अनलक गर्दा गलत पासवर्ड टाइप गरेको सङ्ख्या निरीक्षण गर्नुहोस्, र धेरै पटक गलत पासवर्डहरू टाइप गरिएको खण्डमा आफ्नो Android टिभी यन्त्र लक गर्नुहोस् वा यो प्रयोगकर्ताको सम्पूर्ण डेटा मेटाउनुहोस्।"</string> <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"स्क्रिन अनलक गर्दा गलत पासवर्ड टाइप संख्या अनुगमन गर्नुहोस्, र यदि निकै धेरै गलत पासवर्डहरू टाइप गरिएमा फोन लक गर्नुहोस् वा प्रयोगकर्ताको डेटा मेटाउनुहोस्।"</string> <string name="policylab_resetPassword" msgid="214556238645096520">"स्क्रिन लक परिवर्तन गर्ने"</string> <string name="policydesc_resetPassword" msgid="4626419138439341851">"स्क्रिन लक परिवर्तन गर्नुहोस्।"</string> @@ -684,11 +684,11 @@ <string name="policydesc_forceLock" msgid="1008844760853899693">"कसरी र कहिले स्क्रिन लक गर्ने नियन्त्रण गर्नुहोस्।"</string> <string name="policylab_wipeData" msgid="1359485247727537311">"सबै डेटा मेट्नुहोस्"</string> <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"एउटा फ्याक्ट्रि डेटा रिसेट गरेर चेतावनी नआउँदै ट्याबल्टको डेटा मेट्नुहोस्।"</string> - <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"फ्याक्ट्री डेटा रिसेट गरेर चेतावनी नदिइकन आफ्नो Android TV यन्त्रको डेटा मेटाउनुहोस्।"</string> + <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"फ्याक्ट्री डेटा रिसेट गरेर चेतावनी नदिइकन आफ्नो Android टिभी यन्त्रको डेटा मेटाउनुहोस्।"</string> <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"एउटा फ्याक्ट्रि डेटा रिसेट गरेर चेतावनी नदिइकन फोनको डेटा मेट्न।"</string> <string name="policylab_wipeData_secondaryUser" msgid="413813645323433166">"प्रयोगकर्ता डेटा मेट्नुहोस्"</string> <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"चेतावनी बिना यो ट्याब्लेटमा यस प्रयोगकर्ताको डेटा मेट्नुहोस्।"</string> - <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"यो Android TV यन्त्रमा भएको यस प्रयोगकर्ताको डेटा चेतावनी नदिइकन मेटाउनुहोस्।"</string> + <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"यो Android टिभी यन्त्रमा भएको यस प्रयोगकर्ताको डेटा चेतावनी नदिइकन मेटाउनुहोस्।"</string> <string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"चेतावनी बिना यो फोनमा यस प्रयोगकर्ताको डेटा मेट्नुहोस्।"</string> <string name="policylab_setGlobalProxy" msgid="215332221188670221">"उपकरण विश्वव्यापी प्रोक्सी मिलाउनुहोस्"</string> <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"नीति सक्षम हुँदा प्रयोग गरिनको लागि यन्त्र ग्लोवल प्रोक्सी सेट गर्नुहोस्। केवल यन्त्र मालिकले ग्लोवल प्रोक्सी सेट गर्न सक्नुहुन्छ।"</string> @@ -838,7 +838,7 @@ <string name="faceunlock_multiple_failures" msgid="681991538434031708">"अत्यधिक मोहडा खोल्ने प्रयासहरू बढी भए।"</string> <string name="lockscreen_missing_sim_message_short" msgid="1248431165144893792">"SIM कार्ड छैन"</string> <string name="lockscreen_missing_sim_message" product="tablet" msgid="8596805728510570760">"ट्याब्लेटमा SIM कार्ड छैन।"</string> - <string name="lockscreen_missing_sim_message" product="tv" msgid="2582768023352171073">"तपाईंको Android TV यन्त्रमा SIM कार्ड छैन।"</string> + <string name="lockscreen_missing_sim_message" product="tv" msgid="2582768023352171073">"तपाईंको Android टिभी यन्त्रमा SIM कार्ड छैन।"</string> <string name="lockscreen_missing_sim_message" product="default" msgid="1408695081255172556">"फोनमा SIM कार्ड छैन।"</string> <string name="lockscreen_missing_sim_instructions" msgid="8473601862688263903">"SIM कार्ड घुसाउनुहोस्"</string> <string name="lockscreen_missing_sim_instructions_long" msgid="3664999892038416334">"SIM कार्ड छैन वा पढ्न मिल्दैन। SIM कार्ड हाल्नुहोस्।"</string> @@ -861,13 +861,13 @@ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"तपाईंले गलत तरिकाले आफ्नो पासवर्ड <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक टाइप गर्नुभयो। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> सेकेन्डमा फेरि प्रयास गर्नुहोस्।"</string> <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"तपाईँले गलत तरिकाले तपाईँको PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक टाइप गर्नु भएको छ। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> सेकेन्डमा फेरि प्रयास गर्नुहोस्।"</string> <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"तपाईँले तपाईँको अनलक ढाँचा गलत तरिकाले <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक खिच्नु भएको छ। पछि <xliff:g id="NUMBER_1">%2$d</xliff:g> थप असफल कोसिसहरू, तपाईँको Google साइन इन प्रयोग गरी तपाईँको ट्याब्लेट अनलक गर्न भनिने छ।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकेन्डमा फरि प्रयास गर्नुहोस्।"</string> - <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"तपाईंले आफ्नो अनलक शैली <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले कोर्नुभएको छ। थप <xliff:g id="NUMBER_1">%2$d</xliff:g> प्रयासहरू असफल भएपछि तपाईंलाई आफ्नो Google खाता मार्फत साइन इन गरेर आफ्नो Android TV यन्त्र अनलक गर्न अनुरोध गरिनेछ।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकेन्डपछि फेरि प्रयास गर्नुहोस्।"</string> + <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"तपाईंले आफ्नो अनलक शैली <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले कोर्नुभएको छ। थप <xliff:g id="NUMBER_1">%2$d</xliff:g> प्रयासहरू असफल भएपछि तपाईंलाई आफ्नो Google खाता मार्फत साइन इन गरेर आफ्नो Android टिभी यन्त्र अनलक गर्न अनुरोध गरिनेछ।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकेन्डपछि फेरि प्रयास गर्नुहोस्।"</string> <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"तपाईँले <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले तपाईँको अनलक ढाँचालाई कोर्नु भएको छ। पछि <xliff:g id="NUMBER_1">%2$d</xliff:g> अरू धेरै असफल कोसिसहरूपछि, तपाईँलाई तपाईँको फोन Google साइन इन प्रयोग गरेर अनलक गर्नको लागि सोधिने छ। \n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकेन्डमा पुनः प्रयास गर्नुहोस्।"</string> <string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"तपाईँले <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक ट्याब्लेटलाई अनलक गर्नको लागि गलत तरिकाले कोशिस गर्नुभएको छ। <xliff:g id="NUMBER_1">%2$d</xliff:g> अरू धेरै असफल कोसिसहरूपछि, ट्याब्लेट फ्याट्रि पूर्वनिर्धारितमा रिसेट हुने छ र सबै प्रयोगकर्ता डेटा हराउने छन्।"</string> - <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"तपाईंले आफ्नो Android TV यन्त्र <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले अनलक गर्ने प्रयास गर्नुभएको छ। थप <xliff:g id="NUMBER_1">%2$d</xliff:g> प्रयासहरू असफल भएपछि तपाईंको Android TV यन्त्रलाई रिसेट गरेर पूर्वनिर्धारित फ्याक्ट्री सेटिङ लागू गरिने छ र प्रयोगकर्ताको सम्पूर्ण डेटा गुम्ने छ।"</string> + <string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"तपाईंले आफ्नो Android टिभी यन्त्र <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले अनलक गर्ने प्रयास गर्नुभएको छ। थप <xliff:g id="NUMBER_1">%2$d</xliff:g> प्रयासहरू असफल भएपछि तपाईंको Android टिभी यन्त्रलाई रिसेट गरेर पूर्वनिर्धारित फ्याक्ट्री सेटिङ लागू गरिने छ र प्रयोगकर्ताको सम्पूर्ण डेटा गुम्ने छ।"</string> <string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"तपाईंले गलत तरिकाले <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक फोन अनलक गर्ने प्रयत्न गर्नुभयो। <xliff:g id="NUMBER_1">%2$d</xliff:g> बढी असफल प्रयत्नहरू पछि, फोन फ्याक्ट्रि पूर्वनिर्धारितमा रिसेट हुने छ र सबै प्रयोगकर्ता डेटा हराउने छन्।"</string> <string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"तपाईँले ट्यब्लेटलाई अनलक गर्न गलत तरिकाले <xliff:g id="NUMBER">%d</xliff:g> पटक प्रयास गर्नु भएको छ। अब ट्याब्लेटलाई पूर्वनिर्धारित कार्यशालामा रिसेट गरिने छ।"</string> - <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"तपाईंले आफ्नो Android TV यन्त्र <xliff:g id="NUMBER">%d</xliff:g> पटक गलत तरिकाले अनलक गर्ने प्रयास गर्नुभएको छ। अब तपाईंको Android TV यन्त्रलाई रिसेट गरेर पूर्वनिर्धारित फ्याक्ट्री सेटिङ लागू गरिनेछ।"</string> + <string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"तपाईंले आफ्नो Android टिभी यन्त्र <xliff:g id="NUMBER">%d</xliff:g> पटक गलत तरिकाले अनलक गर्ने प्रयास गर्नुभएको छ। अब तपाईंको Android टिभी यन्त्रलाई रिसेट गरेर पूर्वनिर्धारित फ्याक्ट्री सेटिङ लागू गरिनेछ।"</string> <string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"तपाईंले गलत तरिकाले फोन <xliff:g id="NUMBER">%d</xliff:g> पटक अनलक गर्ने प्रयत्न गर्नुभयो। अब फोन फ्याक्ट्रि पूर्वनिर्धारितमा रिसेट हुने छ।"</string> <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"<xliff:g id="NUMBER">%d</xliff:g> सेकेन्डमा फेरि प्रयास गर्नुहोस्।"</string> <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"ढाँचा बिर्सनु भयो?"</string> @@ -954,7 +954,7 @@ <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"ब्राउजरले भ्रमण गरेको सबै URL हरूको इतिहास र ब्राउजरका सबै बुकमार्कहरू पढ्नको लागि एपलाई अनुमति दिन्छ। नोट: यो अनुमतिलाई तेस्रो पक्ष ब्राउजरहरूद्वारा वा वेब ब्राउज गर्ने क्षमताद्वारा बलपूर्वक गराउन सकिँदैन।"</string> <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"वेब बुकमार्कहरू र इतिहास लेख्नुहोस्"</string> <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"एपलाई तपाईंको ट्याब्लेटमा भण्डार गरिएको ब्राउजरको इतिहास वा बुकमार्कहरू परिमार्जन गर्न अनुमति दिन्छ। यसले एपलाई ब्राजर डेटा मेटाउन वा परिमार्जन गर्न अनुमति दिन सक्दछ। टिप्पणी: यो अनुमति वेब ब्राउज गर्ने क्षमताहरूको साथ तेस्रो-पार्टी ब्राउजर वा अन्य अनुप्रयोगहरूद्वारा लागू गरिएको होइन।"</string> - <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"एपलाई तपाईंको Android TV यन्त्रमा भण्डार गरिएका ब्राउजरको इतिहास र पुस्तक चिन्हहरू परिमार्जन गर्ने अनुमति दिन्छ। यसले एपलाई ब्राउजरको डेटा मेटाउने वा परिमार्जन गर्ने अनुमति दिन सक्छ। ध्यान दिनुहोस्: तेस्रो पक्षीय ब्राउजर वा वेब ब्राउज गर्ने सुविधा प्रदान गर्ने अन्य एपहरूले यो अनुमति लागू गर्न सक्दैनन्।"</string> + <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"एपलाई तपाईंको Android टिभी यन्त्रमा भण्डार गरिएका ब्राउजरको इतिहास र पुस्तक चिन्हहरू परिमार्जन गर्ने अनुमति दिन्छ। यसले एपलाई ब्राउजरको डेटा मेटाउने वा परिमार्जन गर्ने अनुमति दिन सक्छ। ध्यान दिनुहोस्: तेस्रो पक्षीय ब्राउजर वा वेब ब्राउज गर्ने सुविधा प्रदान गर्ने अन्य एपहरूले यो अनुमति लागू गर्न सक्दैनन्।"</string> <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"तपाईँको फोनमा भण्डारण भएको ब्राउजरको इतिहास वा बुकमार्कहरू परिवर्तन गर्नको लागि एपलाई अनुमति दिन्छ। यसले सायद ब्राउजर डेटालाई मेट्न वा परिवर्तन गर्नको लागि एपलाई अनुमति दिन्छ। नोट: वेब ब्राउज गर्ने क्षमतासहितका अन्य एपहरू वा तेस्रो- पक्ष ब्राउजरद्वारा सायद यस अनुमतिलाई लागु गर्न सकिंदैन।"</string> <string name="permlab_setAlarm" msgid="1158001610254173567">"एउटा आलर्म सेट गर्नुहोस्"</string> <string name="permdesc_setAlarm" msgid="2185033720060109640">"स्थापना गरिएको सङ्केत घडी अनुप्रयोगमा सङ्केत समय मिलाउन एपलाई अनुमति दिन्छ। केही सङ्केत घडी एपहरूले यो सुविधा कार्यान्वयन नगर्न सक्छन्।"</string> @@ -1555,7 +1555,7 @@ <string name="activity_resolver_use_once" msgid="948462794469672658">"एक पटक मात्र"</string> <string name="activity_resolver_work_profiles_support" msgid="4071345609235361269">"%1$s कार्य प्रोफाइल समर्थन गर्दैन"</string> <string name="default_audio_route_name" product="tablet" msgid="367936735632195517">"ट्याब्लेट"</string> - <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"TV"</string> + <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"टिभी"</string> <string name="default_audio_route_name" product="default" msgid="9213546147739983977">"फोन"</string> <string name="default_audio_route_name_dock_speakers" msgid="1551166029093995289">"डक स्पिकरहरू"</string> <string name="default_audio_route_name_hdmi" msgid="5474470558160717850">"HDMI"</string> @@ -1613,13 +1613,13 @@ <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="3328686432962224215">"तपाईँले तपाईँक पासवर्ड <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत टाइप गर्नुभएको छ। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकेन्डमा फेरि प्रयास गर्नुहोस्।"</string> <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="7357404233979139075">"तपाईँले तपाईँको अनलक ढाँचा गलत तरिकाले <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक खिच्नु भएको छ। \n\n <xliff:g id="NUMBER_1">%2$d</xliff:g> सेकेन्डमा फेरि कोसिस गर्नुहोस्।"</string> <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="3479940221343361587">"तपाईँले ट्याब्लेटलाई अनलक गर्न गलत तरिकाले <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक कोसिस गर्नु भएको छ। <xliff:g id="NUMBER_1">%2$d</xliff:g> पछि थप असफल प्रयासहरू, ट्याब्लेट पूर्वनिर्धारित कार्यशालामा रिसेट गरिने छ र सबै प्रयोग डेटा हराउने छ।"</string> - <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"तपाईंले आफ्नो Android TV यन्त्र <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले अनलक गर्ने प्रयास गर्नुभएको छ। थप <xliff:g id="NUMBER_1">%2$d</xliff:g> प्रयासहरू असफल भएपछि तपाईंको Android TV यन्त्रलाई रिसेट गरेर पूर्वनिर्धारित फ्याक्ट्री सेटिङ लागू गरिने छ र प्रयोगकर्ताको सम्पूर्ण डेटा गुम्ने छ।"</string> + <string name="kg_failed_attempts_almost_at_wipe" product="tv" msgid="9064457748587850217">"तपाईंले आफ्नो Android टिभी यन्त्र <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले अनलक गर्ने प्रयास गर्नुभएको छ। थप <xliff:g id="NUMBER_1">%2$d</xliff:g> प्रयासहरू असफल भएपछि तपाईंको Android टिभी यन्त्रलाई रिसेट गरेर पूर्वनिर्धारित फ्याक्ट्री सेटिङ लागू गरिने छ र प्रयोगकर्ताको सम्पूर्ण डेटा गुम्ने छ।"</string> <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="5955398963754432548">"तपाईँले गलतसँग फोनलाई अनलक गर्न <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक कोसिस गर्नु भयो। <xliff:g id="NUMBER_1">%2$d</xliff:g> पछि थप असफल कोसिसहरू, फोनलाई पूर्वनिर्धारित कार्यशालामा रिसेट गरिने छ र सबै प्रयोग डेटा हराउने छ।"</string> <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="2299099385175083308">"तपाईँले ट्यब्लेटलाई अनलक गर्न गलत तरिकाले <xliff:g id="NUMBER">%d</xliff:g> पटक प्रयास गर्नु भएको छ। अब ट्याब्लेटलाई पूर्वनिर्धारित कार्यशालामा रिसेट गरिने छ।"</string> - <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"तपाईंले आफ्नो Android TV यन्त्र <xliff:g id="NUMBER">%d</xliff:g> पटक गलत तरिकाले अनलक गर्ने प्रयास गर्नुभएको छ। अब तपाईंको Android TV यन्त्रलाई रिसेट गरेर पूर्वनिर्धारित फ्याक्ट्री सेटिङ लागू गरिनेछ।"</string> + <string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"तपाईंले आफ्नो Android टिभी यन्त्र <xliff:g id="NUMBER">%d</xliff:g> पटक गलत तरिकाले अनलक गर्ने प्रयास गर्नुभएको छ। अब तपाईंको Android टिभी यन्त्रलाई रिसेट गरेर पूर्वनिर्धारित फ्याक्ट्री सेटिङ लागू गरिनेछ।"</string> <string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"तपाईंले गलत तरिकाले फोन <xliff:g id="NUMBER">%d</xliff:g> पटक अनलक गर्ने प्रयत्न गर्नुभयो। अब फोन फ्याक्ट्रि पूर्वनिर्धारितमा रिसेट हुने छ।"</string> <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"तपाईंले गलत तरिकाले आफ्नो अनलक ढाँचा <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक कोर्नुभयो। <xliff:g id="NUMBER_1">%2$d</xliff:g> विफल प्रयत्नहरू पछि, तपाईंलाई आफ्नो ट्याब्लेट इमेल खाता प्रयोग गरेर अनलक गर्न सोधिने छ।\n\n फेरि प्रयास गर्नुहोस् <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकेन्डहरूमा।"</string> - <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"तपाईंले आफ्नो अनलक शैली <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले कोर्नुभएको छ। थप <xliff:g id="NUMBER_1">%2$d</xliff:g> प्रयासहरू असफल भएपछि तपाईंलाई आफ्नो इमेल खाता प्रयोग गरेर आफ्नो Android TV यन्त्र अनलक गर्न अनुरोध गरिनेछ।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकेन्डपछि फेरि प्रयास गर्नुहोस्।"</string> + <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"तपाईंले आफ्नो अनलक शैली <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक गलत तरिकाले कोर्नुभएको छ। थप <xliff:g id="NUMBER_1">%2$d</xliff:g> प्रयासहरू असफल भएपछि तपाईंलाई आफ्नो इमेल खाता प्रयोग गरेर आफ्नो Android टिभी यन्त्र अनलक गर्न अनुरोध गरिनेछ।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकेन्डपछि फेरि प्रयास गर्नुहोस्।"</string> <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"तपाईँले आफ्नो अनलक ढाँचा गलत रूपमा <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक तान्नु भएको छ। <xliff:g id="NUMBER_1">%2$d</xliff:g> धेरै असफल प्रयासहरूपछि, तपाईँलाई एउटा इमेल खाताको प्रयोग गरेर तपाईँको फोन अनलक गर्न सोधिने छ।\n\n फेरि <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकेन्डमा प्रयास गर्नुहोस्।"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"हटाउनुहोस्"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 269755a8cf76..b4b1523913fe 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1665,7 +1665,7 @@ <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Одбиј"</string> <string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Додирните неку функцију да бисте почели да је користите:"</string> <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Одаберите функције које ћете користити са дугметом Приступачност"</string> - <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Одаберите функције које ћете користити са тастером јачине звука као пречицом"</string> + <string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Одаберите функције за пречицу тастером јачине звука"</string> <string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"Услуга <xliff:g id="SERVICE_NAME">%s</xliff:g> је искључена"</string> <string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Измените пречице"</string> <string name="done_accessibility_shortcut_menu_button" msgid="3668407723770815708">"Готово"</string> @@ -1673,8 +1673,8 @@ <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Користи пречицу"</string> <string name="color_inversion_feature_name" msgid="326050048927789012">"Инверзија боја"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"Корекција боја"</string> - <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Задржали сте тастере за јачину звука. Услуга <xliff:g id="SERVICE_NAME">%1$s</xliff:g> је укључена."</string> - <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Задржали сте тастере за јачину звука. Услуга <xliff:g id="SERVICE_NAME">%1$s</xliff:g> је искључена."</string> + <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Држали сте тастере за јачину звука. Услуга <xliff:g id="SERVICE_NAME">%1$s</xliff:g> је укључена."</string> + <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Држали сте тастере за јачину звука. Услуга <xliff:g id="SERVICE_NAME">%1$s</xliff:g> је искључена."</string> <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Притисните и задржите оба тастера за јачину звука три секунде да бисте користили <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string> <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Изаберите функцију која ће се користити када додирнете дугме Приступачност:"</string> <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Одаберите функцију која ће се користити помоћу покрета за приступачност (помоћу два прста превуците нагоре од дна екрана):"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 45c8e349e5f0..b7e8b9a94e30 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1417,7 +1417,7 @@ <string name="forward_intent_to_work" msgid="3620262405636021151">"Ginagamit mo ang app na ito sa iyong profile sa trabaho"</string> <string name="input_method_binding_label" msgid="1166731601721983656">"Pamamaraan ng pag-input"</string> <string name="sync_binding_label" msgid="469249309424662147">"I-sync"</string> - <string name="accessibility_binding_label" msgid="1974602776545801715">"Pagiging Accessible"</string> + <string name="accessibility_binding_label" msgid="1974602776545801715">"Accessibility"</string> <string name="wallpaper_binding_label" msgid="1197440498000786738">"Wallpaper"</string> <string name="chooser_wallpaper" msgid="3082405680079923708">"Baguhin ang wallpaper"</string> <string name="notification_listener_binding_label" msgid="2702165274471499713">"Notification listener"</string> @@ -1621,13 +1621,13 @@ <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Alisin"</string> <string name="allow_while_in_use_permission_in_fgs" msgid="4101339676785053656">"Ang sinimulan sa background na serbisyo sa foreground mula sa <xliff:g id="PACKAGENAME">%1$s</xliff:g> ay hindi magkakaroon ng pahintulot habang ginagamit sa mga R build sa hinaharap. Pakipuntahan ang go/r-bg-fgs-restriction at maghain ng bugreport."</string> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Lakasan ang volume nang lagpas sa inirerekomendang antas?\n\nMaaaring mapinsala ng pakikinig sa malakas na volume sa loob ng mahahabang panahon ang iyong pandinig."</string> - <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gagamitin ang Shortcut sa Pagiging Accessible?"</string> + <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gagamitin ang Shortcut sa Accessibility?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kapag naka-on ang shortcut, magsisimula ang isang feature ng pagiging naa-access kapag pinindot ang parehong button ng volume."</string> - <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"I-on ang mga feature ng pagiging accessible?"</string> - <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Mao-on ang mga feature ng pagiging accessible kapag pinindot nang matagal ang parehong volume key nang ilang segundo. Posibleng mabago nito ang paggana ng iyong device.\n\nMga kasalukuyang feature:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuwede mong baguhin ang mga napiling feature sa Mga Setting > Pagiging Accessible."</string> + <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"I-on ang mga feature ng accessibility?"</string> + <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Mao-on ang mga feature ng accessibility kapag pinindot nang matagal ang parehong volume key nang ilang segundo. Posibleng mabago nito ang paggana ng iyong device.\n\nMga kasalukuyang feature:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuwede mong baguhin ang mga napiling feature sa Mga Setting > Accessibility."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"I-on ang <xliff:g id="SERVICE">%1$s</xliff:g>?"</string> - <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Mao-on ang feature ng pagiging accessible na <xliff:g id="SERVICE">%1$s</xliff:g> kapag pinindot nang matagal ang parehong volume key nang ilang segundo. Posibleng mabago nito ang paggana ng iyong device.\n\nPuwede mong palitan ng ibang feature ang shortcut na ito sa Mga Setting > Pagiging Accessible."</string> + <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Mao-on ang feature ng accessibility na <xliff:g id="SERVICE">%1$s</xliff:g> kapag pinindot nang matagal ang parehong volume key nang ilang segundo. Posibleng mabago nito ang paggana ng iyong device.\n\nPuwede mong palitan ng ibang feature ang shortcut na ito sa Mga Setting > Accessibility."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"I-on"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Huwag i-on"</string> <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"NAKA-ON"</string> @@ -1651,12 +1651,12 @@ <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Gamitin ang Shortcut"</string> <string name="color_inversion_feature_name" msgid="326050048927789012">"Pag-invert ng Kulay"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"Pagwawasto ng Kulay"</string> - <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Mga volume key na pinipindot nang matagal. Na-on ang <xliff:g id="SERVICE_NAME">%1$s</xliff:g>."</string> + <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Pinindot nang matagal ang volume keys. Na-on ang <xliff:g id="SERVICE_NAME">%1$s</xliff:g>."</string> <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Mga volume key na pinipindot nang matagal. Na-off ang <xliff:g id="SERVICE_NAME">%1$s</xliff:g>."</string> <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Pindutin nang matagal ang parehong volume key sa loob ng tatlong segundo para magamit ang <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string> <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Pumili ng feature na gagana sa pamamagitan ng pag-tap mo sa button ng accessibility:"</string> - <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Pumili ng feature na gagana sa pamamagitan ng galaw ng pagiging accessible (pag-swipe pataas mula sa ibaba ng screen gamit ang dalawang daliri):"</string> - <string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"Pumili ng feature na gagana sa pamamagitan ng galaw ng pagiging accessible (pag-swipe pataas mula sa ibaba ng screen gamit ang tatlong daliri):"</string> + <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Pumili ng feature na gagana sa pamamagitan ng galaw ng accessibility (pag-swipe pataas mula sa ibaba ng screen gamit ang dalawang daliri):"</string> + <string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"Pumili ng feature na gagana sa pamamagitan ng galaw ng accessibility (pag-swipe pataas mula sa ibaba ng screen gamit ang tatlong daliri):"</string> <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"Para magpalipat-lipat sa mga feature, pindutin nang matagal ang button ng accessibility."</string> <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"Para magpalipat-lipat sa mga feature, mag-swipe pataas gamit ang dalawang daliri at i-hold ito."</string> <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"Para magpalipat-lipat sa mga feature, mag-swipe pataas gamit ang tatlong daliri at i-hold ito."</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index bb9ffc066917..f3b5a63e78fa 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1651,7 +1651,7 @@ <string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Kısayolu Kullan"</string> <string name="color_inversion_feature_name" msgid="326050048927789012">"Rengi Ters Çevirme"</string> <string name="color_correction_feature_name" msgid="3655077237805422597">"Renk Düzeltme"</string> - <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Ses tuşlarını basılı tutun. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> açıldı."</string> + <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Ses tuşlarını basılı tuttunuz. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> açıldı."</string> <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Ses tuşlarını basılı tutun. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> kapatıldı."</string> <string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"<xliff:g id="SERVICE_NAME">%1$s</xliff:g> hizmetini kullanmak için her iki ses tuşunu basılı tutun"</string> <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Erişilebilirlik düğmesine dokunduğunuzda kullanmak için bir özellik seçin:"</string> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a1c2450be153..0c8745392f5e 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4363,9 +4363,6 @@ <!-- The delete-widget drop target button text --> <string name="kg_reordering_delete_drop_target_text">Remove</string> - <!-- Toast message for background started foreground service while-in-use permission restriction feature --> - <string name="allow_while_in_use_permission_in_fgs">The background started foreground service from <xliff:g id="packageName" example="com.example">%1$s</xliff:g> will not have while-in-use permission in future R builds. Please see go/r-bg-fgs-restriction and file a bugreport.</string> - <!-- Message shown in dialog when user is attempting to set the music volume above the recommended maximum level for headphones --> <string name="safe_media_volume_warning" product="default"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c56c78a0b6a1..08d1182172f0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3830,6 +3830,7 @@ <java-symbol type="drawable" name="android_logotype" /> <java-symbol type="layout" name="platlogo_layout" /> + <java-symbol type="drawable" name="ic_number11" /> <java-symbol type="integer" name="config_notificationWarnRemoteViewSizeBytes" /> <java-symbol type="integer" name="config_notificationStripRemoteViewSizeBytes" /> @@ -4007,9 +4008,6 @@ <java-symbol type="dimen" name="resolver_empty_state_container_padding_top" /> <java-symbol type="dimen" name="resolver_empty_state_container_padding_bottom" /> - <!-- Toast message for background started foreground service while-in-use permission restriction feature --> - <java-symbol type="string" name="allow_while_in_use_permission_in_fgs" /> - <java-symbol type="string" name="config_deviceSpecificDisplayAreaPolicyProvider" /> <!-- Whether to expand the lock screen user switcher by default --> diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index d4c256972b28..964ae21d6086 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -678,7 +678,8 @@ public class InsetsControllerTest { final InsetsState currentState = new InsetsState(mController.getState()); // The caption bar source should be synced with the info in mAttachInfo. assertEquals(captionFrame, currentState.peekSource(ITYPE_CAPTION_BAR).getFrame()); - assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/)); + assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/, + true /* excludeInvisibleIme */)); mController.setCaptionInsetsHeight(0); mController.onStateChanged(state); // The caption bar source should not be there at all, because we don't add empty diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index cd93eeb857f1..7115acfedcf6 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -276,6 +276,15 @@ public class InsetsStateTest { } @Test + public void testEquals_excludeInvisibleIme() { + mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_IME).setVisible(false); + mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 200)); + mState2.getSource(ITYPE_IME).setVisible(false); + assertTrue(mState2.equals(mState, true, true /* excludeInvisibleIme */)); + } + + @Test public void testParcelUnparcel() { mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100)); mState.getSource(ITYPE_IME).setVisibleFrame(new Rect(0, 0, 50, 10)); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index 44a52639bd35..0f6b51f82116 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -222,6 +222,11 @@ public class ChooserWrapperActivity extends ChooserActivity { super.queryTargetServices(adapter); } + @Override + protected boolean isQuietModeEnabled(UserHandle userHandle) { + return sOverrides.isQuietModeEnabled; + } + /** * We cannot directly mock the activity created since instrumentation creates it. * <p> diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp index d6542de91e08..e5492714b29f 100644 --- a/data/etc/car/Android.bp +++ b/data/etc/car/Android.bp @@ -129,6 +129,13 @@ prebuilt_etc { } prebuilt_etc { + name: "privapp_whitelist_com.android.car.companiondevicesupport", + sub_dir: "permissions", + src: "com.android.car.companiondevicesupport.xml", + filename_from_src: true, +} + +prebuilt_etc { name: "privapp_whitelist_com.google.android.car.kitchensink", sub_dir: "permissions", src: "com.google.android.car.kitchensink.xml", diff --git a/data/etc/car/com.android.car.companiondevicesupport.xml b/data/etc/car/com.android.car.companiondevicesupport.xml new file mode 100644 index 000000000000..2067bab20d3d --- /dev/null +++ b/data/etc/car/com.android.car.companiondevicesupport.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<permissions> + <privapp-permissions package="com.android.car.companiondevicesupport"> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.PROVIDE_TRUST_AGENT"/> + <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> + </privapp-permissions> +</permissions> diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp index 308c1a59a7aa..4f4364f72fef 100644 --- a/libs/WindowManager/Jetpack/Android.bp +++ b/libs/WindowManager/Jetpack/Android.bp @@ -13,26 +13,26 @@ // limitations under the License. android_library_import { - name: "window-extensions", - aars: ["window-extensions-release.aar"], + name: "window-sidecar", + aars: ["window-sidecar-release.aar"], sdk_version: "current", } java_library { - name: "androidx.window.extensions", + name: "androidx.window.sidecar", srcs: ["src/**/*.java"], - static_libs: ["window-extensions"], + static_libs: ["window-sidecar"], installable: true, sdk_version: "core_platform", vendor: true, libs: ["framework", "androidx.annotation_annotation",], - required: ["androidx.window.extensions.xml",], + required: ["androidx.window.sidecar.xml",], } prebuilt_etc { - name: "androidx.window.extensions.xml", + name: "androidx.window.sidecar.xml", vendor: true, sub_dir: "permissions", - src: "androidx.window.extensions.xml", + src: "androidx.window.sidecar.xml", filename_from_src: true, } diff --git a/libs/WindowManager/Jetpack/androidx.window.extensions.xml b/libs/WindowManager/Jetpack/androidx.window.extensions.xml deleted file mode 100644 index 1f0ff6656de0..000000000000 --- a/libs/WindowManager/Jetpack/androidx.window.extensions.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 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. - --> -<permissions> - <library - name="androidx.window.extensions" - file="/vendor/framework/androidx.window.extensions.jar"/> -</permissions> diff --git a/libs/WindowManager/Jetpack/androidx.window.sidecar.xml b/libs/WindowManager/Jetpack/androidx.window.sidecar.xml new file mode 100644 index 000000000000..f88a5f4ae039 --- /dev/null +++ b/libs/WindowManager/Jetpack/androidx.window.sidecar.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<permissions> + <library + name="androidx.window.sidecar" + file="/vendor/framework/androidx.window.sidecar.jar"/> +</permissions> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SettingsExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java index 7a3fbf3ad9b8..92e575804bbe 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SettingsExtensionImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Android Open Source Project + * 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. @@ -14,14 +14,14 @@ * limitations under the License. */ -package androidx.window.extensions; +package androidx.window.sidecar; import static android.view.Display.DEFAULT_DISPLAY; -import static androidx.window.extensions.ExtensionHelper.getWindowDisplay; -import static androidx.window.extensions.ExtensionHelper.isInMultiWindow; -import static androidx.window.extensions.ExtensionHelper.rotateRectToDisplayRotation; -import static androidx.window.extensions.ExtensionHelper.transformToWindowSpaceRect; +import static androidx.window.sidecar.SidecarHelper.getWindowDisplay; +import static androidx.window.sidecar.SidecarHelper.isInMultiWindow; +import static androidx.window.sidecar.SidecarHelper.rotateRectToDisplayRotation; +import static androidx.window.sidecar.SidecarHelper.transformToWindowSpaceRect; import android.content.ContentResolver; import android.content.Context; @@ -42,8 +42,8 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -class SettingsExtensionImpl extends StubExtension { - private static final String TAG = "SettingsExtension"; +class SettingsSidecarImpl extends StubSidecar { + private static final String TAG = "SettingsSidecar"; private static final String DEVICE_POSTURE = "device_posture"; private static final String DISPLAY_FEATURES = "display_features"; @@ -106,7 +106,7 @@ class SettingsExtensionImpl extends StubExtension { } } - SettingsExtensionImpl(Context context) { + SettingsSidecarImpl(Context context) { mContext = context; mSettingsObserver = new SettingsObserver(); } @@ -118,29 +118,33 @@ class SettingsExtensionImpl extends StubExtension { /** Update display features with values read from settings. */ private void updateDisplayFeatures() { for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { - ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); + SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); updateWindowLayout(windowToken, newLayout); } } @NonNull @Override - public ExtensionDeviceState getDeviceState() { + public SidecarDeviceState getDeviceState() { ContentResolver resolver = mContext.getContentResolver(); int posture = Settings.Global.getInt(resolver, DEVICE_POSTURE, - ExtensionDeviceState.POSTURE_UNKNOWN); - return new ExtensionDeviceState(posture); + SidecarDeviceState.POSTURE_UNKNOWN); + SidecarDeviceState deviceState = new SidecarDeviceState(); + deviceState.posture = posture; + return deviceState; } @NonNull @Override - public ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) { - List<ExtensionDisplayFeature> displayFeatures = readDisplayFeatures(windowToken); - return new ExtensionWindowLayoutInfo(displayFeatures); + public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) { + List<SidecarDisplayFeature> displayFeatures = readDisplayFeatures(windowToken); + SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo(); + windowLayoutInfo.displayFeatures = displayFeatures; + return windowLayoutInfo; } - private List<ExtensionDisplayFeature> readDisplayFeatures(IBinder windowToken) { - List<ExtensionDisplayFeature> features = new ArrayList<ExtensionDisplayFeature>(); + private List<SidecarDisplayFeature> readDisplayFeatures(IBinder windowToken) { + List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>(); int displayId = getWindowDisplay(windowToken); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); @@ -170,10 +174,10 @@ class SettingsExtensionImpl extends StubExtension { int type; switch (featureType) { case FEATURE_TYPE_FOLD: - type = ExtensionDisplayFeature.TYPE_FOLD; + type = SidecarDisplayFeature.TYPE_FOLD; break; case FEATURE_TYPE_HINGE: - type = ExtensionDisplayFeature.TYPE_HINGE; + type = SidecarDisplayFeature.TYPE_HINGE; break; default: { Log.e(TAG, "Malformed feature type: " + featureType); @@ -189,8 +193,9 @@ class SettingsExtensionImpl extends StubExtension { rotateRectToDisplayRotation(featureRect, displayId); transformToWindowSpaceRect(featureRect, windowToken); if (!featureRect.isEmpty()) { - ExtensionDisplayFeature feature = - new ExtensionDisplayFeature(featureRect, type); + SidecarDisplayFeature feature = new SidecarDisplayFeature(); + feature.setRect(featureRect); + feature.setType(type); features.add(feature); } else { Log.w(TAG, "Failed to adjust feature to window"); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java index c61f1ed2d179..e5b6cff17b26 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.window.extensions; +package androidx.window.sidecar; import static android.view.Display.INVALID_DISPLAY; import static android.view.Surface.ROTATION_0; @@ -32,12 +32,7 @@ import android.view.Surface; import androidx.annotation.Nullable; -/** - * Toolkit class for calculation of the display feature bounds within the window. - * NOTE: This sample implementation only works for Activity windows, because there is no public APIs - * to obtain layout params or bounds for arbitrary windows. - */ -class ExtensionHelper { +class SidecarHelper { /** * Rotate the input rectangle specified in default display orientation to the current display * rotation. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java index 47349f11fb93..0b4915ed5dac 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.window.extensions; +package androidx.window.sidecar; import android.content.Context; @@ -22,14 +22,13 @@ import android.content.Context; * Provider class that will instantiate the library implementation. It must be included in the * vendor library, and the vendor implementation must match the signature of this class. */ -public class ExtensionProvider { - +public class SidecarProvider { /** - * The support library will instantiate the vendor implementation using this interface. - * @return An implementation of {@link ExtensionInterface}. + * Provide a simple implementation of {@link SidecarInterface} that can be replaced by + * an OEM by overriding this method. */ - public static ExtensionInterface getExtensionImpl(Context context) { - return new SettingsExtensionImpl(context); + public static SidecarInterface getSidecarImpl(Context context) { + return new SettingsSidecarImpl(context); } /** @@ -37,6 +36,6 @@ public class ExtensionProvider { * @return API version string in MAJOR.MINOR.PATCH-description format. */ public static String getApiVersion() { - return "1.0.0-settings_sample"; + return "0.1.0-settings_sample"; } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java new file mode 100644 index 000000000000..199c37315c07 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java @@ -0,0 +1,85 @@ +/* + * 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 androidx.window.sidecar; + +import android.os.IBinder; + +import androidx.annotation.NonNull; + +import java.util.HashSet; +import java.util.Set; + +/** + * Basic implementation of the {@link SidecarInterface}. An OEM can choose to use it as the base + * class for their implementation. + */ +abstract class StubSidecar implements SidecarInterface { + + private SidecarCallback mSidecarCallback; + private final Set<IBinder> mWindowLayoutChangeListenerTokens = new HashSet<>(); + private boolean mDeviceStateChangeListenerRegistered; + + StubSidecar() { + } + + @Override + public void setSidecarCallback(@NonNull SidecarCallback sidecarCallback) { + this.mSidecarCallback = sidecarCallback; + } + + @Override + public void onWindowLayoutChangeListenerAdded(@NonNull IBinder iBinder) { + this.mWindowLayoutChangeListenerTokens.add(iBinder); + this.onListenersChanged(); + } + + @Override + public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder iBinder) { + this.mWindowLayoutChangeListenerTokens.remove(iBinder); + this.onListenersChanged(); + } + + @Override + public void onDeviceStateListenersChanged(boolean isEmpty) { + this.mDeviceStateChangeListenerRegistered = !isEmpty; + this.onListenersChanged(); + } + + void updateDeviceState(SidecarDeviceState newState) { + if (this.mSidecarCallback != null) { + mSidecarCallback.onDeviceStateChanged(newState); + } + } + + void updateWindowLayout(@NonNull IBinder windowToken, + @NonNull SidecarWindowLayoutInfo newLayout) { + if (this.mSidecarCallback != null) { + mSidecarCallback.onWindowLayoutChanged(windowToken, newLayout); + } + } + + @NonNull + Set<IBinder> getWindowsListeningForLayoutChanges() { + return mWindowLayoutChangeListenerTokens; + } + + protected boolean hasListeners() { + return !mWindowLayoutChangeListenerTokens.isEmpty() || mDeviceStateChangeListenerRegistered; + } + + protected abstract void onListenersChanged(); +} diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differdeleted file mode 100644 index 0ebbb86daf82..000000000000 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ /dev/null diff --git a/libs/WindowManager/Jetpack/window-sidecar-release.aar b/libs/WindowManager/Jetpack/window-sidecar-release.aar Binary files differnew file mode 100644 index 000000000000..50f101d7d181 --- /dev/null +++ b/libs/WindowManager/Jetpack/window-sidecar-release.aar diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index d177855e5a7d..1e5877356e8d 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -42,7 +42,7 @@ namespace renderthread { // to the screen resolution. This is meant to be a conservative default based on // that analysis. The 4.0f is used because the default pixel format is assumed to // be ARGB_8888. -#define SURFACE_SIZE_MULTIPLIER (5.0f * 4.0f) +#define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f) #define BACKGROUND_RETENTION_PERCENTAGE (0.5f) CacheManager::CacheManager() diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp index af6411011411..d291ec001daf 100644 --- a/libs/incident/Android.bp +++ b/libs/incident/Android.bp @@ -95,7 +95,17 @@ cc_test { name: "libincident_test", test_config: "AndroidTest.xml", defaults: ["libincidentpriv_defaults"], - test_suites: ["device-tests"], + test_suites: ["device-tests", "mts"], + compile_multilib: "both", + multilib: { + lib64: { + suffix: "64", + }, + lib32: { + suffix: "32", + }, + }, + require_root: true, include_dirs: [ "frameworks/base/libs/incident/include", diff --git a/libs/incident/AndroidTest.xml b/libs/incident/AndroidTest.xml index 7c0b04471d13..b6b3f8596826 100644 --- a/libs/incident/AndroidTest.xml +++ b/libs/incident/AndroidTest.xml @@ -16,13 +16,17 @@ <configuration description="Config for libincident_test"> <option name="test-suite-tag" value="device-tests" /> <option name="config-descriptor:metadata" key="component" value="misc" /> - <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> <option name="cleanup" value="true" /> <option name="push" value="libincident_test->/data/local/tmp/libincident_test" /> + <option name="append-bitness" value="true" /> </target_preparer> <test class="com.android.tradefed.testtype.GTest" > <option name="native-test-device-path" value="/data/local/tmp" /> <option name="module-name" value="libincident_test" /> </test> + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.os.statsd" /> + </object> </configuration> diff --git a/libs/incident/TEST_MAPPING b/libs/incident/TEST_MAPPING index 59ebe7664b5f..25e000092ecf 100644 --- a/libs/incident/TEST_MAPPING +++ b/libs/incident/TEST_MAPPING @@ -2,9 +2,6 @@ "presubmit": [ { "name": "libincident_test" - }, - { - "name": "GtsLibIncidentTests" } ] } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index a112bdd0ce03..7d15bbd46697 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -2583,9 +2583,15 @@ public class LocationManager { } public void cancel() { + remove(); + } + + private Consumer<Location> remove() { + Consumer<Location> consumer; ICancellationSignal cancellationSignal; synchronized (this) { mExecutor = null; + consumer = mConsumer; mConsumer = null; if (mAlarmManager != null) { @@ -2605,6 +2611,8 @@ public class LocationManager { // ignore } } + + return consumer; } public void fail() { @@ -2663,16 +2671,10 @@ public class LocationManager { } private void acceptResult(Location location) { - Consumer<Location> consumer; - synchronized (this) { - if (mConsumer == null) { - return; - } - consumer = mConsumer; - cancel(); + Consumer<Location> consumer = remove(); + if (consumer != null) { + consumer.accept(location); } - - consumer.accept(location); } } diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl index 240ae796f957..53122bb990d6 100644 --- a/media/java/android/media/IMediaRouterClient.aidl +++ b/media/java/android/media/IMediaRouterClient.aidl @@ -23,4 +23,5 @@ oneway interface IMediaRouterClient { void onStateChanged(); void onRestoreRoute(); void onSelectedRouteChanged(String routeId); + void onGlobalA2dpChanged(boolean a2dpOn); } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index e5ad569bb24f..54c0bc94c2d0 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -317,9 +317,10 @@ public final class MediaRoute2Info implements Parcelable { @ConnectionState final int mConnectionState; final String mClientPackageName; - final int mVolume; - final int mVolumeMax; final int mVolumeHandling; + final int mVolumeMax; + final int mVolume; + final String mAddress; final Bundle mExtras; final String mProviderId; @@ -336,6 +337,7 @@ public final class MediaRoute2Info implements Parcelable { mVolumeHandling = builder.mVolumeHandling; mVolumeMax = builder.mVolumeMax; mVolume = builder.mVolume; + mAddress = builder.mAddress; mExtras = builder.mExtras; mProviderId = builder.mProviderId; } @@ -353,6 +355,7 @@ public final class MediaRoute2Info implements Parcelable { mVolumeHandling = in.readInt(); mVolumeMax = in.readInt(); mVolume = in.readInt(); + mAddress = in.readString(); mExtras = in.readBundle(); mProviderId = in.readString(); } @@ -483,6 +486,15 @@ public final class MediaRoute2Info implements Parcelable { return mVolume; } + /** + * Gets the hardware address of the route if available. + * @hide + */ + @Nullable + public String getAddress() { + return mAddress; + } + @Nullable public Bundle getExtras() { return mExtras == null ? null : new Bundle(mExtras); @@ -564,6 +576,7 @@ public final class MediaRoute2Info implements Parcelable { && (mVolumeHandling == other.mVolumeHandling) && (mVolumeMax == other.mVolumeMax) && (mVolume == other.mVolume) + && Objects.equals(mAddress, other.mAddress) && Objects.equals(mProviderId, other.mProviderId); } @@ -572,7 +585,7 @@ public final class MediaRoute2Info implements Parcelable { // Note: mExtras is not included. return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription, mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume, - mProviderId); + mAddress, mProviderId); } @Override @@ -614,6 +627,7 @@ public final class MediaRoute2Info implements Parcelable { dest.writeInt(mVolumeHandling); dest.writeInt(mVolumeMax); dest.writeInt(mVolume); + dest.writeString(mAddress); dest.writeBundle(mExtras); dest.writeString(mProviderId); } @@ -637,6 +651,7 @@ public final class MediaRoute2Info implements Parcelable { int mVolumeHandling = PLAYBACK_VOLUME_FIXED; int mVolumeMax; int mVolume; + String mAddress; Bundle mExtras; String mProviderId; @@ -669,24 +684,7 @@ public final class MediaRoute2Info implements Parcelable { * @param routeInfo the existing instance to copy data from. */ public Builder(@NonNull MediaRoute2Info routeInfo) { - Objects.requireNonNull(routeInfo, "routeInfo must not be null"); - - mId = routeInfo.mId; - mName = routeInfo.mName; - mFeatures = new ArrayList<>(routeInfo.mFeatures); - mType = routeInfo.mType; - mIsSystem = routeInfo.mIsSystem; - mIconUri = routeInfo.mIconUri; - mDescription = routeInfo.mDescription; - mConnectionState = routeInfo.mConnectionState; - mClientPackageName = routeInfo.mClientPackageName; - mVolumeHandling = routeInfo.mVolumeHandling; - mVolumeMax = routeInfo.mVolumeMax; - mVolume = routeInfo.mVolume; - if (routeInfo.mExtras != null) { - mExtras = new Bundle(routeInfo.mExtras); - } - mProviderId = routeInfo.mProviderId; + this(routeInfo.mId, routeInfo); } /** @@ -715,6 +713,7 @@ public final class MediaRoute2Info implements Parcelable { mVolumeHandling = routeInfo.mVolumeHandling; mVolumeMax = routeInfo.mVolumeMax; mVolume = routeInfo.mVolume; + mAddress = routeInfo.mAddress; if (routeInfo.mExtras != null) { mExtras = new Bundle(routeInfo.mExtras); } @@ -865,6 +864,16 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Sets the hardware address of the route. + * @hide + */ + @NonNull + public Builder setAddress(String address) { + mAddress = address; + return this; + } + + /** * Sets a bundle of extras for the route. * <p> * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}. diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 908fd820d047..e199cf49db9a 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -246,7 +246,7 @@ public abstract class MediaRoute2ProviderService extends Service { String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { - Log.w(TAG, "Ignoring duplicate session id."); + Log.w(TAG, "notifySessionCreated: Ignoring duplicate session id."); return; } mSessionInfo.put(sessionInfo.getId(), sessionInfo); @@ -274,7 +274,7 @@ public abstract class MediaRoute2ProviderService extends Service { if (mSessionInfo.containsKey(sessionId)) { mSessionInfo.put(sessionId, sessionInfo); } else { - Log.w(TAG, "Ignoring unknown session info."); + Log.w(TAG, "notifySessionUpdated: Ignoring unknown session info."); return; } @@ -304,7 +304,7 @@ public abstract class MediaRoute2ProviderService extends Service { sessionInfo = mSessionInfo.remove(sessionId); if (sessionInfo == null) { - Log.w(TAG, "Ignoring unknown session info."); + Log.w(TAG, "notifySessionReleased: Ignoring unknown session info."); return; } @@ -314,7 +314,7 @@ public abstract class MediaRoute2ProviderService extends Service { try { mRemoteCallback.notifySessionReleased(sessionInfo); } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session info changed."); + Log.w(TAG, "Failed to notify session released.", ex); } } } @@ -485,7 +485,7 @@ public abstract class MediaRoute2ProviderService extends Service { try { mRemoteCallback.updateState(mProviderInfo); } catch (RemoteException ex) { - Log.w(TAG, "Failed to send onProviderInfoUpdated"); + Log.w(TAG, "Failed to publish provider state.", ex); } } diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 12fc3a60cf28..7ae2949e6074 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -621,33 +621,27 @@ public class MediaRouter { final class Client extends IMediaRouterClient.Stub { @Override public void onStateChanged() { - mHandler.post(new Runnable() { - @Override - public void run() { - if (Client.this == mClient) { - updateClientState(); - } + mHandler.post(() -> { + if (Client.this == mClient) { + updateClientState(); } }); } @Override public void onRestoreRoute() { - mHandler.post(new Runnable() { - @Override - public void run() { - // Skip restoring route if the selected route is not a system audio route, - // MediaRouter is initializing, or mClient was changed. - if (Client.this != mClient || mSelectedRoute == null - || (mSelectedRoute != mDefaultAudioVideo - && mSelectedRoute != mBluetoothA2dpRoute)) { - return; - } - if (DEBUG) { - Log.d(TAG, "onRestoreRoute() : route=" + mSelectedRoute); - } - mSelectedRoute.select(); + mHandler.post(() -> { + // Skip restoring route if the selected route is not a system audio route, + // MediaRouter is initializing, or mClient was changed. + if (Client.this != mClient || mSelectedRoute == null + || (mSelectedRoute != mDefaultAudioVideo + && mSelectedRoute != mBluetoothA2dpRoute)) { + return; + } + if (DEBUG) { + Log.d(TAG, "onRestoreRoute() : route=" + mSelectedRoute); } + mSelectedRoute.select(); }); } @@ -659,6 +653,19 @@ public class MediaRouter { } }); } + + // Called when the selection of a connected device (phone speaker or BT devices) + // is changed. + @Override + public void onGlobalA2dpChanged(boolean a2dpOn) { + mHandler.post(() -> { + if (mSelectedRoute == mDefaultAudioVideo && a2dpOn) { + setSelectedRoute(mBluetoothA2dpRoute, false); + } else if (mSelectedRoute == mBluetoothA2dpRoute && !a2dpOn) { + setSelectedRoute(mDefaultAudioVideo, false); + } + }); + } } } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index e767c68f13ac..470c52a964c5 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -196,7 +196,7 @@ public final class MediaRouter2 { try { mMediaRouterService.setDiscoveryRequestWithRouter2(mStub, mDiscoveryPreference); } catch (RemoteException ex) { - Log.e(TAG, "registerRouteCallback: Unable to set discovery request."); + Log.e(TAG, "registerRouteCallback: Unable to set discovery request.", ex); } } } @@ -214,7 +214,7 @@ public final class MediaRouter2 { if (!mRouteCallbackRecords.remove( new RouteCallbackRecord(null, routeCallback, null))) { - Log.w(TAG, "Ignoring unknown callback"); + Log.w(TAG, "unregisterRouteCallback: Ignoring unknown callback"); return; } @@ -227,7 +227,7 @@ public final class MediaRouter2 { mMediaRouterService.setDiscoveryRequestWithRouter2( mStub, mDiscoveryPreference); } catch (RemoteException ex) { - Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request."); + Log.e(TAG, "unregisterRouteCallback: Unable to set discovery request.", ex); } } if (mRouteCallbackRecords.isEmpty() && mNonSystemRoutingControllers.isEmpty()) { @@ -500,7 +500,7 @@ public final class MediaRouter2 { try { mMediaRouterService.setRouteVolumeWithRouter2(stub, route, volume); } catch (RemoteException ex) { - Log.e(TAG, "Unable to send control request.", ex); + Log.e(TAG, "Unable to set route volume.", ex); } } } @@ -620,6 +620,7 @@ public final class MediaRouter2 { changedRoutes.add(route); } } + mShouldUpdateRoutes = true; } if (changedRoutes.size() > 0) { notifyRoutesChanged(changedRoutes); @@ -788,7 +789,8 @@ public final class MediaRouter2 { mMediaRouterService.notifySessionHintsForCreatingSession( stub, uniqueRequestId, route, controllerHints); } catch (RemoteException ex) { - Log.e(TAG, "getSessionHintsOnHandler: Unable to request.", ex); + Log.e(TAG, "onGetControllerHintsForCreatingSessionOnHandler: Unable to notify " + + " session hints for creating session.", ex); } } } @@ -1120,7 +1122,7 @@ public final class MediaRouter2 { Objects.requireNonNull(route, "route must not be null"); synchronized (mControllerLock) { if (mIsReleased) { - Log.w(TAG, "selectRoute() called on released controller. Ignoring."); + Log.w(TAG, "selectRoute: Called on released controller. Ignoring."); return; } } @@ -1169,7 +1171,7 @@ public final class MediaRouter2 { Objects.requireNonNull(route, "route must not be null"); synchronized (mControllerLock) { if (mIsReleased) { - Log.w(TAG, "deselectRoute() called on released controller. Ignoring."); + Log.w(TAG, "deselectRoute: called on released controller. Ignoring."); return; } } @@ -1216,7 +1218,7 @@ public final class MediaRouter2 { Objects.requireNonNull(route, "route must not be null"); synchronized (mControllerLock) { if (mIsReleased) { - Log.w(TAG, "transferToRoute() called on released controller. Ignoring."); + Log.w(TAG, "transferToRoute: Called on released controller. Ignoring."); return; } @@ -1254,17 +1256,17 @@ public final class MediaRouter2 { */ public void setVolume(int volume) { if (getVolumeHandling() == MediaRoute2Info.PLAYBACK_VOLUME_FIXED) { - Log.w(TAG, "setVolume: the routing session has fixed volume. Ignoring."); + Log.w(TAG, "setVolume: The routing session has fixed volume. Ignoring."); return; } if (volume < 0 || volume > getVolumeMax()) { - Log.w(TAG, "setVolume: the target volume is out of range. Ignoring"); + Log.w(TAG, "setVolume: The target volume is out of range. Ignoring"); return; } synchronized (mControllerLock) { if (mIsReleased) { - Log.w(TAG, "setVolume is called on released controller. Ignoring."); + Log.w(TAG, "setVolume: Called on released controller. Ignoring."); return; } } @@ -1298,7 +1300,7 @@ public final class MediaRouter2 { boolean releaseInternal(boolean shouldReleaseSession, boolean shouldNotifyStop) { synchronized (mControllerLock) { if (mIsReleased) { - Log.w(TAG, "releaseInternal() called on released controller. Ignoring."); + Log.w(TAG, "releaseInternal: Called on released controller. Ignoring."); return false; } mIsReleased = true; diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 1c2d581b733d..5a7c87e0235d 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -121,7 +121,7 @@ public final class MediaRouter2Manager { CallbackRecord callbackRecord = new CallbackRecord(executor, callback); if (!mCallbackRecords.addIfAbsent(callbackRecord)) { - Log.w(TAG, "Ignoring to add the same callback twice."); + Log.w(TAG, "Ignoring to register the same callback twice."); return; } } @@ -384,7 +384,7 @@ public final class MediaRouter2Manager { int requestId = mNextRequestId.getAndIncrement(); mMediaRouterService.setRouteVolumeWithManager(client, requestId, route, volume); } catch (RemoteException ex) { - Log.e(TAG, "Unable to send control request.", ex); + Log.e(TAG, "Unable to set route volume.", ex); } } } @@ -414,7 +414,7 @@ public final class MediaRouter2Manager { mMediaRouterService.setSessionVolumeWithManager( client, requestId, sessionInfo.getId(), volume); } catch (RemoteException ex) { - Log.e(TAG, "Unable to send control request.", ex); + Log.e(TAG, "Unable to set session volume.", ex); } } } @@ -576,6 +576,10 @@ public final class MediaRouter2Manager { } void updatePreferredFeatures(String packageName, List<String> preferredFeatures) { + if (preferredFeatures == null) { + mPreferredFeaturesMap.remove(packageName); + return; + } List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures); if ((prevFeatures == null && preferredFeatures.size() == 0) || Objects.equals(preferredFeatures, prevFeatures)) { diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml index d20ab49a22e6..ab9426593535 100644 --- a/packages/CarSystemUI/res/values/colors.xml +++ b/packages/CarSystemUI/res/values/colors.xml @@ -15,7 +15,6 @@ ~ limitations under the License --> <resources xmlns:android="http://schemas.android.com/apk/res/android"> - <color name="nav_bar_ripple_background_color">#40ffffff</color> <!-- colors for user switcher --> <color name="car_user_switcher_background_color">#000000</color> <color name="car_user_switcher_name_text_color">@*android:color/car_body1_light</color> diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index 4bf0fca445d1..cf967c02bec5 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -118,4 +118,7 @@ <item>com.android.systemui.car.window.SystemUIOverlayWindowManager</item> <item>com.android.systemui.car.volume.VolumeUI</item> </string-array> + + <!-- How many milliseconds to wait before force hiding the UserSwitchTransitionView --> + <integer name="config_userSwitchTransitionViewShownTimeoutMs" translatable="false">5000</integer> </resources> diff --git a/packages/CarSystemUI/res/values/styles.xml b/packages/CarSystemUI/res/values/styles.xml index 7fc69e6d5d8f..e76373d4a4f7 100644 --- a/packages/CarSystemUI/res/values/styles.xml +++ b/packages/CarSystemUI/res/values/styles.xml @@ -37,13 +37,9 @@ <item name="android:textColor">@*android:color/car_grey_50</item> </style> - <style name="CarNavigationBarButtonTheme"> - <item name="android:colorControlHighlight">@color/nav_bar_ripple_background_color</item> - </style> - <style name="NavigationBarButton"> <item name="android:layout_height">96dp</item> <item name="android:layout_width">96dp</item> - <item name="android:background">@*android:drawable/item_background_material</item> + <item name="android:background">?android:attr/selectableItemBackground</item> </style> </resources>
\ No newline at end of file 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 4c720abb4c74..37dfce4e16ce 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java @@ -16,10 +16,10 @@ package com.android.systemui.car.navigationbar; -import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES; +import static android.view.InsetsState.ITYPE_CLIMATE_BAR; +import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; -import static android.view.InsetsState.ITYPE_TOP_GESTURES; import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; @@ -368,15 +368,13 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, height, - WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL, + WindowManager.LayoutParams.TYPE_STATUS_BAR, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); lp.setTitle("TopCarNavigationBar"); - lp.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_TOP_GESTURES}; - lp.setFitInsetsTypes(0); lp.windowAnimations = 0; lp.gravity = Gravity.TOP; mWindowManager.addView(mTopNavigationBarWindow, lp); @@ -390,14 +388,13 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, height, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); lp.setTitle("BottomCarNavigationBar"); - lp.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR, ITYPE_BOTTOM_GESTURES}; lp.windowAnimations = 0; lp.gravity = Gravity.BOTTOM; mWindowManager.addView(mBottomNavigationBarWindow, lp); @@ -415,6 +412,8 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); leftlp.setTitle("LeftCarNavigationBar"); + leftlp.providesInsetsTypes = new int[]{ITYPE_CLIMATE_BAR}; + leftlp.setFitInsetsTypes(0); leftlp.windowAnimations = 0; leftlp.gravity = Gravity.LEFT; mWindowManager.addView(mLeftNavigationBarWindow, leftlp); @@ -432,6 +431,8 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); rightlp.setTitle("RightCarNavigationBar"); + rightlp.providesInsetsTypes = new int[]{ITYPE_EXTRA_NAVIGATION_BAR}; + rightlp.setFitInsetsTypes(0); rightlp.windowAnimations = 0; rightlp.gravity = Gravity.RIGHT; mWindowManager.addView(mRightNavigationBarWindow, rightlp); diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java index 0ced4021ce38..029d4c7fa2fb 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java @@ -16,10 +16,7 @@ package com.android.systemui.car.navigationbar; -import static android.view.WindowInsets.Type.systemBars; - import android.content.Context; -import android.graphics.Insets; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; @@ -82,28 +79,9 @@ public class CarNavigationBarView extends LinearLayout { @Override public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) { - applyMargins(windowInsets.getInsets(systemBars())); return windowInsets; } - private void applyMargins(Insets insets) { - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - if (child.getLayoutParams() instanceof LayoutParams) { - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.rightMargin != insets.right || lp.leftMargin != insets.left - || lp.topMargin != insets.top || lp.bottomMargin != insets.bottom) { - lp.rightMargin = insets.right; - lp.leftMargin = insets.left; - lp.topMargin = insets.top; - lp.bottomMargin = insets.bottom; - child.requestLayout(); - } - } - } - } - // Used to forward touch events even if the touch was initiated from a child component @Override public boolean onInterceptTouchEvent(MotionEvent ev) { diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java index 6d140cae5442..7d353f5acd9a 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/BottomNotificationPanelViewMediator.java @@ -16,6 +16,7 @@ package com.android.systemui.car.notification; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.car.navigationbar.CarNavigationBarController; import com.android.systemui.car.window.OverlayPanelViewController; @@ -37,6 +38,7 @@ public class BottomNotificationPanelViewMediator extends NotificationPanelViewMe NotificationPanelViewController notificationPanelViewController, PowerManagerHelper powerManagerHelper, + BroadcastDispatcher broadcastDispatcher, CarDeviceProvisionedController carDeviceProvisionedController, ConfigurationController configurationController @@ -44,6 +46,7 @@ public class BottomNotificationPanelViewMediator extends NotificationPanelViewMe super(carNavigationBarController, notificationPanelViewController, powerManagerHelper, + broadcastDispatcher, carDeviceProvisionedController, configurationController); notificationPanelViewController.setOverlayDirection( diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java index 41349b284147..0c185bae8199 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java @@ -17,10 +17,17 @@ package com.android.systemui.car.notification; import android.car.hardware.power.CarPowerManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Configuration; +import android.os.UserHandle; +import android.util.Log; import androidx.annotation.CallSuper; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.car.navigationbar.CarNavigationBarController; import com.android.systemui.car.window.OverlayViewMediator; @@ -37,18 +44,36 @@ import javax.inject.Singleton; public class NotificationPanelViewMediator implements OverlayViewMediator, ConfigurationController.ConfigurationListener { + private static final boolean DEBUG = false; + private static final String TAG = "NotificationPanelVM"; + private final CarNavigationBarController mCarNavigationBarController; private final NotificationPanelViewController mNotificationPanelViewController; private final PowerManagerHelper mPowerManagerHelper; + private final BroadcastDispatcher mBroadcastDispatcher; private final CarDeviceProvisionedController mCarDeviceProvisionedController; private final ConfigurationController mConfigurationController; + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) Log.v(TAG, "onReceive: " + intent); + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { + if (mNotificationPanelViewController.isPanelExpanded()) { + mNotificationPanelViewController.toggle(); + } + } + } + }; + @Inject public NotificationPanelViewMediator( CarNavigationBarController carNavigationBarController, NotificationPanelViewController notificationPanelViewController, PowerManagerHelper powerManagerHelper, + BroadcastDispatcher broadcastDispatcher, CarDeviceProvisionedController carDeviceProvisionedController, ConfigurationController configurationController @@ -56,6 +81,7 @@ public class NotificationPanelViewMediator implements OverlayViewMediator, mCarNavigationBarController = carNavigationBarController; mNotificationPanelViewController = notificationPanelViewController; mPowerManagerHelper = powerManagerHelper; + mBroadcastDispatcher = broadcastDispatcher; mCarDeviceProvisionedController = carDeviceProvisionedController; mConfigurationController = configurationController; } @@ -84,6 +110,9 @@ public class NotificationPanelViewMediator implements OverlayViewMediator, return mNotificationPanelViewController.isPanelExpanded(); } }); + + mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, + new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null, UserHandle.ALL); } @Override diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java index 8d3eb4c2bbee..89c9931ac76e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/TopNotificationPanelViewMediator.java @@ -16,6 +16,7 @@ package com.android.systemui.car.notification; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.car.navigationbar.CarNavigationBarController; import com.android.systemui.car.window.OverlayPanelViewController; @@ -37,6 +38,7 @@ public class TopNotificationPanelViewMediator extends NotificationPanelViewMedia NotificationPanelViewController notificationPanelViewController, PowerManagerHelper powerManagerHelper, + BroadcastDispatcher broadcastDispatcher, CarDeviceProvisionedController carDeviceProvisionedController, ConfigurationController configurationController @@ -44,6 +46,7 @@ public class TopNotificationPanelViewMediator extends NotificationPanelViewMedia super(carNavigationBarController, notificationPanelViewController, powerManagerHelper, + broadcastDispatcher, carDeviceProvisionedController, configurationController); notificationPanelViewController.setOverlayDirection( diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java index 775ef8152ca2..45f3d342fb6e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java @@ -23,13 +23,17 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.os.Handler; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.util.Log; +import android.view.IWindowManager; import android.widget.ImageView; import android.widget.TextView; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.systemui.R; import com.android.systemui.car.window.OverlayViewController; @@ -44,13 +48,24 @@ import javax.inject.Singleton; */ @Singleton public class UserSwitchTransitionViewController extends OverlayViewController { - private static final String TAG = "UserSwitchTransitionViewController"; + private static final String TAG = "UserSwitchTransition"; private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true"; + private static final boolean DEBUG = false; private final Context mContext; private final Handler mHandler; private final Resources mResources; private final UserManager mUserManager; + private final IWindowManager mWindowManagerService; + private final int mWindowShownTimeoutMs; + private final Runnable mWindowShownTimeoutCallback = () -> { + if (DEBUG) { + Log.w(TAG, "Window was not hidden within " + getWindowShownTimeoutMs() + " ms, so it" + + "was hidden by mWindowShownTimeoutCallback."); + } + + handleHide(); + }; @GuardedBy("this") private boolean mShowing; @@ -62,6 +77,7 @@ public class UserSwitchTransitionViewController extends OverlayViewController { @Main Handler handler, @Main Resources resources, UserManager userManager, + IWindowManager windowManagerService, OverlayViewGlobalStateController overlayViewGlobalStateController) { super(R.id.user_switching_dialog_stub, overlayViewGlobalStateController); @@ -70,6 +86,9 @@ public class UserSwitchTransitionViewController extends OverlayViewController { mHandler = handler; mResources = resources; mUserManager = userManager; + mWindowManagerService = windowManagerService; + mWindowShownTimeoutMs = mResources.getInteger( + R.integer.config_userSwitchTransitionViewShownTimeoutMs); } /** @@ -81,10 +100,20 @@ public class UserSwitchTransitionViewController extends OverlayViewController { if (mPreviousUserId == newUserId || mShowing) return; mShowing = true; mHandler.post(() -> { + try { + mWindowManagerService.setSwitchingUser(true); + mWindowManagerService.lockNow(null); + } catch (RemoteException e) { + Log.e(TAG, "unable to notify window manager service regarding user switch"); + } + start(); populateDialog(mPreviousUserId, newUserId); // next time a new user is selected, this current new user will be the previous user. mPreviousUserId = newUserId; + // In case the window is still showing after WINDOW_SHOWN_TIMEOUT_MS, then hide the + // window and log a warning message. + mHandler.postDelayed(mWindowShownTimeoutCallback, mWindowShownTimeoutMs); }); } @@ -92,6 +121,12 @@ public class UserSwitchTransitionViewController extends OverlayViewController { if (!mShowing) return; mShowing = false; mHandler.post(this::stop); + mHandler.removeCallbacks(mWindowShownTimeoutCallback); + } + + @VisibleForTesting + int getWindowShownTimeoutMs() { + return mWindowShownTimeoutMs; } private void populateDialog(@UserIdInt int previousUserId, @UserIdInt int newUserId) { diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java index eab381c92d98..797dbf515b7e 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java @@ -18,6 +18,8 @@ package com.android.systemui.car.userswitcher; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import android.content.Context; @@ -28,6 +30,7 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; +import android.view.IWindowManager; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -52,6 +55,8 @@ public class UserSwitchTransitionViewControllerTest extends SysuiTestCase { private TestableResources mTestableResources; @Mock private OverlayViewGlobalStateController mOverlayViewGlobalStateController; + @Mock + private IWindowManager mWindowManagerService; @Before public void setUp() { @@ -62,6 +67,7 @@ public class UserSwitchTransitionViewControllerTest extends SysuiTestCase { Handler.getMain(), mTestableResources.getResources(), (UserManager) mContext.getSystemService(Context.USER_SERVICE), + mWindowManagerService, mOverlayViewGlobalStateController ); @@ -118,6 +124,29 @@ public class UserSwitchTransitionViewControllerTest extends SysuiTestCase { any()); } + @Test + public void onWindowShownTimeoutPassed_viewNotHidden_hidesUserSwitchTransitionView() { + mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1); + reset(mOverlayViewGlobalStateController); + + getContext().getMainThreadHandler().postDelayed(() -> { + verify(mOverlayViewGlobalStateController).hideView( + eq(mCarUserSwitchingDialogController), any()); + }, mCarUserSwitchingDialogController.getWindowShownTimeoutMs() + 10); + } + + @Test + public void onWindowShownTimeoutPassed_viewHidden_doesNotHideUserSwitchTransitionViewAgain() { + mCarUserSwitchingDialogController.handleShow(/* currentUserId= */ TEST_USER_1); + mCarUserSwitchingDialogController.handleHide(); + reset(mOverlayViewGlobalStateController); + + getContext().getMainThreadHandler().postDelayed(() -> { + verify(mOverlayViewGlobalStateController, never()).hideView( + eq(mCarUserSwitchingDialogController), any()); + }, mCarUserSwitchingDialogController.getWindowShownTimeoutMs() + 10); + } + private final class TestableUserSwitchTransitionViewController extends UserSwitchTransitionViewController { @@ -125,8 +154,10 @@ public class UserSwitchTransitionViewControllerTest extends SysuiTestCase { TestableUserSwitchTransitionViewController(Context context, Handler handler, Resources resources, UserManager userManager, + IWindowManager windowManagerService, OverlayViewGlobalStateController overlayViewGlobalStateController) { - super(context, handler, resources, userManager, overlayViewGlobalStateController); + super(context, handler, resources, userManager, windowManagerService, + overlayViewGlobalStateController); mHandler = handler; } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 132922a59fc1..f42bf1982b36 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -95,7 +95,8 @@ public class ExternalStorageProvider extends FileSystemProvider { public String docId; public File visiblePath; public File path; - public boolean reportAvailableBytes = true; + // TODO (b/157033915): Make getFreeBytes() faster + public boolean reportAvailableBytes = false; } private static final String ROOT_ID_PRIMARY_EMULATED = diff --git a/packages/OsuLogin/Android.bp b/packages/OsuLogin/Android.bp index d7e36b164811..445c81b89273 100644 --- a/packages/OsuLogin/Android.bp +++ b/packages/OsuLogin/Android.bp @@ -1,5 +1,6 @@ android_app { name: "OsuLogin", + defaults: ["wifi-module-sdk-version-defaults"], static_libs: ["androidx.legacy_legacy-support-v4"], resource_dirs: ["res"], srcs: ["src/**/*.java"], diff --git a/packages/PackageInstaller/res/values-ne/strings.xml b/packages/PackageInstaller/res/values-ne/strings.xml index 60934b1e8ddb..495a05b941d6 100644 --- a/packages/PackageInstaller/res/values-ne/strings.xml +++ b/packages/PackageInstaller/res/values-ne/strings.xml @@ -30,11 +30,11 @@ <string name="install_failed_blocked" msgid="8512284352994752094">"यो प्याकेज स्थापना गर्ने क्रममा अवरोध गरियो।"</string> <string name="install_failed_conflict" msgid="3493184212162521426">"प्याकेजका रूपमा स्थापना नगरिएको एप विद्यमान प्याकेजसँग मेल खाँदैन।"</string> <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"एपका रूपमा स्थापना नगरिएको एप तपाईंको ट्याब्लेटसँग मिल्दो छैन।"</string> - <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"यो एप तपाईंको TV सँग मिल्दो छैन।"</string> + <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"यो एप तपाईंको टिभी सँग मिल्दो छैन।"</string> <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"एपका रूपमा स्थापना नगरिएको एप तपाईंको फोनसँग मिल्दो छैन।"</string> <string name="install_failed_invalid_apk" msgid="8581007676422623930">"प्याकेजका रूपमा स्थापना नगरिएको एप अमान्य जस्तो देखिन्छ।"</string> <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"तपाईंको ट्याब्लेटमा <xliff:g id="APP_NAME">%1$s</xliff:g> स्थापना गर्न सकिएन।"</string> - <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"तपाईंको TV मा <xliff:g id="APP_NAME">%1$s</xliff:g> स्थापना गर्न सकिएन।"</string> + <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"तपाईंको टिभी मा <xliff:g id="APP_NAME">%1$s</xliff:g> स्थापना गर्न सकिएन।"</string> <string name="install_failed_msg" product="default" msgid="6484461562647915707">"तपाईंको फोनमा <xliff:g id="APP_NAME">%1$s</xliff:g> स्थापना गर्न सकिएन।"</string> <string name="launch" msgid="3952550563999890101">"खोल्नुहोस्"</string> <string name="unknown_apps_admin_dlg_text" msgid="4456572224020176095">"तपाईंका प्रशासकले अज्ञात स्रोतहरूबाट प्राप्त अनुप्रयोगहरूलाई स्थापना गर्ने अनुमति दिनुहुन्न"</string> @@ -81,11 +81,11 @@ <string name="message_staging" msgid="8032722385658438567">"एप स्थापना गर्न तयारी गर्दै…"</string> <string name="app_name_unknown" msgid="6881210203354323926">"अज्ञात"</string> <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"तपाईंको सुरक्षाका लागि, तपाईंको ट्याब्लेटलाई यो स्रोतबाट प्राप्त हुने अज्ञात एपहरू स्थापना गर्ने अनुमति छैन।"</string> - <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"तपाईंको सुरक्षाका लागि, तपाईंको TV लाई यस स्रोतबाट प्राप्त हुने अज्ञात एपहरू स्थापना गर्ने अनुमति छैन।"</string> + <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"तपाईंको सुरक्षाका लागि, तपाईंको टिभी लाई यस स्रोतबाट प्राप्त हुने अज्ञात एपहरू स्थापना गर्ने अनुमति छैन।"</string> <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"तपाईंको सुरक्षाका लागि, तपाईंको फोनलाई यो स्रोतबाट प्राप्त हुने अज्ञात एपहरू स्थापना गर्ने अनुमति छैन।"</string> <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"तपाईंको फोन तथा व्यक्तिगत डेटा अज्ञात एपहरूबाट हुने आक्रमणको चपेटामा पर्ने बढी जोखिममा हुन्छन्। यो एप स्थापना गरेर तपाईं यसको प्रयोगबाट तपाईंको फोनमा हुन सक्ने क्षति वा डेटाको नोक्सानीका लागि स्वयं जिम्मेवार हुने कुरामा सहमत हुनुहुन्छ।"</string> <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"तपाईंको ट्याब्लेट तथा व्यक्तिगत डेटा अज्ञात एपहरूबाट हुने आक्रमणको चपेटामा पर्ने बढी जोखिममा हुन्छन्। यो एप स्थापना गरेर तपाईं यसको प्रयोगबाट तपाईंको ट्याब्लेटमा हुन सक्ने क्षति वा डेटाको नोक्सानीका लागि स्वयं जिम्मेवार हुने कुरामा सहमत हुनुहुन्छ।"</string> - <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"तपाईंको TV तथा व्यक्तिगत डेटा अज्ञात एपहरूबाट हुने आक्रमणको चपेटामा पर्ने बढी जोखिममा हुन्छन्। यो एप स्थापना गरेर तपाईं यसको प्रयोगबाट तपाईंको TV मा हुन सक्ने क्षति वा डेटाको नोक्सानीका लागि स्वयं जिम्मेवार हुने कुरामा सहमत हुनुहुन्छ।"</string> + <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"तपाईंको टिभी तथा व्यक्तिगत डेटा अज्ञात एपहरूबाट हुने आक्रमणको चपेटामा पर्ने बढी जोखिममा हुन्छन्। यो एप स्थापना गरेर तपाईं यसको प्रयोगबाट तपाईंको टिभी मा हुन सक्ने क्षति वा डेटाको नोक्सानीका लागि स्वयं जिम्मेवार हुने कुरामा सहमत हुनुहुन्छ।"</string> <string name="anonymous_source_continue" msgid="4375745439457209366">"जारी राख्नुहोस्"</string> <string name="external_sources_settings" msgid="4046964413071713807">"सेटिङहरू"</string> <string name="wear_app_channel" msgid="1960809674709107850">"वेयर एपहरूको स्थापना/स्थापना रद्द गर्दै"</string> diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java index 3bf43dd4d1bf..3565b0e3a9ae 100644 --- a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java +++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java @@ -30,9 +30,7 @@ import android.graphics.Rect; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.DrawableWrapper; import android.os.RemoteException; -import android.util.DisplayMetrics; import android.util.PathParser; -import android.view.Display; import android.view.IWindowManager; import android.view.WindowManagerGlobal; @@ -47,6 +45,9 @@ import java.lang.annotation.RetentionPolicy; */ public class AdaptiveOutlineDrawable extends DrawableWrapper { + private static final float ADVANCED_ICON_CENTER = 50f; + private static final float ADVANCED_ICON_RADIUS = 48f; + @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_DEFAULT, TYPE_ADVANCED}) public @interface AdaptiveOutlineIconType { @@ -61,7 +62,6 @@ public class AdaptiveOutlineDrawable extends DrawableWrapper { private int mStrokeWidth; private Bitmap mBitmap; private int mType; - private float mDensity; public AdaptiveOutlineDrawable(Resources resources, Bitmap bitmap) { super(new AdaptiveIconShapeDrawable(resources)); @@ -83,7 +83,6 @@ public class AdaptiveOutlineDrawable extends DrawableWrapper { mPath = new Path(PathParser.createPathFromPathData( resources.getString(com.android.internal.R.string.config_icon_mask))); mStrokeWidth = resources.getDimensionPixelSize(R.dimen.adaptive_outline_stroke); - mDensity = resources.getDisplayMetrics().density; mOutlinePaint = new Paint(); mOutlinePaint.setColor(getColor(resources, type)); mOutlinePaint.setStyle(Paint.Style.STROKE); @@ -137,12 +136,7 @@ public class AdaptiveOutlineDrawable extends DrawableWrapper { if (mType == TYPE_DEFAULT) { canvas.drawPath(mPath, mOutlinePaint); } else { - final float defaultDensity = getDefaultDisplayDensity(Display.DEFAULT_DISPLAY) - / (float) DisplayMetrics.DENSITY_DEFAULT; - final int insetPx = - Math.round(mInsetPx / (float) (Math.floor( - (mDensity / defaultDensity) * 100) / 100.0)); - canvas.drawCircle(2 * insetPx, 2 * insetPx, 2 * insetPx - mStrokeWidth, + canvas.drawCircle(ADVANCED_ICON_CENTER, ADVANCED_ICON_CENTER, ADVANCED_ICON_RADIUS, mOutlinePaint); } canvas.restoreToCount(count); diff --git a/packages/SettingsLib/SearchWidget/res/values-el/strings.xml b/packages/SettingsLib/SearchWidget/res/values-el/strings.xml index 6f5ab78b304b..d50436a29ac1 100644 --- a/packages/SettingsLib/SearchWidget/res/values-el/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-el/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="search_menu" msgid="1914043873178389845">"Ρυθμίσεις αναζήτησης"</string> + <string name="search_menu" msgid="1914043873178389845">"Αναζήτηση στις ρυθμίσεις"</string> </resources> diff --git a/packages/SettingsLib/SearchWidget/res/values-hy/strings.xml b/packages/SettingsLib/SearchWidget/res/values-hy/strings.xml index 8fa5a84acd79..b68b792acc32 100644 --- a/packages/SettingsLib/SearchWidget/res/values-hy/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-hy/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="search_menu" msgid="1914043873178389845">"Որոնման կարգավորումներ"</string> + <string name="search_menu" msgid="1914043873178389845">"Որոնեք կարգավորումներ"</string> </resources> diff --git a/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml b/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml index 85a8d7342827..5fe116e86f94 100644 --- a/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-pt-rPT/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="search_menu" msgid="1914043873178389845">"Pesquisa de definições"</string> + <string name="search_menu" msgid="1914043873178389845">"Pesquisar nas definições"</string> </resources> diff --git a/packages/SettingsLib/SearchWidget/res/values-sq/strings.xml b/packages/SettingsLib/SearchWidget/res/values-sq/strings.xml index a5313210a6f9..354941d39e12 100644 --- a/packages/SettingsLib/SearchWidget/res/values-sq/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-sq/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="search_menu" msgid="1914043873178389845">"Cilësimet e kërkimit"</string> + <string name="search_menu" msgid="1914043873178389845">"Kërko te cilësimet"</string> </resources> diff --git a/packages/SettingsLib/SearchWidget/res/values-uk/strings.xml b/packages/SettingsLib/SearchWidget/res/values-uk/strings.xml index dfd66b28aa7a..560ac1359e5a 100644 --- a/packages/SettingsLib/SearchWidget/res/values-uk/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-uk/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="search_menu" msgid="1914043873178389845">"Налаштування пошуку"</string> + <string name="search_menu" msgid="1914043873178389845">"Пошук налаштувань"</string> </resources> diff --git a/packages/SettingsLib/SearchWidget/res/values-vi/strings.xml b/packages/SettingsLib/SearchWidget/res/values-vi/strings.xml index cb1a75a616f5..90daf11c712d 100644 --- a/packages/SettingsLib/SearchWidget/res/values-vi/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-vi/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="search_menu" msgid="1914043873178389845">"Tìm kiếm trong các mục cài đặt"</string> + <string name="search_menu" msgid="1914043873178389845">"Tìm trong thông tin cài đặt"</string> </resources> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 69f7fb086b64..e17c0f015ff8 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -57,7 +57,7 @@ <string name="osu_sign_up_complete" msgid="7640183358878916847">"اكتمل الاشتراك. جارٍ الاتصال…"</string> <string name="speed_label_very_slow" msgid="8526005255731597666">"بطيئة جدًا"</string> <string name="speed_label_slow" msgid="6069917670665664161">"بطيئة"</string> - <string name="speed_label_okay" msgid="1253594383880810424">"حسنًا"</string> + <string name="speed_label_okay" msgid="1253594383880810424">"جيدة"</string> <string name="speed_label_medium" msgid="9078405312828606976">"متوسطة"</string> <string name="speed_label_fast" msgid="2677719134596044051">"سريعة"</string> <string name="speed_label_very_fast" msgid="8215718029533182439">"سريعة جدًا"</string> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index 50f0b76b6448..f26fe9d58533 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -203,9 +203,9 @@ <string name="vpn_settings_not_available" msgid="2894137119965668920">"VPN postavke nisu dostupne za ovog korisnika"</string> <string name="tethering_settings_not_available" msgid="266821736434699780">"Postavke za povezivanje putem mobitela nisu dostupne za ovog korisnika"</string> <string name="apn_settings_not_available" msgid="1147111671403342300">"Postavke za ime pristupne tačke nisu dostupne za ovog korisnika"</string> - <string name="enable_adb" msgid="8072776357237289039">"Otklanjanje grešaka putem uređaja spojenog na USB"</string> + <string name="enable_adb" msgid="8072776357237289039">"Otklanjanje grešaka putem USB-a"</string> <string name="enable_adb_summary" msgid="3711526030096574316">"Način rada za uklanjanje grešaka kada je povezan USB"</string> - <string name="clear_adb_keys" msgid="3010148733140369917">"Ukini odobrenja otklanjanja grešaka putem uređaja spojenog na USB"</string> + <string name="clear_adb_keys" msgid="3010148733140369917">"Ukini odobrenja otklanjanja grešaka putem USB-a"</string> <string name="enable_adb_wireless" msgid="6973226350963971018">"Bežično otklanjanje grešaka"</string> <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Način rada otklanjanja grešaka kada je WiFi mreža povezana"</string> <string name="adb_wireless_error" msgid="721958772149779856">"Greška"</string> @@ -300,11 +300,11 @@ <string name="debug_view_attributes" msgid="3539609843984208216">"Omogući pregled atributa prikaza"</string> <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Prijenos podataka na mobilnoj mreži ostaje aktivan čak i kada je aktiviran WiFi (za brzo prebacivanje između mreža)."</string> <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Korištenje hardverskog ubrzavanja za povezivanje putem mobitela ako je dostupno"</string> - <string name="adb_warning_title" msgid="7708653449506485728">"Omogućiti otklanjanje grešaka putem uređaja spojenog na USB?"</string> - <string name="adb_warning_message" msgid="8145270656419669221">"Otklanjanje grešaka putem uređaja spojenog na USB je namijenjeno samo u svrhe razvoja aplikacija. Koristite ga za kopiranje podataka između računara i uređaja, instaliranje aplikacija na uređaj bez obavještenja te čitanje podataka iz zapisnika."</string> + <string name="adb_warning_title" msgid="7708653449506485728">"Omogućiti otklanjanje grešaka putem USB-a?"</string> + <string name="adb_warning_message" msgid="8145270656419669221">"Otklanjanje grešaka putem USB-a je namijenjeno samo u svrhe razvoja aplikacija. Koristite ga za kopiranje podataka između računara i uređaja, instaliranje aplikacija na uređaj bez obavještenja te čitanje podataka iz zapisnika."</string> <string name="adbwifi_warning_title" msgid="727104571653031865">"Omogućiti bežično otklanjanje grešaka?"</string> <string name="adbwifi_warning_message" msgid="8005936574322702388">"Bežično otklanjanje grešaka je namijenjeno samo u svrhe razvoja aplikacija. Koristite ga za kopiranje podataka između računara i uređaja, instaliranje aplikacija na uređaj bez obavještenja te čitanje podataka iz zapisnika."</string> - <string name="adb_keys_warning_message" msgid="2968555274488101220">"Opozvati pristup otklanjanju grešaka putem uređaja spojenog na USB za sve računare koje ste prethodno ovlastili?"</string> + <string name="adb_keys_warning_message" msgid="2968555274488101220">"Opozvati pristup otklanjanju grešaka putem USB-a za sve računare koje ste prethodno ovlastili?"</string> <string name="dev_settings_warning_title" msgid="8251234890169074553">"Dopustiti postavke za razvoj?"</string> <string name="dev_settings_warning_message" msgid="37741686486073668">"Ove postavke su namijenjene samo za svrhe razvoja. Mogu izazvati pogrešno ponašanje uređaja i aplikacija na njemu."</string> <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Potvrdi aplikacije putem USB-a"</string> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index fe2a7751bead..6b0ae2e24bb6 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -57,7 +57,7 @@ <string name="osu_sign_up_complete" msgid="7640183358878916847">"Anmeldung abgeschlossen. Verbindung wird hergestellt…"</string> <string name="speed_label_very_slow" msgid="8526005255731597666">"Sehr langsam"</string> <string name="speed_label_slow" msgid="6069917670665664161">"Langsam"</string> - <string name="speed_label_okay" msgid="1253594383880810424">"Ok"</string> + <string name="speed_label_okay" msgid="1253594383880810424">"Mittel"</string> <string name="speed_label_medium" msgid="9078405312828606976">"Mittel"</string> <string name="speed_label_fast" msgid="2677719134596044051">"Schnell"</string> <string name="speed_label_very_fast" msgid="8215718029533182439">"Sehr schnell"</string> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 2644cb987e3a..4d7c88287f72 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -465,7 +465,7 @@ <item msgid="8894873528875953317">"50%"</item> <item msgid="7529124349186240216">"100%"</item> </string-array> - <string name="charge_length_format" msgid="6941645744588690932">"Πριν από <xliff:g id="ID_1">%1$s</xliff:g>"</string> + <string name="charge_length_format" msgid="6941645744588690932">"Πριν <xliff:g id="ID_1">%1$s</xliff:g>"</string> <string name="remaining_length_format" msgid="4310625772926171089">"Απομένουν <xliff:g id="ID_1">%1$s</xliff:g>"</string> <string name="screen_zoom_summary_small" msgid="6050633151263074260">"Μικρά"</string> <string name="screen_zoom_summary_default" msgid="1888865694033865408">"Προεπιλογή"</string> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 137116fcac2e..376c2e796f77 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -239,7 +239,7 @@ <string name="keep_screen_on" msgid="1187161672348797558">"Mantendu aktibo"</string> <string name="keep_screen_on_summary" msgid="1510731514101925829">"Pantaila ez da ezarriko inoiz inaktibo kargatu bitartean"</string> <string name="bt_hci_snoop_log" msgid="7291287955649081448">"Gaitu Bluetooth HCI miatze-erregistroa"</string> - <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"Hauteman Bluetooth paketeak (aktibatu edo desaktibatu Bluetooth konexioa ezarpena aldatu ostean)."</string> + <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"Hauteman Bluetooth paketeak (aktibatu edo desaktibatu Bluetooth-a ezarpena aldatu ostean)."</string> <string name="oem_unlock_enable" msgid="5334869171871566731">"OEM desblokeoa"</string> <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Onartu abiarazlea desblokeatzea"</string> <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"OEM desblokeoa onartu nahi duzu?"</string> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 9735c163857d..42ccd5310913 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -347,8 +347,8 @@ <string name="disable_overlays_summary" msgid="1954852414363338166">"Selalu gunakan GPU untuk pengomposisian layar"</string> <string name="simulate_color_space" msgid="1206503300335835151">"Simulasikan ruang warna"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"Aktifkan jejak OpenGL"</string> - <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Perutean audio USB nonaktif"</string> - <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Perutean otomatis ke periferal audio USB nonaktif"</string> + <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Pemilihan rute audio USB nonaktif"</string> + <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Pemilihan rute otomatis ke periferal audio USB nonaktif"</string> <string name="debug_layout" msgid="1659216803043339741">"Tampilkan batas tata letak"</string> <string name="debug_layout_summary" msgid="8825829038287321978">"Tampilkan batas klip, margin, dll."</string> <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"Paksa arah tata letak RTL"</string> diff --git a/packages/SettingsLib/res/values-it/arrays.xml b/packages/SettingsLib/res/values-it/arrays.xml index de38625afcde..30186831b113 100644 --- a/packages/SettingsLib/res/values-it/arrays.xml +++ b/packages/SettingsLib/res/values-it/arrays.xml @@ -22,13 +22,13 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string-array name="wifi_status"> <item msgid="1596683495752107015"></item> - <item msgid="3288373008277313483">"Scansione in corso..."</item> - <item msgid="6050951078202663628">"Connessione..."</item> - <item msgid="8356618438494652335">"Autenticazione..."</item> - <item msgid="2837871868181677206">"Acquisizione indirizzo IP..."</item> + <item msgid="3288373008277313483">"Scansione in corso…"</item> + <item msgid="6050951078202663628">"Connessione…"</item> + <item msgid="8356618438494652335">"Autenticazione…"</item> + <item msgid="2837871868181677206">"Acquisizione indirizzo IP…"</item> <item msgid="4613015005934755724">"Connessa"</item> <item msgid="3763530049995655072">"Sospesa"</item> - <item msgid="7852381437933824454">"Disconnessione..."</item> + <item msgid="7852381437933824454">"Disconnessione…"</item> <item msgid="5046795712175415059">"Disconnessa"</item> <item msgid="2473654476624070462">"Operazione non riuscita"</item> <item msgid="9146847076036105115">"Bloccato"</item> @@ -36,13 +36,13 @@ </string-array> <string-array name="wifi_status_with_ssid"> <item msgid="5969842512724979061"></item> - <item msgid="1818677602615822316">"Scansione in corso..."</item> - <item msgid="8339720953594087771">"Connessione a <xliff:g id="NETWORK_NAME">%1$s</xliff:g>..."</item> - <item msgid="3028983857109369308">"Autenticazione con <xliff:g id="NETWORK_NAME">%1$s</xliff:g>..."</item> - <item msgid="4287401332778341890">"Acquisizione indirizzo IP da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>..."</item> + <item msgid="1818677602615822316">"Scansione in corso…"</item> + <item msgid="8339720953594087771">"Connessione a <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item> + <item msgid="3028983857109369308">"Autenticazione con <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item> + <item msgid="4287401332778341890">"Acquisizione indirizzo IP da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item> <item msgid="1043944043827424501">"Connessa a <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item> <item msgid="7445993821842009653">"Sospesa"</item> - <item msgid="1175040558087735707">"Disconnessione da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>..."</item> + <item msgid="1175040558087735707">"Disconnessione da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item> <item msgid="699832486578171722">"Disconnessa"</item> <item msgid="522383512264986901">"Operazione non riuscita"</item> <item msgid="3602596701217484364">"Bloccato"</item> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index fd5dfa5c9e8b..1120c504717f 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -514,7 +514,7 @@ <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem z połączeniem. Wyłącz i ponownie włącz urządzenie"</string> <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Przewodowe urządzenie audio"</string> <string name="help_label" msgid="3528360748637781274">"Pomoc i opinie"</string> - <string name="storage_category" msgid="2287342585424631813">"Pamięć"</string> + <string name="storage_category" msgid="2287342585424631813">"Pamięć wewnętrzna"</string> <string name="shared_data_title" msgid="1017034836800864953">"Udostępniane dane"</string> <string name="shared_data_summary" msgid="5516326713822885652">"Wyświetl i zmień udostępniane dane"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"Brak udostępnionych danych w przypadku tego użytkownika."</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java index c3993e9063b2..dc9384aa7c5d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java +++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java @@ -19,42 +19,9 @@ import static android.os.UserManager.DISALLOW_CONFIG_TETHERING; import android.content.Context; import android.net.ConnectivityManager; -import android.os.SystemProperties; import android.os.UserHandle; -import android.telephony.CarrierConfigManager; - -import androidx.annotation.VisibleForTesting; public class TetherUtil { - - @VisibleForTesting - static boolean isEntitlementCheckRequired(Context context) { - final CarrierConfigManager configManager = (CarrierConfigManager) context - .getSystemService(Context.CARRIER_CONFIG_SERVICE); - if (configManager == null || configManager.getConfig() == null) { - // return service default - return true; - } - return configManager.getConfig().getBoolean(CarrierConfigManager - .KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL); - } - - public static boolean isProvisioningNeeded(Context context) { - // Keep in sync with other usage of config_mobile_hotspot_provision_app. - // ConnectivityManager#enforceTetherChangePermission - String[] provisionApp = context.getResources().getStringArray( - com.android.internal.R.array.config_mobile_hotspot_provision_app); - if (SystemProperties.getBoolean("net.tethering.noprovisioning", false) - || provisionApp == null) { - return false; - } - // Check carrier config for entitlement checks - if (isEntitlementCheckRequired(context) == false) { - return false; - } - return (provisionApp.length == 2); - } - public static boolean isTetherAvailable(Context context) { final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); final boolean tetherConfigDisallowed = RestrictedLockUtilsInternal diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java index 38eeda245616..898796828131 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -22,10 +22,12 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.hardware.usb.IUsbManager; import android.net.Uri; +import android.os.Environment; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; @@ -146,10 +148,23 @@ public class AppUtils { /** * Returns a boolean indicating whether a given package is a mainline module. */ - public static boolean isMainlineModule(Context context, String packageName) { - final PackageManager pm = context.getPackageManager(); + public static boolean isMainlineModule(PackageManager pm, String packageName) { + // Check if the package is listed among the system modules. try { - return pm.getModuleInfo(packageName, 0 /* flags */) != null; + pm.getModuleInfo(packageName, 0 /* flags */); + return true; + } catch (PackageManager.NameNotFoundException e) { + //pass + } + + try { + final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */); + // Check if the package is contained in an APEX. There is no public API to properly + // check whether a given APK package comes from an APEX registered as module. + // Therefore we conservatively assume that any package scanned from an /apex path is + // a system package. + return pkg.applicationInfo.sourceDir.startsWith( + Environment.getApexDirectory().getAbsolutePath()); } catch (PackageManager.NameNotFoundException e) { return false; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 1d32b873c6fa..6c7e03f104dd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -430,7 +430,7 @@ public class InfoMediaManager extends MediaManager { case TYPE_HEARING_AID: case TYPE_BLUETOOTH_A2DP: final BluetoothDevice device = - BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getOriginalId()); + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getAddress()); final CachedBluetoothDevice cachedDevice = mBluetoothManager.getCachedDeviceManager().findDevice(device); if (cachedDevice != null) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java index 0ca779162ef2..df08809b5601 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java @@ -60,13 +60,6 @@ public class TetherUtilTest { } @Test - public void isEntitlementCheckRequired_noConfigManager_returnTrue() { - doReturn(null).when(mContext).getSystemService(Context.CARRIER_CONFIG_SERVICE); - - assertThat(TetherUtil.isEntitlementCheckRequired(mContext)).isTrue(); - } - - @Test public void isTetherAvailable_supported_configDisallowed_hasUserRestriction_returnTrue() { setupIsTetherAvailable(true, true, true); 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 248eb5b96b92..94d95f06050d 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 @@ -681,7 +681,7 @@ public class InfoMediaManagerTest { assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue(); when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP); - when(route2Info.getOriginalId()).thenReturn("00:00:00:00:00:00"); + when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00"); when(mLocalBluetoothManager.getCachedDeviceManager()) .thenReturn(cachedBluetoothDeviceManager); when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class))) @@ -703,7 +703,7 @@ public class InfoMediaManagerTest { mock(CachedBluetoothDeviceManager.class); when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP); - when(route2Info.getOriginalId()).thenReturn("00:00:00:00:00:00"); + when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00"); when(mLocalBluetoothManager.getCachedDeviceManager()) .thenReturn(cachedBluetoothDeviceManager); when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class))) diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index c9e1944bd6f2..51f69a95e163 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -237,4 +237,7 @@ <!-- Default for Settings.Secure.AWARE_LOCK_ENABLED --> <bool name="def_aware_lock_enabled">false</bool> + + <!-- Default for setting for Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED --> + <bool name="def_hdmiControlAutoDeviceOff">false</bool> </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 75b680dd3a88..bec8151a1351 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -219,7 +219,9 @@ public class SettingsHelper { */ @VisibleForTesting public String getRealValueForSystemSetting(String setting) { - return Settings.System.getString(mContext.getContentResolver(), + // The real value irrespectively of the original setting's namespace is stored in + // Settings.Secure. + return Settings.Secure.getString(mContext.getContentResolver(), setting + SETTING_ORIGINAL_KEY_SUFFIX); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 94509ddcc407..b95d34f2966b 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3513,7 +3513,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 190; + private static final int SETTINGS_VERSION = 191; private final int mUserId; @@ -4867,6 +4867,21 @@ public class SettingsProvider extends ContentProvider { currentVersion = 190; } + if (currentVersion == 190) { + // Version 190: get HDMI auto device off from overlay + final SettingsState globalSettings = getGlobalSettingsLocked(); + final Setting currentSetting = globalSettings.getSettingLocked( + Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED); + if (currentSetting.isNull()) { + globalSettings.insertSettingLocked( + Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, + getContext().getResources().getBoolean( + R.bool.def_hdmiControlAutoDeviceOff) ? "1" : "0", + null, true, SettingsState.SYSTEM_PACKAGE_NAME); + } + currentVersion = 191; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/Shell/res/values-it/strings.xml b/packages/Shell/res/values-it/strings.xml index 18ab908945ac..02531f2fb6e7 100644 --- a/packages/Shell/res/values-it/strings.xml +++ b/packages/Shell/res/values-it/strings.xml @@ -21,7 +21,7 @@ <string name="bugreport_in_progress_title" msgid="4311705936714972757">"Generazione segnalazione di bug <xliff:g id="ID">#%d</xliff:g> in corso"</string> <string name="bugreport_finished_title" msgid="4429132808670114081">"Segnalazione di bug <xliff:g id="ID">#%d</xliff:g> acquisita"</string> <string name="bugreport_updating_title" msgid="4423539949559634214">"Aggiunta di dettagli alla segnalazione di bug"</string> - <string name="bugreport_updating_wait" msgid="3322151947853929470">"Attendi..."</string> + <string name="bugreport_updating_wait" msgid="3322151947853929470">"Attendi…"</string> <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"La segnalazione di bug comparirà a breve sul telefono"</string> <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Seleziona per condividere la segnalazione di bug"</string> <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Tocca per condividere la segnalazione di bug"</string> diff --git a/packages/Shell/res/values-ky/strings.xml b/packages/Shell/res/values-ky/strings.xml index 969e9ed0654e..3567ac276e63 100644 --- a/packages/Shell/res/values-ky/strings.xml +++ b/packages/Shell/res/values-ky/strings.xml @@ -29,7 +29,7 @@ <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Мүчүлүштүк тууралуу билдирүүңүздү скриншотсуз бөлүшүү үчүн таптап коюңуз же скриншот даяр болгуча күтө туруңуз"</string> <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Мүчүлүштүк тууралуу билдирүүңүздү скриншотсуз бөлүшүү үчүн таптап коюңуз же скриншот даяр болгуча күтө туруңуз"</string> <string name="bugreport_confirm" msgid="5917407234515812495">"Мүчүлүштүктөр тууралуу билдирүүлөрдө тутумдун ар кандай таржымалдарынан алынган дайындар, ошондой эле купуя маалымат камтылышы мүмкүн (мисалы, жайгашкан жер сыяктуу). Мындай билдирүүлөрдү бир гана ишеничтүү адамдар жана колдонмолор менен бөлүшүңүз."</string> - <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Экинчи көрсөтүлбөсүн"</string> + <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Экинчи көрүнбөсүн"</string> <string name="bugreport_storage_title" msgid="5332488144740527109">"Мүчүлүштүктөрдү кабарлоо"</string> <string name="bugreport_unreadable_text" msgid="586517851044535486">"Мүчүлүштүк тууралуу кабарлаган файл окулбай койду"</string> <string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"Мүчүлүштүктөр жөнүндө кабардын чоо-жайы zip файлына кошулбай койду"</string> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 985269b2bb75..5b6155180e0a 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -263,6 +263,9 @@ <!-- Restore settings (used by QS) even if they have been modified --> <uses-permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE" /> + <!-- Permission to make accessibility service access Bubbles --> + <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY" /> + <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" /> diff --git a/packages/SystemUI/docs/broadcasts.md b/packages/SystemUI/docs/broadcasts.md index 6c8488b332c2..8ec20f5689ff 100644 --- a/packages/SystemUI/docs/broadcasts.md +++ b/packages/SystemUI/docs/broadcasts.md @@ -62,7 +62,7 @@ Acquire the dispatcher by using `@Inject` to obtain a `BroadcastDispatcher`. The * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an * executor in the main thread (default). * @param user A user handle to determine which broadcast should be dispatched to this receiver. - * By default, it is the current user. + * By default, it is the user of the context (system user in SystemUI). * @throws IllegalArgumentException if the filter has other constraints that are not actions or * categories or the filter has no actions. */ diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index 85a9fec859f3..fffcafbf88fb 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -34,7 +34,7 @@ public interface QS extends FragmentBase { String ACTION = "com.android.systemui.action.PLUGIN_QS"; - int VERSION = 7; + int VERSION = 8; String TAG = "QS"; @@ -67,15 +67,12 @@ public interface QS extends FragmentBase { } /** - * We need this to handle nested scrolling for QS.. - * Normally we would do this with requestDisallowInterceptTouchEvent, but when both the - * scroll containers are using the same touch slop, they try to start scrolling at the - * same time and NotificationPanelView wins, this lets QS win. - * - * TODO: Do this using NestedScroll capabilities. + * Should touches from the notification panel be disallowed? + * The notification panel might grab any touches rom QS at any time to collapse the shade. + * We should disallow that in case we are showing the detail panel. */ - default boolean onInterceptTouchEvent(MotionEvent event) { - return isCustomizing(); + default boolean disallowPanelTouches() { + return isShowingDetail(); } @ProvidesInterface(version = HeightListener.VERSION) diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml index 80deb34656a0..fe460e38dc0b 100644 --- a/packages/SystemUI/res-keyguard/values-it/strings.xml +++ b/packages/SystemUI/res-keyguard/values-it/strings.xml @@ -48,7 +48,7 @@ <string name="keyguard_permanent_disabled_sim_instructions" msgid="2490584154727897806">"La scheda SIM è stata disattivata definitivamente.\n Contatta il fornitore del tuo servizio wireless per ricevere un\'altra scheda SIM."</string> <string name="keyguard_sim_locked_message" msgid="4343544458476911044">"La SIM è bloccata."</string> <string name="keyguard_sim_puk_locked_message" msgid="6253830777745450550">"La SIM è bloccata tramite PUK."</string> - <string name="keyguard_sim_unlock_progress_dialog_message" msgid="2394023844117630429">"Sblocco SIM..."</string> + <string name="keyguard_sim_unlock_progress_dialog_message" msgid="2394023844117630429">"Sblocco SIM…"</string> <string name="keyguard_accessibility_pin_area" msgid="7403009340414014734">"Area PIN"</string> <string name="keyguard_accessibility_password" msgid="3524161948484801450">"Password del dispositivo"</string> <string name="keyguard_accessibility_sim_pin_area" msgid="6272116591533888062">"Area PIN SIM"</string> @@ -77,7 +77,7 @@ <string name="kg_puk_enter_puk_hint_multi" msgid="4876780689904862943">"La SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\" non è attiva al momento. Inserisci il codice PUK per continuare. Contatta l\'operatore per avere informazioni dettagliate."</string> <string name="kg_puk_enter_pin_hint" msgid="6028432138916150399">"Inserisci il codice PIN desiderato"</string> <string name="kg_enter_confirm_pin_hint" msgid="4261064020391799132">"Conferma il codice PIN desiderato"</string> - <string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"Sblocco SIM..."</string> + <string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"Sblocco SIM…"</string> <string name="kg_invalid_sim_pin_hint" msgid="2762202646949552978">"Il PIN deve essere di 4-8 numeri."</string> <string name="kg_invalid_sim_puk_hint" msgid="5319756880543857694">"Il codice PUK dovrebbe avere almeno otto numeri."</string> <string name="kg_invalid_puk" msgid="1774337070084931186">"Inserisci di nuovo il codice PUK corretto. Ripetuti tentativi comportano la disattivazione definitiva della scheda SIM."</string> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 67c4458de2f2..5f2a946a1b6d 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -129,7 +129,7 @@ </style> <style name="TextAppearance.Keyguard.BottomArea"> - <item name="android:textSize">16sp</item> + <item name="android:textSize">14sp</item> <item name="android:maxLines">1</item> <item name="android:textColor">?attr/wallpaperTextColor</item> <item name="android:shadowColor">@color/keyguard_shadow_color</item> diff --git a/packages/SystemUI/res-product/values-ne/strings.xml b/packages/SystemUI/res-product/values-ne/strings.xml index 81a063c721cf..148cb51fd902 100644 --- a/packages/SystemUI/res-product/values-ne/strings.xml +++ b/packages/SystemUI/res-product/values-ne/strings.xml @@ -21,7 +21,7 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="dock_alignment_slow_charging" product="default" msgid="6997633396534416792">"अझ छिटो चार्ज गर्न फोनलाई फेरि मिलाउनुहोस्"</string> <string name="dock_alignment_not_charging" product="default" msgid="3980752926226749808">"तारविनै चार्ज गर्न फोनलाई फेरि मिलाउनुहोस्"</string> - <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Android TV यन्त्र चाँडै निष्क्रिय हुने छ; सक्रिय राख्न कुनै बटन थिच्नुहोस्।"</string> + <string name="inattentive_sleep_warning_message" product="tv" msgid="6844464574089665063">"Android टिभी यन्त्र चाँडै निष्क्रिय हुने छ; सक्रिय राख्न कुनै बटन थिच्नुहोस्।"</string> <string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"यो यन्त्र चाँडै निष्क्रिय हुने छ; सक्रिय राख्न थिच्नुहोस्।"</string> <string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"ट्याब्लेटमा SIM कार्ड छैन।"</string> <string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"फोनमा SIM कार्ड छैन।"</string> diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml index 7313d54c599a..0f561cb933e6 100644 --- a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml +++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml @@ -41,7 +41,8 @@ android:paddingStart="16dp" android:paddingBottom="16dp" android:fontFamily="@*android:string/config_bodyFontFamilyMedium" - android:maxLines="1" + android:maxLines="2" + android:ellipsize="end" android:text="@string/bubbles_user_education_manage_title" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/> @@ -52,6 +53,8 @@ android:paddingStart="16dp" android:paddingBottom="24dp" android:text="@string/bubbles_user_education_manage" + android:maxLines="2" + android:ellipsize="end" android:fontFamily="@*android:string/config_bodyFontFamily" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/> diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml index a0d8ae42f584..4850e7534943 100644 --- a/packages/SystemUI/res/layout/controls_management_favorites.xml +++ b/packages/SystemUI/res/layout/controls_management_favorites.xml @@ -26,6 +26,8 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/controls_management_list_margin" android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_marginStart="@dimen/controls_management_status_side_margin" + android:layout_marginEnd="@dimen/controls_management_status_side_margin" android:gravity="center_horizontal" /> diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 6a235218b32f..ef7325ea8f38 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -48,4 +48,18 @@ android:visibility="gone" android:pointerIcon="crosshair"/> <include layout="@layout/global_screenshot_static"/> + <FrameLayout + android:id="@+id/global_screenshot_dismiss_button" + android:layout_width="@dimen/screenshot_dismiss_button_tappable_size" + android:layout_height="@dimen/screenshot_dismiss_button_tappable_size" + android:elevation="7dp" + android:visibility="gone" + android:contentDescription="@string/screenshot_dismiss_ui_description"> + <ImageView + android:id="@+id/global_screenshot_dismiss_image" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="@dimen/screenshot_dismiss_button_margin" + android:src="@drawable/screenshot_cancel"/> + </FrameLayout> </FrameLayout> diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml index da5277ce3876..9ec2f20597e7 100644 --- a/packages/SystemUI/res/layout/global_screenshot_static.xml +++ b/packages/SystemUI/res/layout/global_screenshot_static.xml @@ -54,22 +54,4 @@ android:layout_height="wrap_content"/> </HorizontalScrollView> <include layout="@layout/global_screenshot_preview"/> - <FrameLayout - android:id="@+id/global_screenshot_dismiss_button" - android:layout_width="@dimen/screenshot_dismiss_button_tappable_size" - android:layout_height="@dimen/screenshot_dismiss_button_tappable_size" - android:elevation="7dp" - android:visibility="gone" - android:contentDescription="@string/screenshot_dismiss_ui_description" - app:layout_constraintStart_toEndOf="@+id/global_screenshot_preview" - app:layout_constraintEnd_toEndOf="@+id/global_screenshot_preview" - app:layout_constraintTop_toTopOf="@+id/global_screenshot_preview" - app:layout_constraintBottom_toTopOf="@+id/global_screenshot_preview"> - <ImageView - android:id="@+id/global_screenshot_dismiss_image" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_margin="@dimen/screenshot_dismiss_button_margin" - android:src="@drawable/screenshot_cancel"/> - </FrameLayout> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/home_handle.xml b/packages/SystemUI/res/layout/home_handle.xml index 7c5db105a09e..54a0b9fba39c 100644 --- a/packages/SystemUI/res/layout/home_handle.xml +++ b/packages/SystemUI/res/layout/home_handle.xml @@ -21,6 +21,7 @@ android:layout_width="@dimen/navigation_home_handle_width" android:layout_height="match_parent" android:layout_weight="0" + android:contentDescription="@string/accessibility_home" android:paddingStart="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding" /> diff --git a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml index 21a671cf9d5a..214c44a41c5c 100644 --- a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml +++ b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml @@ -20,24 +20,26 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical|start" + android:paddingTop="1dp" + android:paddingBottom="1dp" android:paddingEnd="12dp"> <FrameLayout android:layout_width="@*android:dimen/conversation_content_start" - android:layout_height="36dp" + android:layout_height="25dp" > <ImageView android:id="@*android:id/conversation_icon" - android:layout_width="24dp" - android:layout_height="24dp" + android:layout_width="20dp" + android:layout_height="20dp" android:layout_gravity="center" /> <ViewStub android:id="@*android:id/conversation_face_pile" android:layout="@*android:layout/conversation_face_pile_layout" - android:layout_width="36dp" - android:layout_height="36dp" + android:layout_width="25dp" + android:layout_height="25dp" android:layout_gravity="center" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index b90a3719b08f..04de9784812f 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -42,6 +42,17 @@ android:textAppearance="@style/TextAppearance.Keyguard.BottomArea" android:accessibilityLiveRegion="polite"/> + <com.android.systemui.statusbar.phone.KeyguardIndicationTextView + android:id="@+id/keyguard_indication_enterprise_disclosure" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingStart="@dimen/keyguard_indication_text_padding" + android:paddingEnd="@dimen/keyguard_indication_text_padding" + android:textAppearance="@style/TextAppearance.Keyguard.BottomArea" + android:alpha=".54" + android:visibility="gone"/> + </LinearLayout> <FrameLayout diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml index d721818d6e54..a722fbfce19a 100644 --- a/packages/SystemUI/res/layout/media_view.xml +++ b/packages/SystemUI/res/layout/media_view.xml @@ -39,7 +39,7 @@ android:layout_alignParentLeft="true" android:fontFamily="@*android:string/config_bodyFontFamily" android:textColor="@color/media_primary_text" - android:gravity="left" + android:gravity="start" android:textSize="14sp" /> <TextView @@ -49,7 +49,7 @@ android:layout_alignParentRight="true" android:fontFamily="@*android:string/config_bodyFontFamily" android:textColor="@color/media_primary_text" - android:gravity="right" + android:gravity="end" android:textSize="14sp" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml index 294bd50fcf8b..59e1a755d7d2 100644 --- a/packages/SystemUI/res/layout/qs_detail.xml +++ b/packages/SystemUI/res/layout/qs_detail.xml @@ -22,6 +22,7 @@ android:background="@drawable/qs_detail_background" android:clickable="true" android:orientation="vertical" + android:layout_marginTop="@*android:dimen/quick_qs_offset_height" android:paddingBottom="8dp" android:visibility="invisible" android:elevation="4dp" @@ -44,7 +45,7 @@ android:scaleType="fitXY" /> - <com.android.systemui.qs.NonInterceptingScrollView + <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" @@ -54,7 +55,7 @@ android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent"/> - </com.android.systemui.qs.NonInterceptingScrollView> + </ScrollView> <include layout="@layout/qs_detail_buttons" /> diff --git a/packages/SystemUI/res/layout/qs_divider.xml b/packages/SystemUI/res/layout/qs_divider.xml deleted file mode 100644 index 39d48ea4746e..000000000000 --- a/packages/SystemUI/res/layout/qs_divider.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 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. ---> -<View xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="1dp" - android:alpha=".12" - android:background="?android:attr/colorForeground" /> diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index ebfd0a0fd537..5c00af5705e9 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -61,7 +61,10 @@ android:clickable="true" android:gravity="center_vertical" android:focusable="true" + android:singleLine="true" + android:ellipsize="end" android:textAppearance="@style/TextAppearance.QS.Status" + android:layout_marginEnd="4dp" android:visibility="gone"/> </com.android.keyguard.AlphaOptimizedLinearLayout> diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml index a8960d9b9437..5c8b2b08324f 100644 --- a/packages/SystemUI/res/layout/qs_paged_page.xml +++ b/packages/SystemUI/res/layout/qs_paged_page.xml @@ -20,7 +20,5 @@ android:id="@+id/tile_page" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingStart="@dimen/notification_side_paddings" - android:paddingEnd="@dimen/notification_side_paddings" android:clipChildren="false" android:clipToPadding="false" /> diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index cdf84260e399..761ab03ee87e 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -48,18 +48,22 @@ android:clipChildren="false" android:background="@drawable/qs_bg_gradient" /> - - <com.android.systemui.qs.QSPanel - android:id="@+id/quick_settings_panel" - android:layout_marginTop="@*android:dimen/quick_qs_offset_height" + <com.android.systemui.qs.NonInterceptingScrollView + android:id="@+id/expanded_qs_scroll_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="4dp" - android:background="@android:color/transparent" - android:focusable="true" - android:accessibilityTraversalBefore="@android:id/edit"> - <include layout="@layout/qs_footer_impl" /> - </com.android.systemui.qs.QSPanel> + android:layout_weight="1"> + <com.android.systemui.qs.QSPanel + android:id="@+id/quick_settings_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:focusable="true" + android:accessibilityTraversalBefore="@android:id/edit"> + <include layout="@layout/qs_footer_impl" /> + </com.android.systemui.qs.QSPanel> + </com.android.systemui.qs.NonInterceptingScrollView> <include layout="@layout/quick_status_bar_expanded_header" /> diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml index 15f398aa52e6..e7c7b5fbf890 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer.xml @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" +<com.android.systemui.util.NeverExactlyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:clickable="true" android:paddingBottom="@dimen/qs_tile_padding_top" @@ -23,23 +23,28 @@ android:paddingStart="@dimen/qs_footer_padding_start" android:paddingEnd="@dimen/qs_footer_padding_end" android:gravity="center_vertical" + android:layout_gravity="center_vertical|center_horizontal" android:background="@android:color/transparent"> - <TextView + <com.android.systemui.util.AutoMarqueeTextView android:id="@+id/footer_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="start" android:layout_weight="1" + android:singleLine="true" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" android:textAppearance="@style/TextAppearance.QS.TileLabel" - style="@style/qs_security_footer"/> + android:textColor="?android:attr/textColorPrimary"/> <ImageView android:id="@+id/footer_icon" android:layout_width="@dimen/qs_footer_icon_size" android:layout_height="@dimen/qs_footer_icon_size" + android:layout_marginStart="8dp" android:contentDescription="@null" android:src="@drawable/ic_info_outline" - style="@style/qs_security_footer"/> + android:tint="?android:attr/textColorPrimary" /> -</LinearLayout> +</com.android.systemui.util.NeverExactlyLinearLayout> diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml index e6ef9b4b902c..fb82304663aa 100644 --- a/packages/SystemUI/res/layout/quick_settings_header_info.xml +++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml @@ -20,11 +20,12 @@ android:layout_height="@dimen/qs_header_tooltip_height" android:layout_below="@id/quick_status_bar_system_icons" android:visibility="invisible" - android:theme="@style/QSHeaderTheme"> + android:theme="@style/QSHeaderTheme" + android:forceHasOverlappingRendering="false"> <com.android.systemui.qs.QSHeaderInfoLayout android:id="@+id/status_container" - android:layout_width="match_parent" + android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent"> diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index abeb33111c40..dc34127496f6 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -29,7 +29,6 @@ android:clipToPadding="false" android:paddingTop="0dp" android:paddingEnd="0dp" - android:paddingBottom="10dp" android:paddingStart="0dp" android:elevation="4dp" > @@ -52,6 +51,7 @@ android:clipChildren="false" android:clipToPadding="false" android:focusable="true" + android:paddingBottom="10dp" android:importantForAccessibility="yes" /> <TextView diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml index fd9936f6b8ea..acef94315e91 100644 --- a/packages/SystemUI/res/layout/screen_record_dialog.xml +++ b/packages/SystemUI/res/layout/screen_record_dialog.xml @@ -20,111 +20,126 @@ android:orientation="vertical" android:background="@drawable/rounded_bg_full"> - <!-- Header --> - <LinearLayout + <!-- Scrollview is necessary to fit everything in landscape layout --> + <ScrollView android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:gravity="center" - android:padding="@dimen/screenrecord_dialog_padding"> - <ImageView - android:layout_width="@dimen/screenrecord_logo_size" - android:layout_height="@dimen/screenrecord_logo_size" - android:src="@drawable/ic_screenrecord" - android:tint="@color/GM2_red_500" - android:layout_marginBottom="@dimen/screenrecord_dialog_padding"/> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceLarge" - android:text="@string/screenrecord_start_label"/> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/screenrecord_description" - android:textAppearance="?android:attr/textAppearanceSmall" - android:paddingTop="@dimen/screenrecord_dialog_padding" - android:paddingBottom="@dimen/screenrecord_dialog_padding"/> + android:layout_height="wrap_content"> - <!-- Options --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal"> - <ImageView - android:layout_width="@dimen/screenrecord_logo_size" - android:layout_height="@dimen/screenrecord_logo_size" - android:src="@drawable/ic_mic_26dp" - android:tint="@color/GM2_grey_700" - android:layout_gravity="center" - android:layout_weight="0" - android:layout_marginRight="@dimen/screenrecord_dialog_padding"/> - <Spinner - android:id="@+id/screen_recording_options" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:prompt="@string/screenrecord_audio_label"/> - <Switch - android:layout_width="wrap_content" - android:layout_height="48dp" - android:layout_weight="1" - android:layout_gravity="end" - android:id="@+id/screenrecord_audio_switch"/> - </LinearLayout> + android:orientation="vertical"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <ImageView - android:layout_width="@dimen/screenrecord_logo_size" - android:layout_height="@dimen/screenrecord_logo_size" - android:src="@drawable/ic_touch" - android:tint="@color/GM2_grey_700" - android:layout_gravity="center" - android:layout_marginRight="@dimen/screenrecord_dialog_padding"/> - <Switch + <!-- Header --> + <LinearLayout android:layout_width="match_parent" - android:layout_height="48dp" - android:id="@+id/screenrecord_taps_switch" - android:text="@string/screenrecord_taps_label" - android:textColor="?android:attr/textColorPrimary" - android:textAppearance="?android:attr/textAppearanceSmall"/> + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center" + android:padding="@dimen/screenrecord_dialog_padding"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_screenrecord" + android:tint="@color/GM2_red_500" + android:layout_marginBottom="@dimen/screenrecord_dialog_padding"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:text="@string/screenrecord_start_label"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/screenrecord_description" + android:textAppearance="?android:attr/textAppearanceSmall" + android:paddingTop="@dimen/screenrecord_dialog_padding" + android:paddingBottom="@dimen/screenrecord_dialog_padding"/> - </LinearLayout> - </LinearLayout> + <!-- Options --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_mic_26dp" + android:tint="@color/GM2_grey_700" + android:layout_gravity="center" + android:layout_weight="0" + android:layout_marginRight="@dimen/screenrecord_dialog_padding"/> + <Spinner + android:id="@+id/screen_recording_options" + android:layout_width="0dp" + android:layout_height="48dp" + android:layout_weight="1" + android:prompt="@string/screenrecord_audio_label"/> + <Switch + android:layout_width="wrap_content" + android:minWidth="48dp" + android:layout_height="48dp" + android:layout_weight="0" + android:layout_gravity="end" + android:contentDescription="@string/screenrecord_audio_label" + android:id="@+id/screenrecord_audio_switch"/> + </LinearLayout> - <!-- hr --> - <View - android:layout_width="match_parent" - android:layout_height="1dp" - android:background="@color/GM2_grey_300"/> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <ImageView + android:layout_width="@dimen/screenrecord_logo_size" + android:layout_height="@dimen/screenrecord_logo_size" + android:src="@drawable/ic_touch" + android:tint="@color/GM2_grey_700" + android:layout_gravity="center" + android:layout_marginRight="@dimen/screenrecord_dialog_padding"/> + <Switch + android:layout_width="match_parent" + android:layout_height="48dp" + android:id="@+id/screenrecord_taps_switch" + android:text="@string/screenrecord_taps_label" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="?android:attr/textAppearanceSmall"/> - <!-- Buttons --> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:padding="@dimen/screenrecord_dialog_padding"> - <Button - android:id="@+id/button_cancel" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_weight="0" - android:layout_gravity="start" - android:text="@string/cancel" - style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"/> - <Space - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1"/> - <Button - android:id="@+id/button_start" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_weight="0" - android:layout_gravity="end" - android:text="@string/screenrecord_start" - style="@android:style/Widget.DeviceDefault.Button.Colored"/> - </LinearLayout> + </LinearLayout> + </LinearLayout> + + <!-- hr --> + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/GM2_grey_300"/> + + <!-- Buttons --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="@dimen/screenrecord_dialog_padding"> + <Button + android:id="@+id/button_cancel" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="0" + android:layout_gravity="start" + android:text="@string/cancel" + style="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"/> + <Space + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1"/> + <Button + android:id="@+id/button_start" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_weight="0" + android:layout_gravity="end" + android:text="@string/screenrecord_start" + style="@android:style/Widget.DeviceDefault.Button.Colored"/> + </LinearLayout> + </LinearLayout> + </ScrollView> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 88102b2adc94..48b25b0773e4 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Bestuur"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Geskiedenis"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Inkomend"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nuut"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Stil"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Kennisgewings"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Gesprekke"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Beweeg na regs bo"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Beweeg na links onder"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Beweeg na regs onder"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Maak borrel toe"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Moenie dat gesprek \'n borrel word nie"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Klets met borrels"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nuwe gesprekke verskyn as swerwende ikone, of borrels Tik op borrel om dit oop te maak. Sleep om dit te skuif."</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 66c0131d6671..9a75c1c0879c 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"ያቀናብሩ"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ታሪክ"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"ገቢ"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"አዲስ"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"ጸጥ ያለ"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"ማሳወቂያዎች"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"ውይይቶች"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ወደ ላይኛው ቀኝ አንቀሳቅስ"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"የግርጌውን ግራ አንቀሳቅስ"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"ታችኛውን ቀኝ ያንቀሳቅሱ"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"አረፋን አሰናብት"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"ውይይቶችን በአረፋ አታሳይ"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"አረፋዎችን በመጠቀም ይወያዩ"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"አዲስ ውይይቶች እንደ ተንሳፋፊ አዶዎች ወይም አረፋዎች ሆነው ይታያሉ። አረፋን ለመክፈት መታ ያድርጉ። ለመውሰድ ይጎትቱት።"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index ebf4bc80b689..bfa21071d815 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -522,7 +522,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"إدارة"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"السجلّ"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"الإشعارات الواردة"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"الإشعارات الجديدة"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"صامت"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"الإشعارات"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"المحادثات"</string> @@ -607,7 +607,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"تم تثبيت الشاشة على التطبيق"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. المس مع الاستمرار الزرين \"رجوع\" و\"نظرة عامة\" لإزالة التثبيت."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. المس مع الاستمرار الزرين \"رجوع\" و\"الشاشة الرئيسية\" لإزالة التثبيت."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. مرّر الشاشة بسرعة للأعلى مع الاستمرار لإزالة تثبيت الشاشة."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"يؤدي هذا الإجراء إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. لإلغاء تثبيت الشاشة على هذا التطبيق، اسحب بسرعة للأعلى مع إبقاء الإصبع على الشاشة."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. المس مع الاستمرار زر \"نظرة عامة\" لإزالة التثبيت."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. المس مع الاستمرار زر \"الشاشة الرئيسية\" لإزالة التثبيت."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"يمكن الوصول إلى البيانات الشخصية (مثلاً جهات الاتصال ومحتوى الرسائل الإلكترونية)"</string> @@ -1022,8 +1022,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"الانتقال إلى أعلى اليسار"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"نقل إلى أسفل يمين الشاشة"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"نقل إلى أسفل اليسار"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"إغلاق فقاعة المحادثة"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"عدم عرض المحادثة كفقاعة محادثة"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"الدردشة باستخدام فقاعات المحادثات"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"تظهر المحادثات الجديدة كرموز عائمة أو كفقاعات. انقر لفتح فقاعة المحادثة، واسحبها لتحريكها."</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index c3d1e78f74e5..ca21ff674595 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"সকলো মচক"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"অন্তৰ্গামী"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"নতুন"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"নীৰৱ"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"জাননীসমূহ"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"বাৰ্তালাপ"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"শীৰ্ষৰ সোঁফালে নিয়ক"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"বুটামটো বাওঁফালে নিয়ক"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"তলৰ সোঁফালে নিয়ক"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"বাবল অগ্ৰাহ্য কৰক"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"বাৰ্তালাপ বাবল নকৰিব"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Bubbles ব্যৱহাৰ কৰি চাট কৰক"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"নতুন বাৰ্তালাপ উপঙি থকা চিহ্নসমূহ অথবা bubbles হিচাপে প্ৰদর্শিত হয়। Bubbles খুলিবলৈ টিপক। এইটো স্থানান্তৰ কৰিবলৈ টানি নিয়ক।"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 3919fe314361..3a7c98ca34cf 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"İdarə edin"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Tarixçə"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Gələn"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Yeni"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Səssiz"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Bildirişlər"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Söhbətlər"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Yuxarıya sağa köçürün"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Aşağıya sola köçürün"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Aşağıya sağa köçürün"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Qabarcığı ləğv edin"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Söhbətdən gələn bildirişi göstərməyin"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Yumrucuqlardan istifadə edərək söhbət edin"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Yeni söhbətlər üzən nişanlar və ya yumrucuqlar kimi görünür. Yumrucuğu açmaq üçün toxunun. Hərəkət etdirmək üçün sürüşdürün."</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index f2a6d65bd051..cdface92a34c 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -513,7 +513,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Dolazno"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Novo"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Nečujno"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Obaveštenja"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Konverzacije"</string> @@ -598,7 +598,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Aplikacija je zakačena"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Na ovaj način se ovo stalno prikazuje dok ga ne otkačite. Dodirnite i zadržite Nazad i Pregled da biste ga otkačili."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Na ovaj način se ovo stalno prikazuje dok ga ne otkačite. Dodirnite i zadržite Nazad i Početna da biste ga otkačili."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Na ovaj način se stalno prikazuje dok ga ne otkačite. Prevucite nagore i zadržite da biste ga otkačili."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Stalno će se prikazivati dok je ne otkačite. Prevucite nagore i zadržite da biste je otkačili."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Na ovaj način se ovo stalno prikazuje dok ga ne otkačite. Dodirnite i zadržite Pregled da biste ga otkačili."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Na ovaj način se ovo stalno prikazuje dok ga ne otkačite. Dodirnite i zadržite Početna da biste ga otkačili."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Mogu da budu dostupni lični podaci (kao što su kontakti i sadržaj imejlova)."</string> @@ -1007,8 +1007,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Premesti gore desno"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Premesti dole levo"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Premesti dole desno"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Odbacivanje oblačića"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Ne koristi oblačiće za konverzaciju"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Ćaskajte u oblačićima"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nove konverzacije se prikazuju kao plutajuće ikone ili oblačići. Dodirnite da biste otvorili oblačić. Prevucite da biste ga premestili."</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index d34f089ec9aa..32ec1841ea3e 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -516,7 +516,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Кіраваць"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Гісторыя"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Уваходныя"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Новае"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Без гуку"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Апавяшчэнні"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Размовы"</string> @@ -601,7 +601,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Праграма замацавана"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Будзе паказвацца, пакуль не адмацуеце. Каб адмацаваць, краніце і ўтрымлівайце кнопкі \"Назад\" і \"Агляд\"."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Будзе паказвацца, пакуль не адмацуеце. Каб адмацаваць, націсніце і ўтрымлівайце кнопкі \"Назад\" і \"Галоўны экран\"."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Будзе паказвацца, пакуль не адмацуеце Каб адмацаваць, прагартайце ўверх, утрымліваючы палец на экране"</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Будзе паказвацца, пакуль не адмацуеце. Каб адмацаваць, прагартайце ўверх, утрымліваючы палец на экране."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Будзе паказвацца, пакуль не адмацуеце. Каб адмацаваць, краніце і ўтрымлівайце кнопку \"Агляд\"."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Будзе паказвацца, пакуль не адмацуеце. Каб адмацаваць, націсніце і ўтрымлівайце кнопку \"Галоўны экран\"."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Могуць быць даступныя асабістыя даныя (напрыклад, кантакты і змесціва электроннай пошты)."</string> @@ -1012,8 +1012,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Перамясціце правей і вышэй"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Перамясціць лявей і ніжэй"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Перамясціць правей і ніжэй"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Адхіліць апавяшчэнне"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Не паказваць размову ў выглядзе ўсплывальных апавяшчэнняў"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Усплывальныя апавяшчэнні"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Новыя размовы будуць паказвацца як рухомыя значкі ці ўсплывальныя апавяшчэнні. Націсніце, каб адкрыць усплывальнае апавяшчэнне. Перацягніце яго, каб перамясціць."</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 2388cf6167c0..a2a2671b3f70 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -92,7 +92,7 @@ <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Записът на екрана се обработва"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущо известие за сесия за записване на екрана"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Да се стартира ли записът?"</string> - <string name="screenrecord_description" msgid="1123231719680353736">"По време на записване системата Android може да прихване поверителна информация, която е показана на екрана или възпроизвеждана на устройството ви. Това включва пароли, данни за плащане, снимки, съобщения и аудио."</string> + <string name="screenrecord_description" msgid="1123231719680353736">"По време на записване системата Android може да запише и поверителна информация, която е показана на екрана или възпроизвеждана на устройството ви. Това включва пароли, данни за плащане, снимки, съобщения и аудио."</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Записване на звук"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Аудио от устройството"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Звук от устройството ви, като например музика, обаждания и мелодии"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Управление"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Входящи"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Нови"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Беззвучни"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Известия"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Разговори"</string> @@ -1002,10 +1002,9 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Преместване горе вдясно"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Преместване долу вляво"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Преместване долу вдясно"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Балонче: Отхвърляне"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Без балончета за разговора"</string> - <string name="bubbles_user_education_title" msgid="5547017089271445797">"Разговаряне чрез балончета"</string> + <string name="bubbles_user_education_title" msgid="5547017089271445797">"Чат с балончета"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Новите разговори се показват като плаващи икони, или балончета. Докоснете балонче, за да го отворите, или го плъзнете, за да го преместите."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Управление на балончетата по всяко време"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Докоснете „Управление“, за да изключите балончетата от това приложение"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index d5e827f6b618..7a9fecad5f9d 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"সবকিছু সাফ করুন"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"পরিচালনা করুন"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"ইনকামিং"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"নতুন"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"আওয়াজ করবে না"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"বিজ্ঞপ্তি"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"কথোপকথন"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"অ্যাপ পিন করা হয়েছে"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"এটি আপনি আনপিন না করা পর্যন্ত এটিকে প্রদর্শিত করবে৷ আনপিন করতে ফিরুন এবং ওভারভিউ স্পর্শ করে ধরে থাকুন।"</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"এর ফলে আপনি এটি আনপিন না করা পর্যন্ত এটি দেখানো হতে থাকবে। আনপিন করতে \"ফিরে যান\" এবং \"হোম\" বোতামদুটি ট্যাপ করে ধরে রাখুন।"</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"এর ফলে আপনি আনপিন না করা পর্যন্ত এটি দেখানো হতে থাকবে। আনপিন করার জন্য উপরের দিকে সোয়াইপ করে ধরে থাকুন"</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"এর ফলে আপনি আনপিন না করা পর্যন্ত এটি দেখানো হবে। আনপিন করার জন্য উপরের দিকে সোয়াইপ করে ধরে থাকুন।"</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"এটি আপনি আনপিন না করা পর্যন্ত এটিকে প্রদর্শিত করবে৷ আনপিন করতে ওভারভিউ স্পর্শ করে ধরে থাকুন৷"</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"এর ফলে আপনি এটি আনপিন না করা পর্যন্ত এটি দেখানো হতে থাকবে। আনপিন করতে \"হোম\" বোতামটি ট্যাপ করে ধরে রাখুন।"</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"ব্যক্তিগত তথ্য অ্যাক্সেস করা যেতে পারে (যেমন, পরিচিতি ও ইমেল কন্টেন্ট)।"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"উপরে ডানদিকে সরান"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"নিচে বাঁদিকে সরান"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"নিচে ডান দিকে সরান"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"বাবল খারিজ করুন"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"কথোপকথন বাবল হিসেবে দেখাবে না"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"বাবল ব্যবহার করে চ্যাট করুন"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"নতুন কথোপকথন ভেসে থাকা আইকন বা বাবল হিসেবে দেখানো হয়। বাবল খুলতে ট্যাপ করুন। সেটি সরাতে ধরে টেনে আনুন।"</string> @@ -1068,7 +1067,7 @@ <string name="controls_error_retryable" msgid="864025882878378470">"সমস্যা, আবার চেষ্টা করা হচ্ছে…"</string> <string name="controls_error_removed" msgid="6675638069846014366">"খুঁজে পাওয়া যায়নি"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"কন্ট্রোল উপলভ্য নেই"</string> - <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> ডিভাইস অ্যাক্সেস করা যায়নি। <xliff:g id="APPLICATION">%2$s</xliff:g> অ্যাপ চেক করে দেখুন যাতে এটি নিশ্চিত করে নিতে পারেন যে কন্ট্রোল এখনও উপলভ্য আছে এবং অ্যাপ সেটিংসে কোনও পরিবর্তন করা হয়নি।"</string> + <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> ডিভাইস অ্যাক্সেস করা যায়নি। কন্ট্রোল এখনও উপলভ্য আছে কিনা ও অ্যাপ সেটিংসে কোনও পরিবর্তন করা হয়েছে কিনা তা ভাল করে দেখে নিতে <xliff:g id="APPLICATION">%2$s</xliff:g> অ্যাপ চেক করুন।"</string> <string name="controls_open_app" msgid="483650971094300141">"অ্যাপ খুলুন"</string> <string name="controls_error_generic" msgid="352500456918362905">"স্ট্যাটাস লোড করা যাচ্ছে না"</string> <string name="controls_error_failed" msgid="960228639198558525">"সমস্যা হয়েছে, আবার চেষ্টা করুন"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 37397f959aea..49fae626fa5a 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -513,7 +513,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historija"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Dolazno"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Novo"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Nečujno"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Obavještenja"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Razgovori"</string> @@ -601,7 +601,7 @@ <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Na ovaj način ekran ostaje prikazan dok ga ne otkačite. Prevucite prema gore i držite da otkačite."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ekran ostaje prikazan ovako dok ga ne otkačite. Da ga otkačite, dodirnite i držite dugme Pregled."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Na ovaj način ekran ostaje prikazan dok ga ne otkačite. Da okačite ekran, dodirnite ili držite dugme Početna."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Lični podaci mogu biti dostupni (kao što su kontakti i sadržaj e-pošte)."</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Lični podaci mogu biti dostupni (naprimjer kontakti i sadržaj e-pošte)."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Zakačena aplikacija može otvoriti druge aplikacije."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Dodirnite i držite dugmad Nazad i Pregled da otkačite ovu aplikaciju"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Dodirnite i držite dugmad Nazad i Početni ekran da otkačite ovu aplikaciju"</string> @@ -1009,8 +1009,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Pomjerite gore desno"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Pomjeri dolje lijevo"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Pomjerite dolje desno"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Odbaci oblačić"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Nemoj prikazivati razgovor u oblačićima"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chatajte koristeći oblačiće"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Novi razgovori se prikazuju kao plutajuće ikone ili oblačići. Dodirnite da otvorite oblačić. Prevucite da ga premjestite."</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 2fdddf710e13..5cc0853a6e18 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gestiona"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Entrants"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Novetats"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silenci"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificacions"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Converses"</string> @@ -1002,10 +1002,9 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Mou a dalt a la dreta"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Mou a baix a l\'esquerra"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Mou a baix a la dreta"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Ignora la bombolla"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"No mostris la conversa com a bombolla"</string> - <string name="bubbles_user_education_title" msgid="5547017089271445797">"Xateja utilitzant les bombolles"</string> + <string name="bubbles_user_education_title" msgid="5547017089271445797">"Xateja amb bombolles"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Les converses noves es mostren com a icones flotants o bombolles. Toca per obrir una bombolla. Arrossega-la per moure-la."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Controla les bombolles en qualsevol moment"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Toca Gestiona per desactivar les bombolles d\'aquesta aplicació"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 18012b5e4280..a071793af881 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -516,7 +516,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Spravovat"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historie"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Příchozí"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nové"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Tiché"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Oznámení"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Konverzace"</string> @@ -601,7 +601,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Aplikace je připnuta"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Obsah bude připnut v zobrazení, dokud jej neuvolníte. Uvolníte jej stisknutím a podržením tlačítek Zpět a Přehled."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Obsah bude připnut v zobrazení, dokud ho neuvolníte. Uvolníte ho podržením tlačítek Zpět a Plocha."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Obsah bude připnut v zobrazení, dokud ho neuvolníte. Uvolnit ho můžete přejetím nahoru a podržením."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Díky připnutí bude vidět, dokud ji neodepnete. Odepnout ji můžete přejetím nahoru a podržením."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Obsah bude připnut v zobrazení, dokud jej neuvolníte. Uvolníte jej stisknutím a podržením tlačítka Přehled."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Obsah bude připnut v zobrazení, dokud ho neuvolníte. Uvolníte ho podržením tlačítka Plocha."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Může mít přístup k soukromým datům (například kontaktům a obsahu e-mailů)"</string> @@ -1012,8 +1012,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Přesunout vpravo nahoru"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Přesunout vlevo dolů"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Přesunout vpravo dolů"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Zavřít bublinu"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Nezobrazovat konverzaci v bublinách"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chatujte pomocí bublin"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nové konverzace se zobrazují jako plovoucí ikony, neboli bubliny. Klepnutím bublinu otevřete. Přetažením ji posunete."</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 3f71e3d49137..f1bbbff3d7b6 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Indgående"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nye"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Lydløs"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifikationer"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Samtaler"</string> @@ -595,10 +595,10 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Appen er fastgjort"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Dette fastholder skærmen i visningen, indtil du frigør den. Tryk på Tilbage og Overblik, og hold fingeren nede for at frigøre skærmen."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Dette fastholder skærmen i visningen, indtil du frigør den. Hold Tilbage og Startskærm nede for at frigøre skærmen."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Dette fastholder skærmen i visningen, indtil du frigør den. Stryg opad, og hold fingeren nede for at frigøre den."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Dette fastholder appen på skærmen, indtil du frigør den. Stryg opad, og hold fingeren nede for at frigøre den."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Dette fastholder skærmen i visningen, indtil du frigør den. Tryk på Tilbage, og hold fingeren nede for at frigøre skærmen."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Dette fastholder skærmen i visningen, indtil du frigør den. Hold Startskærm nede for at frigøre skærmen."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Personoplysninger er muligvis tilgængelige (f.eks. kontakter og mailindhold)."</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Der kan stadig være adgang til personoplysninger (f.eks. kontakter og mailindhold)."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"En fastgjort app kan åbne andre apps."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Du kan frigøre denne app ved at holde knapperne Tilbage og Oversigt nede"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Du kan frigøre denne app ved at holde knapperne Tilbage og Hjem nede"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Flyt op til højre"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Flyt ned til venstre"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Flyt ned til højre"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Afvis bobbel"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Vis ikke samtaler i bobler"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chat ved hjælp af bobler"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nye samtaler vises som svævende ikoner eller bobler. Tryk for at åbne boblen. Træk for at flytte den."</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 075a496c244b..3397cd615138 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -510,7 +510,8 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Verwalten"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Verlauf"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Neue Benachrichtigungen"</string> + <!-- no translation found for notification_section_header_incoming (850925217908095197) --> + <skip /> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Lautlos"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Benachrichtigungen"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Unterhaltungen"</string> @@ -1002,8 +1003,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Nach rechts oben verschieben"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Nach unten links verschieben"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Nach unten rechts verschieben"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Bubble schließen"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Unterhaltung nicht als Bubble anzeigen"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Bubbles zum Chatten verwenden"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Neue Unterhaltungen erscheinen als unverankerte Symbole, \"Bubbles\" genannt. Wenn du die Bubble öffnen möchtest, tippe sie an. Wenn du sie verschieben möchtest, zieh an ihr."</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index c8db318dfb65..f24f7457b0c6 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -256,7 +256,7 @@ <!-- no translation found for accessibility_work_mode (1280025758672376313) --> <skip /> <string name="accessibility_notification_dismissed" msgid="4411652015138892952">"Η ειδοποίηση έχει απορριφθεί."</string> - <string name="accessibility_bubble_dismissed" msgid="270358867566720729">"Η φούσκα παραβλέφθηκε."</string> + <string name="accessibility_bubble_dismissed" msgid="270358867566720729">"Το συννεφάκι παραβλέφθηκε."</string> <string name="accessibility_desc_notification_shade" msgid="5355229129428759989">"Πλαίσιο σκίασης ειδοποιήσεων."</string> <string name="accessibility_desc_quick_settings" msgid="4374766941484719179">"Γρήγορες ρυθμίσεις."</string> <string name="accessibility_desc_lock_screen" msgid="5983125095181194887">"Οθόνη κλειδώματος"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Διαχείριση"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Ιστορικό"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Εισερχόμενες"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Νέα"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Σίγαση"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Ειδοποιήσεις"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Συζητήσεις"</string> @@ -710,7 +710,7 @@ <string name="inline_keep_showing_app" msgid="4393429060390649757">"Να συνεχίσουν να εμφανίζονται ειδοποιήσεις από αυτήν την εφαρμογή;"</string> <string name="notification_silence_title" msgid="8608090968400832335">"Σίγαση"</string> <string name="notification_alert_title" msgid="3656229781017543655">"Προεπιλογή"</string> - <string name="notification_bubble_title" msgid="8330481035191903164">"Φούσκα"</string> + <string name="notification_bubble_title" msgid="8330481035191903164">"Συννεφάκι"</string> <string name="notification_channel_summary_low" msgid="4860617986908931158">"Χωρίς ήχο ή δόνηση"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Χωρίς ήχο ή δόνηση και εμφανίζεται χαμηλά στην ενότητα συζητήσεων"</string> <string name="notification_channel_summary_default" msgid="3282930979307248890">"Ενδέχεται να κουδουνίζει ή να δονείται βάσει των ρυθμίσεων του τηλεφώνου"</string> @@ -1002,11 +1002,10 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Μετακίνηση επάνω δεξιά"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Μετακίνηση κάτω αριστερά"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Μετακίνηση κάτω δεξιά"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Παράβλ. για συννεφ."</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Να μην γίνει προβολή της συζήτησης σε συννεφάκια."</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Συζητήστε χρησιμοποιώντας συννεφάκια."</string> - <string name="bubbles_user_education_description" msgid="1160281719576715211">"Οι νέες συζητήσεις εμφανίζονται ως κινούμενα εικονίδια ή φούσκες. Πατήστε για να ανοίξετε τη φούσκα. Σύρετε για να το μετακινήσετε."</string> + <string name="bubbles_user_education_description" msgid="1160281719576715211">"Οι νέες συζητήσεις εμφανίζονται ως κινούμενα εικονίδια ή συννεφάκια. Πατήστε για να ανοίξετε το συννεφάκι. Σύρετε για να το μετακινήσετε."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Ελέγξτε τα συννεφάκια ανά πάσα στιγμή."</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Πατήστε Διαχείριση για να απενεργοποιήσετε τα συννεφάκια από αυτήν την εφαρμογή."</string> <string name="bubbles_user_education_got_it" msgid="8282812431953161143">"Το κατάλαβα."</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 708f2f4f017a..ac20c8477d47 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -92,7 +92,7 @@ <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string> - <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android system can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string> + <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Incoming"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"New"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silent"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifications"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"App is pinned"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"This keeps it in view until you unpin. Touch & hold Back and Overview to unpin."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"This keeps it in view until you unpin. Touch & hold Back and Home to unpin."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up & hold to unpin."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up and hold to unpin."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"This keeps it in view until you unpin. Touch & hold Overview to unpin."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"This keeps it in view until you unpin. Touch & hold Home to unpin."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Personal data may be accessible (such as contacts and email content)."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Move top right"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Move bottom left"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Move bottom right"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Dismiss bubble"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Don’t bubble conversation"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chat using bubbles"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 313c604953ea..233664e5fbc7 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -92,7 +92,7 @@ <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string> - <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android system can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string> + <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Incoming"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"New"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silent"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifications"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"App is pinned"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"This keeps it in view until you unpin. Touch & hold Back and Overview to unpin."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"This keeps it in view until you unpin. Touch & hold Back and Home to unpin."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up & hold to unpin."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up and hold to unpin."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"This keeps it in view until you unpin. Touch & hold Overview to unpin."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"This keeps it in view until you unpin. Touch & hold Home to unpin."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Personal data may be accessible (such as contacts and email content)."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Move top right"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Move bottom left"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Move bottom right"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Dismiss bubble"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Don’t bubble conversation"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chat using bubbles"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 708f2f4f017a..ac20c8477d47 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -92,7 +92,7 @@ <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string> - <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android system can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string> + <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Incoming"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"New"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silent"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifications"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"App is pinned"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"This keeps it in view until you unpin. Touch & hold Back and Overview to unpin."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"This keeps it in view until you unpin. Touch & hold Back and Home to unpin."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up & hold to unpin."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up and hold to unpin."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"This keeps it in view until you unpin. Touch & hold Overview to unpin."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"This keeps it in view until you unpin. Touch & hold Home to unpin."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Personal data may be accessible (such as contacts and email content)."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Move top right"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Move bottom left"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Move bottom right"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Dismiss bubble"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Don’t bubble conversation"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chat using bubbles"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 708f2f4f017a..ac20c8477d47 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -92,7 +92,7 @@ <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string> - <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android system can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string> + <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Incoming"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"New"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silent"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifications"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"App is pinned"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"This keeps it in view until you unpin. Touch & hold Back and Overview to unpin."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"This keeps it in view until you unpin. Touch & hold Back and Home to unpin."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up & hold to unpin."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up and hold to unpin."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"This keeps it in view until you unpin. Touch & hold Overview to unpin."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"This keeps it in view until you unpin. Touch & hold Home to unpin."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Personal data may be accessible (such as contacts and email content)."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Move top right"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Move bottom left"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Move bottom right"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Dismiss bubble"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Don’t bubble conversation"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chat using bubbles"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 5d2e3eb16780..f37742d62062 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Manage"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Incoming"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"New"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silent"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifications"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index db5b68c0dd15..b4bba8f3145b 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -146,7 +146,7 @@ <string name="biometric_dialog_face_icon_description_authenticating" msgid="3401633342366146535">"Buscando tu rostro"</string> <string name="biometric_dialog_face_icon_description_authenticated" msgid="2242167416140740920">"Se autenticó el rostro"</string> <string name="biometric_dialog_face_icon_description_confirmed" msgid="7918067993953940778">"Confirmado"</string> - <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"Presiona Confirmar para completarla"</string> + <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"Presiona Confirmar para completar"</string> <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"Autenticado"</string> <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Usar PIN"</string> <string name="biometric_dialog_use_pattern" msgid="2315593393167211194">"Usar patrón"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Administrar"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Entrante"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nuevo"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silenciadas"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificaciones"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversaciones"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Ubicar arriba a la derecha"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Ubicar abajo a la izquierda"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Ubicar abajo a la derecha"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Descartar burbuja"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"No mostrar la conversación en burbujas"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chat con burbujas"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Las conversaciones nuevas aparecen como elementos flotantes o burbujas. Presiona para abrir la burbuja. Arrástrala para moverla."</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 1d8549a78efa..6ffed58c3df8 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionar"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Entrantes"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nuevas"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silenciadas"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificaciones"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversaciones"</string> @@ -593,16 +593,16 @@ <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"desactivar"</string> <string name="accessibility_output_chooser" msgid="7807898688967194183">"Cambiar dispositivo de salida"</string> <string name="screen_pinning_title" msgid="9058007390337841305">"La aplicación está fijada"</string> - <string name="screen_pinning_description" msgid="8699395373875667743">"La pantalla se mantiene visible hasta que dejas de fijarla. Para ello, mantén pulsados los botones Atrás y Aplicaciones recientes."</string> - <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"La pantalla se mantiene visible hasta que dejas de fijarla. Para ello, mantén pulsados los botones Atrás e Inicio."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Se mantiene visible hasta que dejas de fijarla. Para ello, desliza el dedo hacia arriba y mantén pulsado."</string> - <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"La pantalla se mantiene visible hasta que dejas de fijarla. Para ello, mantén pulsado el botón Aplicaciones recientes."</string> - <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"La pantalla se mantiene visible hasta que dejas de fijarla. Para ello, mantén pulsado el botón Inicio."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Es posible que se revelen datos personales, como contactos o el contenido de correos."</string> + <string name="screen_pinning_description" msgid="8699395373875667743">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para dejar de fijarla, mantén pulsados los botones Atrás y Aplicaciones recientes."</string> + <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para dejar de fijarla, mantén pulsados los botones Atrás e Inicio."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para dejar de fijarla, desliza el dedo hacia arriba y mantenlo pulsado."</string> + <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para dejar de fijarla, mantén pulsado el botón Aplicaciones recientes."</string> + <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para dejar de fijarla, mantén pulsado el botón Inicio."</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Es posible que se pueda acceder a datos personales, como contactos o el contenido de correos."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Se pueden abrir otras aplicaciones desde aplicaciones fijadas."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Para dejar de fijar esta aplicación, mantén pulsados los botones Atrás y Aplicaciones recientes"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Para dejar de fijar esta aplicación, mantén pulsados los botones Atrás e Inicio"</string> - <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"Para dejar de fijar esta aplicación, desliza el dedo hacia arriba y mantenlo pulsado"</string> + <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"Para dejar de fijar esta aplicación, desliza el dedo hacia arriba y no lo separes de la pantalla."</string> <string name="screen_pinning_positive" msgid="3285785989665266984">"Entendido"</string> <string name="screen_pinning_negative" msgid="6882816864569211666">"No, gracias"</string> <string name="screen_pinning_start" msgid="7483998671383371313">"Aplicación fijada"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Mover arriba a la derecha"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Mover abajo a la izquierda."</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Mover abajo a la derecha"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Cerrar burbuja"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"No mostrar conversación en burbujas"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chatea con burbujas"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Las conversaciones nuevas aparecen como iconos flotantes llamadas \"burbujas\". Toca para abrir la burbuja. Arrastra para moverla."</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index ede01b7f191c..258bf5a581d0 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Haldamine"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Ajalugu"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Sissetulevad"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Uued"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Hääletu"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Märguanded"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Vestlused"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Teisalda üles paremale"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Teisalda alla vasakule"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Teisalda alla paremale"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Sule mull"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Ära kuva vestlust mullina"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Vestelge mullide abil"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Uued vestlused kuvatakse hõljuvate ikoonidena ehk mullidena. Puudutage mulli avamiseks. Lohistage mulli, et seda liigutada."</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 641d031ef4aa..cb6f7967d3e8 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -92,7 +92,7 @@ <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Pantaila-grabaketa prozesatzen"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pantailaren grabaketa-saioaren jakinarazpen jarraitua"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Grabatzen hasi nahi duzu?"</string> - <string name="screenrecord_description" msgid="1123231719680353736">"Pantaila grabatzen duzun bitartean, Android sistemak detektatu egin dezake pantailan agertzen den edo gailuak erreproduzitzen duen kontuzko informazioa; besteak beste, pasahitzak, ordainketen informazioa, argazkiak, mezuak eta audioak."</string> + <string name="screenrecord_description" msgid="1123231719680353736">"Pantaila grabatzen duzun bitartean, Android sistemak detektatu egin dezake pantailan agertzen den edo gailuak erreproduzitzen duen kontuzko informazioa; besteak beste, pasahitzak, ordainketa-informazioa, argazkiak, mezuak eta audioa."</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Grabatu audioa"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Gailuaren audioa"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Gailuko soinuak; adibidez, musika, deiak eta tonuak"</string> @@ -279,12 +279,12 @@ <string name="accessibility_quick_settings_dnd_changed_off" msgid="1457150026842505799">"Desaktibatu egin da ez molestatzeko modua."</string> <string name="accessibility_quick_settings_dnd_changed_on" msgid="186315911607486129">"Aktibatu egin da ez molestatzeko modua."</string> <string name="accessibility_quick_settings_bluetooth" msgid="8250942386687551283">"Bluetooth-a."</string> - <string name="accessibility_quick_settings_bluetooth_off" msgid="3795983516942423240">"Bluetooth konexioa desaktibatuta dago."</string> - <string name="accessibility_quick_settings_bluetooth_on" msgid="3819082137684078013">"Bluetooth konexioa aktibatuta dago."</string> + <string name="accessibility_quick_settings_bluetooth_off" msgid="3795983516942423240">"Bluetooth bidezko konexioa desaktibatuta dago."</string> + <string name="accessibility_quick_settings_bluetooth_on" msgid="3819082137684078013">"Bluetooth bidezko konexioa aktibatuta dago."</string> <string name="accessibility_quick_settings_bluetooth_connecting" msgid="7362294657419149294">"Bluetooth bidez konektatzen ari da."</string> <string name="accessibility_quick_settings_bluetooth_connected" msgid="5237625393869747261">"Bluetooth bidez konektatuta dago."</string> - <string name="accessibility_quick_settings_bluetooth_changed_off" msgid="3344226652293797283">"Bluetooth konexioa desaktibatu egin da."</string> - <string name="accessibility_quick_settings_bluetooth_changed_on" msgid="1263282011749437549">"Bluetooth konexioa aktibatu egin da."</string> + <string name="accessibility_quick_settings_bluetooth_changed_off" msgid="3344226652293797283">"Bluetooth bidezko konexioa desaktibatu egin da."</string> + <string name="accessibility_quick_settings_bluetooth_changed_on" msgid="1263282011749437549">"Bluetooth bidezko konexioa aktibatu egin da."</string> <string name="accessibility_quick_settings_location_off" msgid="6122523378294740598">"Kokapena hautemateko aukera desaktibatuta dago."</string> <string name="accessibility_quick_settings_location_on" msgid="6869947200325467243">"Kokapena hautemateko aukera aktibatuta dago."</string> <string name="accessibility_quick_settings_location_changed_off" msgid="5132776369388699133">"Kokapena hautemateko aukera desaktibatu egin da."</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Kudeatu"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Jasotako azkenak"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Berria"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Isila"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Jakinarazpenak"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Elkarrizketak"</string> @@ -595,11 +595,11 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Aplikazioa ainguratuta dago"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Horrela, ikusgai egongo da aingura kendu arte. Aingura kentzeko, eduki sakatuta \"Atzera\" eta \"Ikuspegi orokorra\" botoiak."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Horrela, ikusgai egongo da aingura kendu arte. Aingura kentzeko, eduki sakatuta Atzera eta Hasiera botoiak."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Horrela, ikusgai egongo da aingura kendu arte. Aingura kentzeko, eduki sakatuta Hasiera botoia."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Horrela, ikusgai egongo da aingura kendu arte. Aingura kentzeko, pasatu hatza gora eduki ezazu sakatuta."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Horrela, ikusgai egongo da aingura kendu arte. Aingura kentzeko, eduki sakatuta \"Ikuspegi orokorra\" botoia."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Horrela, ikusgai egongo da aingura kendu arte. Aingura kentzeko, eduki sakatuta Hasiera botoia."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Baliteke datu pertsonalak atzitu ahal izatea (adibidez, kontaktuak eta posta elektronikoko edukia)."</string> - <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Baliteke ainguratutako aplikazioak beste aplikazio batzuk irekitzeko gai izatea."</string> + <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Baliteke ainguratutako aplikazioa beste aplikazio batzuk irekitzeko gai izatea."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Aplikazioari aingura kentzeko, eduki sakatuta Atzera eta Ikuspegi orokorra botoiak"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Aplikazioari aingura kentzeko, eduki sakatuta Atzera eta Hasiera botoiak"</string> <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"Aplikazioari aingura kentzeko, arrastatu aplikazioa gora eta eduki ezazu sakatuta"</string> @@ -624,7 +624,7 @@ <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Dardara"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Ez jo tonua"</string> <string name="qs_status_phone_vibrate" msgid="7055409506885541979">"Telefonoaren dardara aktibatuta dago"</string> - <string name="qs_status_phone_muted" msgid="3763664791309544103">"Telefonoaren tonu-jotzailea desaktibatuta dago"</string> + <string name="qs_status_phone_muted" msgid="3763664791309544103">"Tonu-jotzailea desaktibatuta"</string> <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Sakatu audioa aktibatzeko."</string> <string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Sakatu dardara ezartzeko. Baliteke erabilerraztasun-eginbideen audioa desaktibatzea."</string> <string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Sakatu audioa desaktibatzeko. Baliteke erabilerraztasun-eginbideen audioa desaktibatzea."</string> @@ -959,7 +959,7 @@ <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> - <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> (<xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>)"</string> <string name="wifi_is_off" msgid="5389597396308001471">"Wi-Fi konexioa desaktibatuta dago"</string> - <string name="bt_is_off" msgid="7436344904889461591">"Bluetooth konexioa desaktibatuta dago"</string> + <string name="bt_is_off" msgid="7436344904889461591">"Bluetooth bidezko konexioa desaktibatuta dago"</string> <string name="dnd_is_off" msgid="3185706903793094463">"Ez molestatzeko modua desaktibatuta dago"</string> <string name="qs_dnd_prompt_auto_rule" msgid="3535469468310002616">"Ez molestatzeko modua aktibatu du arau automatiko batek (<xliff:g id="ID_1">%s</xliff:g>)."</string> <string name="qs_dnd_prompt_app" msgid="4027984447935396820">"Ez molestatzeko modua aktibatu du aplikazio batek (<xliff:g id="ID_1">%s</xliff:g>)."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Eraman goialdera, eskuinetara"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Eraman behealdera, ezkerretara"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Eraman behealdera, eskuinetara"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Baztertu burbuila"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Ez erakutsi elkarrizketak burbuila gisa"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Txateatu burbuilen bidez"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Elkarrizketa berriak ikono gainerakor edo burbuila gisa agertzen dira. Sakatu burbuila irekitzeko. Arrasta ezazu mugitzeko."</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index eec54deb2b8e..ae98244cf415 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"مدیریت"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"سابقه"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"ورودی"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"جدید"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"بیصدا"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"اعلانها"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"مکالمهها"</string> @@ -595,10 +595,10 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"برنامه پین شده است"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"تا زمانی که پین را بردارید، در نما نگهداشته میشود. برای برداشتن پین، «برگشت» و «نمای کلی» را لمس کنید و نگهدارید."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"تا برداشتن پین، در نما نگهداشته میشود. برای برداشتن پین، «برگشت» و «صفحه اصلی» را لمس کنید و نگهدارید."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"تا برداشتن پین، در نما نگهداشته میشود. برای برداشتن پین، از پایین صفحه تند بهطرف بالا بکشید و نگهدارید."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"به این ترتیب تا زمانی پین آن را برندارید قابلمشاهده است. برای برداشتن پین، از پایین صفحه تند بهطرف بالا بکشید و نگه دارید."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"تا زمانی که پین را بردارید، در نما نگهداشته میشود. برای برداشتن پین، «نمای کلی» را لمس کنید و نگهدارید."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"تا برداشتن پین، در نما نگهداشته میشود. برای برداشتن پین، «صفحه اصلی» را لمس کنید و نگهدارید."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"ممکن است دادههای شخصی (مانند مخاطبین و محتوای ایمیل) دردسترس باشند."</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"ممکن است دادههای شخصی (مانند مخاطبین و محتوای ایمیل) در دسترس باشد."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"برنامه پینشده ممکن است برنامههای دیگر را باز کند."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"برای برداشتن پین این برنامه، دکمههای «برگشت» و «نمای کلی» را لمس کنید و نگهدارید"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"برای برداشتن پین این برنامه، دکمههای «برگشت» و «صفحه اصلی» را لمس کنید و نگه دارید"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"انتقال به بالا سمت چپ"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"انتقال به پایین سمت راست"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"انتقال به پایین سمت چپ"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"رد کردن حبابک"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"مکالمه در حباب نشان داده نشود"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"گپ بااستفاده از ابزارک اعلان"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"مکالمههای جدید بهصورت نمادهای شناور یا ابزارک اعلان نشان داده شوند. برای باز کردن ابزارک اعلان ضربه بزنید. برای جابهجایی، آن را بکشید."</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index ee7ed70718a1..05a88f4cc6ee 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Poista kaikki"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Muuta asetuksia"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Saapuvat"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Uudet"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Äänetön"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Ilmoitukset"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Keskustelut"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Siirrä oikeaan yläreunaan"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Siirrä vasempaan alareunaan"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Siirrä oikeaan alareunaan"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Ohita kupla"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Älä näytä kuplia keskusteluista"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chattaile kuplien avulla"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Uudet keskustelut näkyvät kelluvina kuvakkeina tai kuplina. Avaa kupla napauttamalla. Siirrä sitä vetämällä."</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index c71ab7ee4894..e7ef0824539f 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Entrantes"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nouvelles"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Mode silencieux"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifications"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Déplacer dans coin sup. droit"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Déplacer dans coin inf. gauche"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Déplacer dans coin inf. droit"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Ignorer la bulle"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Ne pas afficher les conversations dans des bulles"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Clavarder en utilisant des bulles"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes (de bulles). Touchez une bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string> @@ -1069,7 +1068,7 @@ <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"La commande n\'est pas accessible"</string> <string name="controls_error_removed_message" msgid="2885911717034750542">"Impossible d\'accéder à <xliff:g id="DEVICE">%1$s</xliff:g>. Vérifiez l\'application <xliff:g id="APPLICATION">%2$s</xliff:g> pour vous assurer que la commande est toujours offerte et que les paramètres de l\'application n\'ont pas changé."</string> - <string name="controls_open_app" msgid="483650971094300141">"Ouvrir"</string> + <string name="controls_open_app" msgid="483650971094300141">"Ouvrir l\'application"</string> <string name="controls_error_generic" msgid="352500456918362905">"Impossible de charger l\'état"</string> <string name="controls_error_failed" msgid="960228639198558525">"Erreur. Veuillez réessayer."</string> <string name="controls_in_progress" msgid="4421080500238215939">"En cours"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 4c69c865d1b5..c7d88720a9df 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Notifications entrantes"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nouvelles notifications"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silencieuses"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifications"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Déplacer en haut à droite"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Déplacer en bas à gauche"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Déplacer en bas à droite"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Fermer la bulle"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Ne pas afficher la conversations dans des bulles"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chatter en utilisant des bulles"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes ou bulles. Appuyez sur la bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index d80bd95abf65..93524f8492d8 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todas"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Xestionar"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Entrantes"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Notificacións novas"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silencio"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificacións"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Mover á parte superior dereita"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Mover á parte infer. esquerda"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Mover á parte inferior dereita"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Ignorar burbulla"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Non mostrar a conversa como burbulla"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chatear usando burbullas"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"As conversas novas aparecen como iconas flotantes ou burbullas. Toca para abrir a burbulla e arrastra para movela."</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index c641208055c1..473f6de00c89 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"મેનેજ કરો"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ઇતિહાસ"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"ઇનકમિંગ"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"નવા"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"સાઇલન્ટ"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"નોટિફિકેશન"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"વાતચીત"</string> @@ -593,11 +593,11 @@ <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"બંધ કરો"</string> <string name="accessibility_output_chooser" msgid="7807898688967194183">"આઉટપુટ ઉપકરણ સ્વિચ કરો"</string> <string name="screen_pinning_title" msgid="9058007390337841305">"ઍપને પિન કરેલી છે"</string> - <string name="screen_pinning_description" msgid="8699395373875667743">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને દૃશ્યક્ષમ રાખે છે. અનપિન કરવા માટે પાછળ અને ઝલકને સ્પર્શ કરી રાખો."</string> - <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને દૃશ્યક્ષમ રાખે છે. અનપિન કરવા માટે પાછળ અને હોમને સ્પર્શ કરી રાખો."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"તમે જ્યાં સુધી અનપિન નહીં કરો ત્યાં સુધી આ તેને દૃશ્યક્ષમ રાખે છે. ઉપરની તરફ સ્વાઇપ કરો અને અનપિન કરવા માટે દબાવી રાખો."</string> - <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને દૃશ્યક્ષમ રાખે છે. અનપિન કરવા માટે ઝલકને સ્પર્શ કરી રાખો."</string> - <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને દૃશ્યક્ષમ રાખે છે. અનપિન કરવા માટે હોમને સ્પર્શ કરી રાખો."</string> + <string name="screen_pinning_description" msgid="8699395373875667743">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને વ્યૂમાં રાખે છે. અનપિન કરવા માટે પાછળ અને ઓવરવ્યૂને સ્પર્શ કરી રાખો."</string> + <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને વ્યૂમાં રાખે છે. અનપિન કરવા માટે પાછળ અને હોમને સ્પર્શ કરી રાખો."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"તમે જ્યાં સુધી અનપિન નહીં કરો ત્યાં સુધી આ તેને વ્યૂમાં રાખે છે. ઉપરની તરફ સ્વાઇપ કરો અને અનપિન કરવા માટે દબાવી રાખો."</string> + <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને વ્યૂમાં રાખે છે. અનપિન કરવા માટે ઓવરવ્યૂને સ્પર્શ કરી રાખો."</string> + <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને વ્યૂમાં રાખે છે. અનપિન કરવા માટે હોમને સ્પર્શ કરી રાખો."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"વ્યક્તિગત ડેટા ઍક્સેસ કરી શકાતો હોઈ શકે (જેમ કે સંપર્કો અને ઇમેઇલનું કન્ટેન્ટ)."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"પિન કરેલી ઍપ અન્ય ઍપને ખોલી શકે છે."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"આ ઍપને અનપિન કરવા માટે, પાછળ અને ઓવરવ્યૂ બટનને સ્પર્શ કરી રાખો"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ઉપર જમણે ખસેડો"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"નીચે ડાબે ખસેડો"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"નીચે જમણે ખસેડો"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"બબલને છોડી દો"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"વાતચીતને બબલ કરશો નહીં"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"બબલનો ઉપયોગ કરીને ચેટ કરો"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"નવી વાતચીત ફ્લોટિંગ આઇકન અથવા બબલ જેવી દેખાશે. બબલને ખોલવા માટે ટૅપ કરો. તેને ખસેડવા માટે ખેંચો."</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 428840742b45..00389073626b 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -512,7 +512,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"प्रबंधित करें"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"हाल ही में मिली सूचनाएं"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"नई सूचनाएं"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"बिना आवाज़ किए मिलने वाली सूचनाएं"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"वाइब्रेशन या आवाज़ के साथ मिलने वाली सूचनाएं"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"बातचीत"</string> @@ -595,12 +595,12 @@ <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"बंद करें"</string> <string name="accessibility_output_chooser" msgid="7807898688967194183">"आउटपुट डिवाइस बदलें"</string> <string name="screen_pinning_title" msgid="9058007390337841305">"ऐप्लिकेशन पिन किया गया है"</string> - <string name="screen_pinning_description" msgid="8699395373875667743">"इससे वह तब तक दिखता रहता है जब तक कि आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए, \'वापस जाएं\' और \'खास जानकारी\' को दबाकर रखें."</string> + <string name="screen_pinning_description" msgid="8699395373875667743">"इससे वह तब तक दिखता रहता है, जब तक कि आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए, \'वापस जाएं\' और \'खास जानकारी\' को दबाकर रखें."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"इससे वह तब तक दिखाई देती है जब तक आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए, होम और वापस जाएं वाले बटन को दबाकर रखें."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"इससे ऐप्लिकेशन की स्क्रीन तब तक दिखाई देती है जब तक आप उसे अनपिन नहीं करते. अनपिन करने के लिए ऊपर स्वाइप करें और स्क्रीन दबाकर रखें."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"इससे ऐप्लिकेशन की स्क्रीन तब तक दिखाई देती है, जब तक आप उसे अनपिन नहीं करते. अनपिन करने के लिए ऊपर स्वाइप करें और स्क्रीन दबाकर रखें."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"इससे वह तब तक दिखता रहता है जब तक कि आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए, \'खास जानकारी\' को दबाकर रखें."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"इससे वह तब तक दिखाई देती है जब तक आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए, होम बटन को दबाकर रखें."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"निजी डेटा ऐक्सेस किया जा सकता है. जैसे कि संपर्क और ईमेल का कॉन्टेंट."</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"निजी डेटा ऐक्सेस किया जा सकता है, जैसे कि संपर्क और ईमेल का कॉन्टेंट."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"पिन किए गए ऐप्लिकेशन से दूसरे ऐप्लिकेशन भी खोले जा सकते हैं."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"इस ऐप्लिकेशन को अनपिन करने के लिए, \'वापस जाएं\' और \'खास जानकारी\' बटन को साथ-साथ दबाकर रखें"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"इस ऐप्लिकेशन को अनपिन करने के लिए, \'होम\' और \'वापस जाएं\' बटन को साथ-साथ दबाकर रखें"</string> @@ -1004,8 +1004,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"सबसे ऊपर दाईं ओर ले जाएं"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"बाईं ओर सबसे नीचे ले जाएं"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"सबसे नीचे दाईं ओर ले जाएं"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"बबल खारिज करें"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"बातचीत को बबल न करें"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"बबल्स का इस्तेमाल करके चैट करें"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"नई बातचीत फ़्लोटिंग आइकॉन या बबल्स की तरह दिखेंगी. बबल को खोलने के लिए टैप करें. इसे एक जगह से दूसरी जगह ले जाने के लिए खींचें और छोड़ें."</string> @@ -1017,7 +1016,7 @@ <string name="notification_content_gesture_nav_available" msgid="4431460803004659888">"सिस्टम नेविगेशन अपडेट करने के लिए \'सेटिंग\' में जाएं"</string> <string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"स्टैंडबाई"</string> <string name="priority_onboarding_title" msgid="2893070698479227616">"बातचीत को अहम बातचीत के तौर पर सेट किया गया है"</string> - <string name="priority_onboarding_behavior" msgid="5342816047020432929">"अहम बातचीत:"</string> + <string name="priority_onboarding_behavior" msgid="5342816047020432929">"अहम बातचीत यहां दिखेगी:"</string> <string name="priority_onboarding_show_at_top_text" msgid="1678400241025513541">"बातचीत सेक्शन में सबसे ऊपर दिखाएं"</string> <string name="priority_onboarding_show_avatar_text" msgid="5756291381124091508">"लॉक स्क्रीन पर प्रोफ़ाइल फ़ोटो दिखाएं"</string> <string name="priority_onboarding_appear_as_bubble_text" msgid="4227039772250263122">"खास बातचीत फ़्लोटिंग बबल की तरह ऐप्लिकेशन के ऊपर दिखेंगी"</string> @@ -1068,9 +1067,9 @@ <string name="controls_media_resume" msgid="1933520684481586053">"फिर से शुरू करें"</string> <string name="controls_error_timeout" msgid="794197289772728958">"काम नहीं कर रहा, ऐप जांचें"</string> <string name="controls_error_retryable" msgid="864025882878378470">"कोई गड़बड़ी हुई, फिर से कोशिश की जा रही है…"</string> - <string name="controls_error_removed" msgid="6675638069846014366">"कंट्रोल नहीं मिला"</string> + <string name="controls_error_removed" msgid="6675638069846014366">"कंट्रोल मौजूद नहीं है"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"कंट्रोल मौजूद नहीं है"</string> - <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> ऐक्सेस नहीं किया जा सका. <xliff:g id="APPLICATION">%2$s</xliff:g> ऐप्लिकेशन देखें, ताकि यह पक्का किया जा सके कि कंट्रोल अब भी मौजूद है और सेटिंग में कोई बदलाव नहीं हुआ है."</string> + <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> को ऐक्सेस नहीं किया जा सका. <xliff:g id="APPLICATION">%2$s</xliff:g> ऐप्लिकेशन देखें, ताकि यह पक्का किया जा सके कि कंट्रोल अब भी मौजूद है और सेटिंग में कोई बदलाव नहीं हुआ है."</string> <string name="controls_open_app" msgid="483650971094300141">"ऐप्लिकेशन खोलें"</string> <string name="controls_error_generic" msgid="352500456918362905">"स्थिति लोड नहीं की जा सकती"</string> <string name="controls_error_failed" msgid="960228639198558525">"गड़बड़ी हुई, फिर से कोशिश करें"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index b8b784c36735..b0e6b51e8f6a 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -513,7 +513,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Povijest"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Dolazno"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Novo"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Bešumno"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Obavijesti"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Razgovori"</string> @@ -1007,10 +1007,9 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Premjesti u gornji desni kut"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Premjesti u donji lijevi kut"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Premjestite u donji desni kut"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Odbaci oblačić"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Zaustavi razgovor u oblačićima"</string> - <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chatanje pomoću oblačića"</string> + <string name="bubbles_user_education_title" msgid="5547017089271445797">"Oblačići u chatu"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Novi razgovori pojavljuju se kao pomične ikone ili oblačići. Dodirnite za otvaranje oblačića. Povucite da biste ga premjestili."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Upravljanje oblačićima u svakom trenutku"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Dodirnite Upravljanje da biste isključili oblačiće iz ove aplikacije"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 551fcd8b2975..64e7011a63a9 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Kezelés"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Előzmények"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Bejövő"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Új"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Néma"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Értesítések"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Beszélgetések"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Az alkalmazás ki van tűzve"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Megjelenítve tartja addig, amíg Ön fel nem oldja a rögzítést. A feloldáshoz tartsa lenyomva a Vissza és az Áttekintés lehetőséget."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Megjelenítve tartja addig, amíg Ön fel nem oldja a rögzítést. A feloldáshoz tartsa lenyomva a Vissza és a Kezdőképernyő elemet."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Megjelenítve tartja addig, amíg Ön fel nem oldja a rögzítést. A feloldáshoz csúsztasson fel, és tartsa ujját a képernyőn."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Megjelenítve tartja addig, amíg Ön fel nem oldja a rögzítést. A feloldáshoz csúsztassa fel és tartsa ujját a képernyőn."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Megjelenítve tartja addig, amíg Ön fel nem oldja a rögzítést. A feloldáshoz tartsa lenyomva az Áttekintés lehetőséget."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Megjelenítve tartja addig, amíg Ön fel nem oldja a rögzítést. A feloldáshoz tartsa lenyomva a Kezdőképernyő elemet."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Bizonyos személyes adatok (például a névjegyek és az e-mailek tartalma) hozzáférhetők lehetnek."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Áthelyezés fel és jobbra"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Áthelyezés le és balra"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Áthelyezés le és jobbra"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Buborék elvetése"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Ne jelenjen meg a beszélgetés buborékban"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Buborékokat használó csevegés"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Az új beszélgetések lebegő ikonként, vagyis buborékként jelennek meg. A buborék megnyitásához koppintson rá. Áthelyezéshez húzza a kívánt helyre."</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 730f1155502b..a337cc22390b 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -92,7 +92,7 @@ <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Էկրանի տեսագրության մշակում"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Էկրանի տեսագրման աշխատաշրջանի ընթացիկ ծանուցում"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Սկսե՞լ տեսագրումը"</string> - <string name="screenrecord_description" msgid="1123231719680353736">"Տեսագրման ընթացքում Android-ի համակարգը կարող է գրանցել անձնական տեղեկություններ, որոնք տեսանելի են էկրանին կամ նվագարկվում են ձեր սարքում։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string> + <string name="screenrecord_description" msgid="1123231719680353736">"Տեսագրման ընթացքում Android համակարգը կարող է գրանցել անձնական տեղեկություններ, որոնք տեսանելի են էկրանին կամ նվագարկվում են ձեր սարքում։ Սա ներառում է այնպիսի տեղեկություններ, ինչպիսիք են, օրինակ, գաղտնաբառերը, վճարային տվյալները, լուսանկարները, հաղորդագրությունները և նվագարկվող աուդիո ֆայլերը։"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Ձայնագրել"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Սարքի ձայները"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Ձեր սարքի ձայները, օրինակ՝ երաժշտությունը, զանգերն ու զանգերանգները"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Կառավարել"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Պատմություն"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Մուտքային"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Նոր"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Անձայն"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Ծանուցումներ"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Խոսակցություններ"</string> @@ -720,7 +720,7 @@ <string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Կարգավորումներ"</string> <string name="notification_priority_title" msgid="2079708866333537093">"Կարևոր"</string> <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը զրույցի գործառույթներ չի աջակցում"</string> - <string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Վերջին ամպիկներ չկան"</string> + <string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Ամպիկներ չկան"</string> <string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Այստեղ կցուցադրվեն վերջերս օգտագործված և փակված ամպիկները, որոնք կկարողանաք հեշտությամբ վերաբացել"</string> <string name="notification_unblockable_desc" msgid="2073030886006190804">"Այս ծանուցումները չեն կարող փոփոխվել:"</string> <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ծանուցումների տվյալ խումբը հնարավոր չէ կարգավորել այստեղ"</string> @@ -1002,11 +1002,10 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Տեղափոխել վերև՝ աջ"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Տեղափոխել ներքև՝ ձախ"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Տեղափոխել ներքև՝ աջ"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Փակել ամպիկը"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Զրույցը չցուցադրել ամպիկի տեսքով"</string> - <string name="bubbles_user_education_title" msgid="5547017089271445797">"Զրուցել ամպիկների միջոցով"</string> - <string name="bubbles_user_education_description" msgid="1160281719576715211">"Նոր խոսակցությունները կհայտնվեն լողացող պատկերակների կամ ամպիկների տեսքով։ Հպեք՝ ամպիկը բացելու համար։ Քաշեք՝ այն տեղափոխելու համար։"</string> + <string name="bubbles_user_education_title" msgid="5547017089271445797">"Զրույցի ամպիկներ"</string> + <string name="bubbles_user_education_description" msgid="1160281719576715211">"Նոր զրույցները կհայտնվեն լողացող պատկերակների կամ ամպիկների տեսքով։ Հպեք՝ ամպիկը բացելու համար։ Քաշեք՝ այն տեղափոխելու համար։"</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Ամպիկների կարգավորումներ"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Հպեք «Կառավարել» կոճակին՝ այս հավելվածի ամպիկներն անջատելու համար։"</string> <string name="bubbles_user_education_got_it" msgid="8282812431953161143">"Եղավ"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 3614cb4d88c0..72fac719f1f1 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Kelola"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Histori"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Masuk"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Baru"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Senyap"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifikasi"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Percakapan"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Aplikasi dipasangi pin"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh lama tombol Kembali dan Ringkasan untuk melepas pin."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh lama tombol Kembali dan Beranda untuk melepas pin."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ini akan terus ditampilkan sampai Anda melepas pin. Geser ke atas & tahan untuk melepas pin."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Aplikasi akan terus ditampilkan sampai pin dilepas. Geser ke atas dan tahan untuk lepas pin."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh lama tombol Ringkasan untuk melepas pin."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh lama tombol Beranda untuk melepas pin."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Data pribadi dapat diakses (seperti kontak dan konten email)."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Pindahkan ke kanan atas"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Pindahkan ke kiri bawah"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Pindahkan ke kanan bawah"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Tutup balon"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Jangan gunakan percakapan balon"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chat dalam tampilan balon"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Percakapan baru muncul sebagai ikon mengambang, atau balon. Ketuk untuk membuka balon. Tarik untuk memindahkannya."</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 224164c012e5..a3119ce21498 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Ferill"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Mótteknar"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nýtt"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Hljóðlaust"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Tilkynningar"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Samtöl"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Færa efst til hægri"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Færa neðst til vinstri"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Færðu neðst til hægri"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Loka blöðru"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Ekki setja samtal í blöðru"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Spjalla með blöðrum"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Ný samtöl birtast sem fljótandi tákn eða blöðrur. Ýttu til að opna blöðru. Dragðu hana til að færa."</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index f9fcda4b3eca..b90ae3e5834a 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -78,8 +78,8 @@ <string name="compat_mode_off" msgid="7682459748279487945">"Estendi per riemp. schermo"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> <string name="remote_input_image_insertion_text" msgid="4850791636452521123">"è stata inviata un\'immagine"</string> - <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Salvataggio screenshot..."</string> - <string name="screenshot_saving_title" msgid="2298349784913287333">"Salvataggio screenshot..."</string> + <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Salvataggio screenshot…"</string> + <string name="screenshot_saving_title" msgid="2298349784913287333">"Salvataggio screenshot…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot salvato"</string> <string name="screenshot_saved_text" msgid="7778833104901642442">"Tocca per visualizzare lo screenshot"</string> <string name="screenshot_failed_title" msgid="3259148215671936891">"Impossibile salvare lo screenshot"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gestisci"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Cronologia"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"In arrivo"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nuove"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silenziose"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifiche"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversazioni"</string> @@ -592,14 +592,14 @@ <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"attiva"</string> <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"disattiva"</string> <string name="accessibility_output_chooser" msgid="7807898688967194183">"Cambia dispositivo di uscita"</string> - <string name="screen_pinning_title" msgid="9058007390337841305">"L\'app è bloccata"</string> + <string name="screen_pinning_title" msgid="9058007390337841305">"L\'app è bloccata su schermo"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"La schermata rimane visibile finché non viene sganciata. Per sganciarla, tieni premuto Indietro e Panoramica."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"La schermata rimane visibile finché non viene disattivato il blocco su schermo. Per disattivarlo, tocca e tieni premuto Indietro e Home."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Mantiene la visualizzazione fino allo sblocco. Scorri verso l\'alto e tieni premuto per sbloccare."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Rimarrà visibile finché non viene sbloccata. Scorri verso l\'alto e tieni premuto per sbloccarla."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"La schermata rimane visibile finché non viene sganciata. Per sganciarla, tieni premuto Panoramica."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"La schermata rimane visibile finché non viene disattivato il blocco su schermo. Per disattivarlo, tocca e tieni premuto Home."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"I dati personali potrebbero essere accessibili (ad esempio i contatti e i contenuti delle email)."</string> - <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Un\'app bloccata potrebbe aprire altre app."</string> + <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"L\'app bloccata su schermo potrebbe aprire altre app."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Per sbloccare questa app, tocca e tieni premuti i pulsanti Indietro e Panoramica"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Per sbloccare questa app, tocca e tieni premuti i pulsanti Indietro e Home"</string> <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"Per sbloccare questa app, scorri verso l\'alto e tieni premuto"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Sposta in alto a destra"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Sposta in basso a sinistra"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Sposta in basso a destra"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Ignora bolla"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Non utilizzare bolle per la conversazione"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chatta utilizzando le bolle"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Le nuove conversazioni vengono visualizzate come icone mobili o bolle. Tocca per aprire la bolla. Trascinala per spostarla."</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 172919c497e3..9d2d0cc973aa 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -516,7 +516,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכל"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"ניהול"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"היסטוריה"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"התקבלו"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"התראות חדשות"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"שקט"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"התראות"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"שיחות"</string> @@ -601,7 +601,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"האפליקציה מוצמדת"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"נשאר בתצוגה עד לביטול ההצמדה. יש ללחוץ לחיצה ארוכה על הלחצנים \'הקודם\' ו\'סקירה\' כדי לבטל את ההצמדה."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"נשאר בתצוגה עד לביטול ההצמדה. יש ללחוץ לחיצה ארוכה על הלחצנים \'הקודם\' ו\'דף הבית\' כדי לבטל את ההצמדה."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"נשאר בתצוגה עד לביטול ההצמדה. יש להחליק למעלה ולהחזיק כדי לבטל הצמדה."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"היא תמשיך להופיע עד שההצמדה תבוטל. כדי לבטל את ההצמדה, יש להחליק למעלה ולהחזיק."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"נשאר בתצוגה עד לביטול ההצמדה. יש ללחוץ לחיצה ארוכה על הלחצן \'סקירה\' כדי לבטל את ההצמדה."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"נשאר בתצוגה עד לביטול ההצמדה. יש ללחוץ לחיצה ארוכה על הלחצן \'דף הבית\' כדי לבטל את ההצמדה."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"ייתכן שתתאפשר גישה למידע אישי (כמו אנשי קשר ותוכן מהאימייל)."</string> @@ -1012,10 +1012,9 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"העברה לפינה הימנית העליונה"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"העברה לפינה השמאלית התחתונה"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"העברה לפינה הימנית התחתונה"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"סגירת בועה"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"אין להציג בועות לשיחה"</string> - <string name="bubbles_user_education_title" msgid="5547017089271445797">"לדבר בצ\'אט באמצעות בועות"</string> + <string name="bubbles_user_education_title" msgid="5547017089271445797">"לדבר בבועות"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"שיחות חדשות מופיעות כסמלים צפים, או בועות. יש להקיש כדי לפתוח בועה. יש לגרור כדי להזיז אותה."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"שליטה בבועות, בכל זמן"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"יש להקיש על \'ניהול\' כדי להשבית את הבועות מהאפליקציה הזו"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index a2bb39cd9e6b..4f6c7858379c 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"履歴"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"新着"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"新着"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"サイレント"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"通知"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"会話"</string> @@ -603,7 +603,7 @@ <string name="screen_pinning_toast" msgid="8177286912533744328">"このアプリの固定を解除するには [戻る] ボタンと [最近] ボタンを長押しします"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"このアプリの固定を解除するには [戻る] ボタンと [ホーム] ボタンを長押しします"</string> <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"このアプリの固定を解除するには、上にスワイプして長押しします"</string> - <string name="screen_pinning_positive" msgid="3285785989665266984">"はい"</string> + <string name="screen_pinning_positive" msgid="3285785989665266984">"OK"</string> <string name="screen_pinning_negative" msgid="6882816864569211666">"いいえ"</string> <string name="screen_pinning_start" msgid="7483998671383371313">"固定したアプリ"</string> <string name="screen_pinning_exit" msgid="4553787518387346893">"固定を解除したアプリ"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"右上に移動"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"左下に移動"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"右下に移動"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"ふきだしを閉じる"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"会話をバブルで表示しない"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"チャットでバブルを使う"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"新しい会話はフローティング アイコン(バブル)として表示されます。タップするとバブルが開きます。ドラッグしてバブルを移動できます。"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 37b5ec98797e..aeee416750ed 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"მართვა"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ისტორია"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"შემომავალი"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"ახალი"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"ჩუმი"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"შეტყობინებები"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"საუბრები"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"გადაანაცვლეთ ზევით და მარჯვნივ"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"ქვევით და მარცხნივ გადატანა"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"გადაანაცვ. ქვემოთ და მარჯვნივ"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"ბუშტის დახურვა"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"აიკრძალოს საუბრის ბუშტები"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"ჩეთი ბუშტების გამოყენებით"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"ახალი საუბრები გამოჩნდება როგორც მოტივტივე ხატულები ან ბუშტები. შეეხეთ ბუშტის გასახსნელად. გადაიტანეთ ჩავლებით."</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 73f0e6c7af7d..356526ce9a1d 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -101,7 +101,7 @@ <string name="screenrecord_start" msgid="330991441575775004">"Бастау"</string> <string name="screenrecord_ongoing_screen_only" msgid="4459670242451527727">"Экрандағы бейне жазылуда."</string> <string name="screenrecord_ongoing_screen_and_audio" msgid="5351133763125180920">"Экрандағы бейне және аудио жазылуда."</string> - <string name="screenrecord_taps_label" msgid="1595690528298857649">"Экранды түртуді көрсету"</string> + <string name="screenrecord_taps_label" msgid="1595690528298857649">"Экранды түрткенде көрсету"</string> <string name="screenrecord_stop_text" msgid="6549288689506057686">"Тоқтату үшін түртіңіз"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Тоқтату"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Тоқтата тұру"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазалау"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Басқару"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Тарих"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Кіріс"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Жаңа"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Үнсіз"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Хабарландырулар"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Әңгімелер"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Жоғары оң жаққа жылжыту"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Төменгі сол жаққа жылжыту"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Төменгі оң жаққа жылжыту"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Қалқымалы анықтаманы жабу"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Әңгіменің қалқыма хабары көрсетілмесін"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Қалқыма хабарлар арқылы сөйлесу"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Жаңа әңгімелер қалқыма белгішелер немесе хабарлар түрінде көрсетіледі. Қалқыма хабарды ашу үшін түртіңіз. Жылжыту үшін сүйреңіз."</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 47fd544912ea..b0f90e2b2869 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាតទាំងអស់"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"គ្រប់គ្រង"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ប្រវត្តិ"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"មកដល់"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"ថ្មី"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"ស្ងាត់"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"ការជូនដំណឹង"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"ការសន្ទនា"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"កម្មវិធីត្រូវបានខ្ទាស់"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"វានឹងនៅតែបង្ហាញ រហូតទាល់តែអ្នកដកការដៅ។ សូមសង្កត់ប៊ូតុងថយក្រោយ និងប៊ូតុងទិដ្ឋភាពរួមឲ្យជាប់ ដើម្បីដកការដៅ។"</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"វានឹងនៅតែបង្ហាញ រហូតទាល់តែអ្នកដកការដៅ។ សូមចុចប៊ូតុងថយក្រោយ និងប៊ូតុងទំព័រដើមឱ្យជាប់ ដើម្បីដកការដៅ។"</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"វានឹងនៅតែបង្ហាញ រហូតទាល់តែអ្នកដកការដៅ។ អូសឡើងលើឱ្យជាប់ ដើម្បីដកការដៅ។"</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"វានឹងនៅតែបង្ហាញ រហូតទាល់តែអ្នកដកខ្ទាស់ចេញ។ អូសឡើងលើឱ្យជាប់ ដើម្បីដកខ្ទាស់។"</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"វានឹងនៅតែបង្ហាញ រហូតទាល់តែអ្នកដកការដៅ។ សូមសង្កត់ប៊ូតុងទិដ្ឋភាពរួមឲ្យជាប់ ដើម្បីដកការដៅ។"</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"វានឹងនៅតែបង្ហាញ រហូតទាល់តែអ្នកដកការដៅ។ សូមចុចប៊ូតុងទំព័រដើមឱ្យជាប់ ដើម្បីដកការដៅ។"</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"អាចចូលប្រើទិន្នន័យផ្ទាល់ខ្លួនបាន (ដូចជា ទំនាក់ទំនង និងខ្លឹមសារអ៊ីមែលជាដើម)។"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ផ្លាស់ទីទៅផ្នែកខាងលើខាងស្ដាំ"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"ផ្លាស់ទីទៅផ្នែកខាងក្រោមខាងឆ្វេង"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"ផ្លាស់ទីទៅផ្នែកខាងក្រោមខាងស្ដាំ"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"ច្រានចោលពពុះ"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"កុំបង្ហាញការសន្ទនាជាពពុះ"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"ជជែកដោយប្រើពពុះ"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"ការសន្ទនាថ្មីៗបង្ហាញជាពពុះ ឬរូបអណ្ដែត។ ចុច ដើម្បីបើកពពុះ។ អូស ដើម្បីផ្លាស់ទីពពុះនេះ។"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index afa60524fca6..ed5342554913 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -510,7 +510,8 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ಇತಿಹಾಸ"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"ಒಳಬರುವ"</string> + <!-- no translation found for notification_section_header_incoming (850925217908095197) --> + <skip /> <string name="notification_section_header_gentle" msgid="6804099527336337197">"ನಿಶ್ಶಬ್ದ"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"ಅಧಿಸೂಚನೆಗಳು"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"ಸಂಭಾಷಣೆಗಳು"</string> @@ -1002,8 +1003,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ಬಲ ಮೇಲ್ಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"ಸ್ಕ್ರೀನ್ನ ಎಡ ಕೆಳಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"ಕೆಳಗಿನ ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"ಬಬಲ್ ವಜಾಗೊಳಿಸಿ"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"ಸಂಭಾಷಣೆಯನ್ನು ಬಬಲ್ ಮಾಡಬೇಡಿ"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"ಬಬಲ್ಸ್ ಬಳಸಿ ಚಾಟ್ ಮಾಡಿ"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"ಹೊಸ ಸಂಭಾಷಣೆಗಳು ತೇಲುವ ಐಕಾನ್ಗಳು ಅಥವಾ ಬಬಲ್ಸ್ ಆಗಿ ಗೋಚರಿಸುತ್ತವೆ. ಬಬಲ್ ತೆರೆಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಅದನ್ನು ಡ್ರ್ಯಾಗ್ ಮಾಡಲು ಎಳೆಯಿರಿ."</string> @@ -1068,7 +1068,7 @@ <string name="controls_error_retryable" msgid="864025882878378470">"ದೋಷ, ಮರುಪ್ರಯತ್ನಿಸಲಾಗುತ್ತಿದೆ…"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ಕಂಡುಬಂದಿಲ್ಲ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ನಿಯಂತ್ರಣ ಲಭ್ಯವಿಲ್ಲ"</string> - <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಿಯಂತ್ರಣ ಈಗಲೂ ಲಭ್ಯವಿದೆಯೇ ಮತ್ತು <xliff:g id="APPLICATION">%2$s</xliff:g> ಆ್ಯಪ್ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಬದಲಾಯಿಸಲಾಗಿಲ್ಲ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು ಆ್ಯಪ್ ಅನ್ನು ಪರಿಶೀಲಿಸಿ."</string> + <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ನಿಯಂತ್ರಣ ಈಗಲೂ ಲಭ್ಯವಿದೆ ಮತ್ತು ಆ್ಯಪ್ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಬದಲಾಯಿಸಲಾಗಿಲ್ಲ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು <xliff:g id="APPLICATION">%2$s</xliff:g> ಆ್ಯಪ್ ಅನ್ನು ಪರಿಶೀಲಿಸಿ."</string> <string name="controls_open_app" msgid="483650971094300141">"ಆ್ಯಪ್ ತೆರೆಯಿರಿ"</string> <string name="controls_error_generic" msgid="352500456918362905">"ಸ್ಥಿತಿ ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="controls_error_failed" msgid="960228639198558525">"ದೋಷ, ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index a75c230cd681..a27f4f723a0c 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"기록"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"최근 알림"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"새 알림"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"무음"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"알림"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"대화"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"오른쪽 상단으로 이동"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"왼쪽 하단으로 이동"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"오른쪽 하단으로 이동"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"대화창 닫기"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"대화를 대화창으로 표시하지 않음"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"대화창으로 채팅하기"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"새로운 대화가 플로팅 아이콘인 대화창으로 표시됩니다. 대화창을 열려면 탭하세요. 드래그하여 이동할 수 있습니다."</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index a0f532acf9da..74f43e870da7 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -95,7 +95,7 @@ <string name="screenrecord_description" msgid="1123231719680353736">"Жаздыруу учурунда Android тутуму экраныңызда көрүнүп турган жана түзмөктө ойноп жаткан бардык купуя маалыматты жаздырып алат. Буга сырсөздөр, төлөм маалыматы, сүрөттөр, билдирүүлөр жана аудио файлдар кирет."</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Аудио жаздыруу"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Түзмөктүн аудиосу"</string> - <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Музыка, чалуулар жана рингтондор сыяктуу түзмөгүңүздөгү добуштар"</string> + <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Музыка, чалуулар жана шыңгырлар сыяктуу түзмөгүңүздөгү добуштар"</string> <string name="screenrecord_mic_label" msgid="2111264835791332350">"Микрофон"</string> <string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"Түзмөктүн аудиосу жана микрофон"</string> <string name="screenrecord_start" msgid="330991441575775004">"Баштадык"</string> @@ -506,11 +506,11 @@ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Бул функцияны аткарган кызматка экраныңыздагы бардык маалымат же түзмөктө ойнотулуп жаткан нерсе, сырсөздөр, төлөмдөрдүн чоо-жайы, сүрөттөр, билдирүүлөр жана аудио файлдар жеткиликтүү болот."</string> <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жаздырып же тышкы экранга чыгарып баштайсызбы?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> менен жаздырылып же тышкы экранга чыгарылып башталсынбы?"</string> - <string name="media_projection_remember_text" msgid="6896767327140422951">"Экинчи көрсөтүлбөсүн"</string> + <string name="media_projection_remember_text" msgid="6896767327140422951">"Экинчи көрүнбөсүн"</string> <string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Башкаруу"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Таржымал"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Кирүүчү"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Жаңы"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Үнсүз"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Билдирмелер"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Жазышуулар"</string> @@ -598,7 +598,7 @@ <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ал бошотулмайынча көрүнө берет. Бошотуу үчүн өйдө сүрүп, коё бербей басып туруңуз."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ал бошотулмайынча көрүнө берет. Бошотуу үчүн, \"Карап чыгуу\" баскычын басып, кармап туруңуз."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Ал бошотулмайынча көрүнө берет. Бошотуу үчүн, \"Башкы бет\" баскычын басып, кармап туруңуз."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Байланыштар жана электрондук почталардын мазмуну сыяктуу жеке дайындар ачык болушу мүмкүн."</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Байланыштар жана электрондук почталардын мазмуну сыяктуу жеке маалымат ачык болушу мүмкүн."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Кадалган колдонмо башка колдонмолорду ача алат."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Бул колдонмону бошотуу үчүн \"Артка\" жана \"Назар салуу\" баскычтарын басып, кармап туруңуз"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Бул колдонмону бошотуу үчүн \"Артка\" жана \"Башкы бет\" баскычтарын басып, кармап туруңуз"</string> @@ -714,14 +714,14 @@ <string name="notification_channel_summary_low" msgid="4860617986908931158">"Үнү чыкпайт жана дирилдебейт"</string> <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Үнү чыгып же дирилдебейт жана жазышуу бөлүмүнүн ылдый жагында көрүнөт"</string> <string name="notification_channel_summary_default" msgid="3282930979307248890">"Телефондун жөндөөлөрүнө жараша шыңгырап же дирилдеши мүмкүн"</string> - <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Телефондун жөндөөлөрүнө жараша шыңгырап же дирилдеши мүмкүн. <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосундагы жазышуулар демейки жөндөө боюнча калкып чыкма билдирмелер болуп көрүнөт."</string> + <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Телефондун жөндөөлөрүнө жараша шыңгырап же дирилдеши мүмкүн. <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосундагы жазышуулар демейки жөндөө боюнча калкып чыкма билдирмелер түрүндө көрүнөт."</string> <string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Калкыма ыкчам баскыч менен көңүлүңүздү бул мазмунга буруп турат."</string> <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Жазышуу бөлүмүнүн жогорку жагында калкып чыкма билдирме түрүндө көрүнүп, профиль сүрөтү кулпуланган экрандан чагылдырылат"</string> <string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Жөндөөлөр"</string> <string name="notification_priority_title" msgid="2079708866333537093">"Маанилүүлүгү"</string> <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> жазышуу функцияларын колдоого албайт"</string> - <string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Акыркы калкып чыкма билдирмелер жок"</string> - <string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Кайра жөнөтүлгөн жана жабылган калкып чыкма билдирмелер ушул жерде көрүнөт"</string> + <string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Азырынча эч нерсе жок"</string> + <string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Акыркы жана жабылган калкып чыкма билдирмелер ушул жерде көрүнөт"</string> <string name="notification_unblockable_desc" msgid="2073030886006190804">"Бул билдирмелерди өзгөртүүгө болбойт."</string> <string name="notification_multichannel_desc" msgid="7414593090056236179">"Бул билдирмелердин тобун бул жерде конфигурациялоого болбойт"</string> <string name="notification_delegate_header" msgid="1264510071031479920">"Прокси билдирмеси"</string> @@ -864,12 +864,12 @@ <string-array name="clock_options"> <item msgid="3986445361435142273">"Сааттар, мүнөттөр жана секунддар"</item> <item msgid="1271006222031257266">"Сааттар жана мүнөттөр (демейки шартта)"</item> - <item msgid="6135970080453877218">"Бул сүрөтчө көрсөтүлбөсүн"</item> + <item msgid="6135970080453877218">"Бул сүрөтчө көрүнбөсүн"</item> </string-array> <string-array name="battery_options"> <item msgid="7714004721411852551">"Ар дайым пайызы көрсөтүлсүн"</item> <item msgid="3805744470661798712">"Кубаттоо учурунда пайызы көрсөтүлсүн (демейки)"</item> - <item msgid="8619482474544321778">"Бул сүрөтчө көрсөтүлбөсүн"</item> + <item msgid="8619482474544321778">"Бул сүрөтчө көрүнбөсүн"</item> </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"Анча маанилүү эмес билдирменин сүрөтчөлөрүн көрсөтүү"</string> <string name="other" msgid="429768510980739978">"Башка"</string> @@ -1002,11 +1002,10 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Жогорку оң жакка жылдырыңыз"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Төмөнкү сол жакка жылдыруу"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Төмөнкү оң жакка жылдырыңыз"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Калкып чыкма билдирмени жабуу"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Жазышуу калкып чыкма билдирмеде көрүнбөсүн"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Калкып чыкма билдирмелер аркылуу маектешүү"</string> - <string name="bubbles_user_education_description" msgid="1160281719576715211">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер болуп көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн таптап коюңуз. Жылдыруу үчүн сүйрөңүз."</string> + <string name="bubbles_user_education_description" msgid="1160281719576715211">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер түрүндө көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн таптап коюңуз. Жылдыруу үчүн сүйрөңүз."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Калкып чыкма билдирмелерди каалаган убакта көзөмөлдөңүз"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Бул колдонмодогу калкып чыкма билдирмелерди өчүрүү үчүн \"Башкарууну\" басыңыз"</string> <string name="bubbles_user_education_got_it" msgid="8282812431953161143">"Түшүндүм"</string> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index d118d8956f17..2d42ce6faa2c 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -31,7 +31,6 @@ <dimen name="battery_detail_graph_space_bottom">9dp</dimen> <integer name="quick_settings_num_columns">4</integer> - <bool name="quick_settings_wide">true</bool> <dimen name="qs_detail_margin_top">0dp</dimen> <dimen name="volume_tool_tip_right_margin">136dp</dimen> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 829d39aac864..0fe47aab6e7c 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"ຈັດການ"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ປະຫວັດ"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"ຂາເຂົ້າ"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"ໃໝ່"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"ປິດສຽງ"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"ການແຈ້ງເຕືອນ"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"ການສົນທະນາ"</string> @@ -598,7 +598,7 @@ <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"ນີ້ຈະເຮັດໃຫ້ມັນຢູ່ໃນມຸມມອງຈົນກວ່າທ່ານຈະເຊົາປັກໝຸດ. ປັດຂຶ້ນຄ້າງໄວ້ເພື່ອເຊົາປັກໝຸດ."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"ນີ້ຈະສະແດງມັນໃນໜ້າຈໍຈົນກວ່າທ່ານຈະເຊົາປັກມຸດ. ໃຫ້ແຕະປຸ່ມພາບຮວມຄ້າງໄວ້ເພື່ອຍົກເລີກການປັກມຸດ."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"ນີ້ຈະສະແດງມັນໃນໜ້າຈໍຈົນກວ່າທ່ານຈະເຊົາປັກໝຸດ. ໃຫ້ແຕະປຸ່ມພາບຮວມຄ້າງໄວ້ເພື່ອຍົກເລີກການປັກໝຸດ."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"ອາດສາມາດເຂົ້າເຖິງຂໍ້ມູນສ່ວນຕົວໄດ້ (ເຊັ່ນ: ລາຍຊື່ຜູ້ຕິດຕໍ່ ແລະ ເນື້ອຫາອີເມວ)"</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"ອາດສາມາດເຂົ້າເຖິງຂໍ້ມູນສ່ວນຕົວໄດ້ (ເຊັ່ນ: ລາຍຊື່ຜູ້ຕິດຕໍ່ ແລະ ເນື້ອຫາອີເມວ)."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"ແອັບທີ່ປັກໝຸດໄວ້ອາດເປີດແອັບອື່ນ."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"ເພື່ອຍົກເລີກການປັກໝຸດແອັບນີ້, ໃຫ້ແຕະປຸ່ມກັບຄືນ ແລະ ປຸ່ມພາບຮວມຄ້າງໄວ້"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"ເພື່ອຍົກເລີກການປັກໝຸດແອັບນີ້, ໃຫ້ແຕະປຸ່ມກັບຄືນ ແລະ ປຸ່ມພາບຮວມຄ້າງໄວ້"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ຍ້າຍຂວາເທິງ"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"ຍ້າຍຊ້າຍລຸ່ມ"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"ຍ້າຍຂວາລຸ່ມ"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"ປິດ bubble ໄວ້"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"ຢ່າໃຊ້ຟອງໃນການສົນທະນາ"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"ສົນທະນາໂດຍໃຊ້ຟອງ"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"ການສົນທະນາໃໝ່ຈະປາກົດເປັນໄອຄອນ ຫຼື ຟອງແບບລອຍ. ແຕະເພື່ອເປີດຟອງ. ລາກເພື່ອຍ້າຍມັນ."</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index de793947a037..59cab5795f13 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -516,7 +516,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Tvarkyti"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Gaunami"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nauja"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Tylūs"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Pranešimai"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Pokalbiai"</string> @@ -724,10 +724,10 @@ <string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Naudojant slankųjį spartųjį klavišą lengviau sutelkti dėmesį į šį turinį."</string> <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Rodoma pokalbių skilties viršuje, rodoma kaip slankusis burbulas, pateikiama profilio nuotrauka užrakinimo ekrane"</string> <string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Nustatymai"</string> - <string name="notification_priority_title" msgid="2079708866333537093">"Prioritetas"</string> + <string name="notification_priority_title" msgid="2079708866333537093">"Prioritetiniai"</string> <string name="no_shortcut" msgid="8257177117568230126">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ nepalaiko pokalbių funkcijų"</string> <string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Nėra naujausių burbulų"</string> - <string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Naujausi ir atsisakyti burbulus bus rodomi čia"</string> + <string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Naujausi ir atsisakyti burbulai bus rodomi čia"</string> <string name="notification_unblockable_desc" msgid="2073030886006190804">"Šių pranešimų keisti negalima."</string> <string name="notification_multichannel_desc" msgid="7414593090056236179">"Šios grupės pranešimai čia nekonfigūruojami"</string> <string name="notification_delegate_header" msgid="1264510071031479920">"Per tarpinį serverį gautas pranešimas"</string> @@ -1012,11 +1012,10 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Perkelti į viršų dešinėje"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Perkelti į apačią kairėje"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Perkelti į apačią dešinėje"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Atsisakyti burbulo"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Nerodyti pokalbio burbule"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Pokalbis naudojant burbulus"</string> - <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nauji pokalbiai rodomi kaip slankiosios piktogramos arba burbulus. Palieskite, kad atidarytumėte burbulą. Vilkite, kad perkeltumėte."</string> + <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nauji pokalbiai rodomi kaip slankiosios piktogramos arba burbulai. Palieskite, kad atidarytumėte burbulą. Vilkite, kad perkeltumėte."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Bet kada valdyti burbulus"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Palieskite „Tvarkyti“, kad išjungtumėte burbulus šioje programoje"</string> <string name="bubbles_user_education_got_it" msgid="8282812431953161143">"Supratau"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index ba6b8954f655..bc157026e745 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -513,7 +513,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Pārvaldīt"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Vēsture"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Ienākošie"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Jauni"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Klusums"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Paziņojumi"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Sarunas"</string> @@ -1007,8 +1007,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Pārvietot augšpusē pa labi"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Pārvietot apakšpusē pa kreisi"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Pārvietot apakšpusē pa labi"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Nerādīt burbuli"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Nerādīt sarunu burbuļos"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Tērzēšana, izmantojot burbuļus"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Jaunas sarunas tiek rādītas kā peldošas ikonas vai burbuļi. Pieskarieties, lai atvērtu burbuli. Velciet, lai to pārvietotu."</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 522cebd8ea6c..09e7ff17714f 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -479,7 +479,7 @@ <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Да се отстрани гостинот?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Сите апликации и податоци во сесијата ќе се избришат."</string> <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Отстрани"</string> - <string name="guest_wipe_session_title" msgid="7147965814683990944">"Добредојде назад, гостине!"</string> + <string name="guest_wipe_session_title" msgid="7147965814683990944">"Добре дојде пак, гостине!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"Дали сакате да продолжите со сесијата?"</string> <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Почни одново"</string> <string name="guest_wipe_session_dontwipe" msgid="3211052048269304205">"Да, продолжи"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Управувајте"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Дојдовни"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Нов"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Безгласно"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Известувања"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Разговори"</string> @@ -592,20 +592,20 @@ <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"овозможи"</string> <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"оневозможи"</string> <string name="accessibility_output_chooser" msgid="7807898688967194183">"Префрлете го излезниот уред"</string> - <string name="screen_pinning_title" msgid="9058007390337841305">"Апликацијата е прикачена"</string> + <string name="screen_pinning_title" msgid="9058007390337841305">"Апликацијата е закачена"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Ќе се гледа сѐ додека не го откачите. Допрете и држете „Назад“ и „Краток преглед“ за откачување."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Ќе се гледа сѐ додека не го откачите. Допрете и задржете „Назад“ и „Почетен екран“ за откачување."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ќе се гледа сѐ додека не го откачите. Лизгајте нагоре и задржете за откачување."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ќе се гледа сѐ додека не ја откачите. Повлечете нагоре и задржете за откачување."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ќе се гледа сѐ додека не го откачите. Допрете и држете „Краток преглед“ за откачување."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Ќе се гледа сѐ додека не го откачите. Допрете и задржете „Почетен екран“ за откачување."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Личните податоци може да се пристапни (како контакти и содржини од е-пошта)."</string> - <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Прикачените апликации може да отворат други апликации."</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Може да бидат достапни лични податоци (како контакти и содржини од е-пошта)."</string> + <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Закачената апликација може да отвора други апликации."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"За откачување на апликацијава, допрете и држете на копчињата „Назад“ и „Преглед“"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"За откачување на апликацијава, допрете и држете на копчињата „Назад“ и „Почетен екран“"</string> <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"За откачување на апликацијава, повлечете нагоре и држете"</string> <string name="screen_pinning_positive" msgid="3285785989665266984">"Сфатив"</string> <string name="screen_pinning_negative" msgid="6882816864569211666">"Не, фала"</string> - <string name="screen_pinning_start" msgid="7483998671383371313">"Апликацијата е прикачена"</string> + <string name="screen_pinning_start" msgid="7483998671383371313">"Апликацијата е закачена"</string> <string name="screen_pinning_exit" msgid="4553787518387346893">"Апликацијата е откачена"</string> <string name="quick_settings_reset_confirmation_title" msgid="463533331480997595">"Сокриј <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> <string name="quick_settings_reset_confirmation_message" msgid="2320586180785674186">"Ќе се појави повторно следниот пат кога ќе го вклучите во поставки."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Премести горе десно"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Премести долу лево"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Премести долу десно"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Отфрли балонче"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Не прикажувај го разговорот во балончиња"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Разговор во балончиња"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Новите разговори ќе се појавуваат како лебдечки икони или балончиња. Допрете за отворање на балончето. Повлечете за да го преместите."</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 03e826f54e45..e4d4ba407732 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -510,7 +510,8 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്ക്കുക"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ചരിത്രം"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"ഇൻകമിംഗ്"</string> + <!-- no translation found for notification_section_header_incoming (850925217908095197) --> + <skip /> <string name="notification_section_header_gentle" msgid="6804099527336337197">"നിശബ്ദം"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"അറിയിപ്പുകൾ"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"സംഭാഷണങ്ങൾ"</string> @@ -1002,11 +1003,10 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"മുകളിൽ വലതുഭാഗത്തേക്ക് നീക്കുക"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"ചുവടെ ഇടതുഭാഗത്തേക്ക് നീക്കുക"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"ചുവടെ വലതുഭാഗത്തേക്ക് നീക്കുക"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"ബബിൾ ഡിസ്മിസ് ചെയ്യൂ"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"സംഭാഷണം ബബിൾ ചെയ്യരുത്"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"ബബിളുകൾ ഉപയോഗിച്ച് ചാറ്റ് ചെയ്യുക"</string> - <string name="bubbles_user_education_description" msgid="1160281719576715211">"പുതിയ സംഭാഷണങ്ങൾ ഫ്ലോട്ടിംഗ് ഐക്കണുകളോ ബബിളുകളോ ആയി ദൃശ്യമാവുന്നു. ബബിൾ തുറക്കാൻ ടാപ്പ് ചെയ്യു. ഇത് നീക്കാൻ വലിച്ചിടുക."</string> + <string name="bubbles_user_education_description" msgid="1160281719576715211">"പുതിയ സംഭാഷണങ്ങൾ ഫ്ലോട്ടിംഗ് ഐക്കണുകളോ ബബിളുകളോ ആയി ദൃശ്യമാവുന്നു. ബബിൾ തുറക്കാൻ ടാപ്പ് ചെയ്യൂ. ഇത് നീക്കാൻ വലിച്ചിടുക."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"ബബിളുകൾ ഏതുസമയത്തും നിയന്ത്രിക്കുക"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"ഈ ആപ്പിൽ നിന്നുള്ള ബബിളുകൾ ഓഫാക്കാൻ മാനേജ് ചെയ്യുക ടാപ്പ് ചെയ്യുക"</string> <string name="bubbles_user_education_got_it" msgid="8282812431953161143">"ലഭിച്ചു"</string> @@ -1014,17 +1014,14 @@ <string name="notification_content_system_nav_changed" msgid="5077913144844684544">"സിസ്റ്റം നാവിഗേഷൻ അപ്ഡേറ്റ് ചെയ്തു. മാറ്റങ്ങൾ വരുത്താൻ ക്രമീകരണത്തിലേക്ക് പോവുക."</string> <string name="notification_content_gesture_nav_available" msgid="4431460803004659888">"സിസ്റ്റം നാവിഗേഷൻ അപ്ഡേറ്റ് ചെയ്യാൻ ക്രമീകരണത്തിലേക്ക് പോവുക"</string> <string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"സ്റ്റാൻഡ്ബൈ"</string> - <!-- no translation found for priority_onboarding_title (2893070698479227616) --> - <skip /> - <!-- no translation found for priority_onboarding_behavior (5342816047020432929) --> - <skip /> + <string name="priority_onboarding_title" msgid="2893070698479227616">"സംഭാഷണം മുൻഗണനയുള്ളതായി സജ്ജീകരിച്ചു"</string> + <string name="priority_onboarding_behavior" msgid="5342816047020432929">"മുൻഗണനാ സംഭാഷണങ്ങൾ ഇനിപ്പറയുന്നവ ചെയ്യും:"</string> <string name="priority_onboarding_show_at_top_text" msgid="1678400241025513541">"സംഭാഷണ വിഭാഗത്തിന്റെ മുകളിൽ കാണിക്കുക"</string> <string name="priority_onboarding_show_avatar_text" msgid="5756291381124091508">"ലോക്ക് സ്ക്രീനിൽ പ്രൊഫൈൽ ചിത്രം കാണിക്കുക"</string> <string name="priority_onboarding_appear_as_bubble_text" msgid="4227039772250263122">"ആപ്പുകളുടെ മുകളിൽ ഫ്ലോട്ടിംഗ് ബബിൾ ആയി ദൃശ്യമാകും"</string> <string name="priority_onboarding_ignores_dnd_text" msgid="2918952762719600529">"\'ശല്യപ്പെടുത്തരുത്\' തടസ്സപ്പെടുത്തുക"</string> <string name="priority_onboarding_done_button_title" msgid="4569550984286506007">"മനസ്സിലായി"</string> - <!-- no translation found for priority_onboarding_settings_button_title (6663601574303585927) --> - <skip /> + <string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ക്രമീകരണം"</string> <string name="magnification_overlay_title" msgid="6584179429612427958">"മാഗ്നിഫിക്കേഷൻ ഓവർലേ വിൻഡോ"</string> <string name="magnification_window_title" msgid="4863914360847258333">"മാഗ്നിഫിക്കേഷൻ വിൻഡോ"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"മാഗ്നിഫിക്കേഷൻ വിൻഡോ നിയന്ത്രണങ്ങൾ"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 9a15e98fa763..4ab58f089e24 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Удирдах"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Түүх"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Ирж буй"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Шинэ"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Чимээгүй"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Мэдэгдлүүд"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Харилцан яриа"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Баруун дээш зөөх"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Зүүн доош зөөх"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Баруун доош зөөх"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Бөмбөлгийг хаах"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Харилцан яриаг бүү бөмбөлөг болго"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Бөмбөлөг ашиглан чатлаарай"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Шинэ харилцан яриа нь хөвөгч дүрс тэмдэг эсвэл бөмбөлөг хэлбэрээр харагддаг. Бөмбөлгийг нээхийн тулд товшино уу. Түүнийг зөөхийн тулд чирнэ үү."</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index cbc50b90e300..d49f46d840b2 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -510,7 +510,8 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"आलेल्या"</string> + <!-- no translation found for notification_section_header_incoming (850925217908095197) --> + <skip /> <string name="notification_section_header_gentle" msgid="6804099527336337197">"सायलंट"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"सूचना"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"संभाषणे"</string> @@ -1002,8 +1003,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"वर उजवीकडे हलवा"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"तळाशी डावीकडे हलवा"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"तळाशी उजवीकडे हलवा"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"बबल डिसमिस करा"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"संभाषणाला बबल करू नका"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"बबल वापरून चॅट करा"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"नवीन संभाषणे फ्लोटिंग आयकन किंवा बबल म्हणून दिसतात. बबल उघडण्यासाठी टॅप करा. हे हलवण्यासाठी ड्रॅग करा."</string> @@ -1014,17 +1014,14 @@ <string name="notification_content_system_nav_changed" msgid="5077913144844684544">"सिस्टम नेव्हिगेशन अपडेट केले. बदल करण्यासाठी, सेटिंग्जवर जा."</string> <string name="notification_content_gesture_nav_available" msgid="4431460803004659888">"सिस्टम नेव्हिगेशन अपडेट करण्यासाठी सेटिंग्जवर जा"</string> <string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"स्टँडबाय"</string> - <!-- no translation found for priority_onboarding_title (2893070698479227616) --> - <skip /> - <!-- no translation found for priority_onboarding_behavior (5342816047020432929) --> - <skip /> + <string name="priority_onboarding_title" msgid="2893070698479227616">"संभाषणाला प्राधान्य म्हणून सेट केले आहे"</string> + <string name="priority_onboarding_behavior" msgid="5342816047020432929">"प्राधान्य दिलेली संभाषणे:"</string> <string name="priority_onboarding_show_at_top_text" msgid="1678400241025513541">"संभाषण विभागाच्या सर्वात वरती दाखवा"</string> <string name="priority_onboarding_show_avatar_text" msgid="5756291381124091508">"लॉक स्क्रीनवर प्रोफाइल फोटो दाखवा"</string> <string name="priority_onboarding_appear_as_bubble_text" msgid="4227039772250263122">"ॲप्सच्या सर्वात वरती फ्लोटिंग बबल म्हणून दिसतील"</string> <string name="priority_onboarding_ignores_dnd_text" msgid="2918952762719600529">"व्यत्यय आणू नका मध्ये अडथळा आणतील"</string> <string name="priority_onboarding_done_button_title" msgid="4569550984286506007">"समजले"</string> - <!-- no translation found for priority_onboarding_settings_button_title (6663601574303585927) --> - <skip /> + <string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"सेटिंग्ज"</string> <string name="magnification_overlay_title" msgid="6584179429612427958">"मॅग्निफिकेशन ओव्हरले विंडो"</string> <string name="magnification_window_title" msgid="4863914360847258333">"मॅग्निफिकेशन विंडो"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"मॅग्निफिकेशन विंडो नियंत्रणे"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index f89d5a0a4d4a..be17eade57a1 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Urus"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Sejarah"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Masuk"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Baharu"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Senyap"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Pemberitahuan"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Perbualan"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Alihkan ke atas sebelah kanan"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Alihkan ke bawah sebelah kiri"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Alihkan ke bawah sebelah kanan"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Ketepikan gelembung"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Jangan jadikan perbualan dalam bentuk gelembung"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Bersembang menggunakan gelembung"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Perbualan baharu muncul sebagai ikon terapung atau gelembung. Ketik untuk membuka gelembung. Seret untuk mengalihkan gelembung tersebut."</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index dc70e80a7125..eaf4157ff4e1 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -101,7 +101,7 @@ <string name="screenrecord_start" msgid="330991441575775004">"စတင်ရန်"</string> <string name="screenrecord_ongoing_screen_only" msgid="4459670242451527727">"ဖန်သားပြင်ကို ရိုက်ကူးနေသည်"</string> <string name="screenrecord_ongoing_screen_and_audio" msgid="5351133763125180920">"အသံနှင့် ဖန်သားပြင်ကို ရိုက်ကူးနေသည်"</string> - <string name="screenrecord_taps_label" msgid="1595690528298857649">"မျက်နှာပြင်ပေါ်တွင် ထိချက်များ ပြသည်"</string> + <string name="screenrecord_taps_label" msgid="1595690528298857649">"မျက်နှာပြင်ပေါ်တွင် ထိချက်များ ပြရန်"</string> <string name="screenrecord_stop_text" msgid="6549288689506057686">"ရပ်ရန် တို့ပါ"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"ရပ်ရန်"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"ခဏရပ်ရန်"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံး ဖယ်ရှားရန်"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"စီမံရန်"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"မှတ်တမ်း"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"အဝင်"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"အသစ်"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"အသံတိတ်ခြင်း"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"အကြောင်းကြားချက်များ"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"စကားဝိုင်းများ"</string> @@ -598,7 +598,7 @@ <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"သင်က ပင်မဖြုတ်မချင်း ၎င်းကို ပြသထားပါမည်။ ပင်ဖြုတ်ရန် အပေါ်သို့ပွတ်ဆွဲပြီး ဖိထားပါ။"</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"သင်ပင်မဖြုတ်မချင်း ၎င်းကိုပြသထားပါမည်။ ပင်ဖြုတ်ရန် Overview ကိုထိပြီး ဖိထားပါ။"</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"သင်က ပင်မဖြုတ်မချင်း ၎င်းကိုပြသထားပါမည်။ ပင်ဖြုတ်ရန် \'ပင်မ\' ခလုတ်ကို တို့၍ဖိထားပါ။"</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"(အဆက်အသွယ်နှင့် အီးမေးလ်အကြောင်းအရာများကဲ့သို့) ကိုယ်ရေးကိုယ်တာ ဒေတာများကို အသုံးပြုနိုင်ပါသည်။"</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"ကိုယ်ရေးကိုယ်တာ ဒေတာများ (အဆက်အသွယ်နှင့် အီးမေးလ် အကြောင်းအရာများကဲ့သို့) ကို အသုံးပြုနိုင်ပါသည်။"</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"ပင်ထိုးထားသည့်အက်ပ်က အခြားအက်ပ်များကို ဖွင့်နိုင်သည်။"</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"ဤအက်ပ်ကိုပင်ဖြုတ်ရန် \'နောက်သို့\' နှင့် \'အနှစ်ချုပ်\' ခလုတ်များကို ထိ၍နှိပ်ထားပါ"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"ဤအက်ပ်ကိုပင်ဖြုတ်ရန် \'နောက်သို့\' နှင့် \'ပင်မ\' ခလုတ်တို့ကို ထိ၍နှိပ်ထားပါ"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ညာဘက်ထိပ်သို့ ရွှေ့ပါ"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"ဘယ်အောက်ခြေသို့ ရွှေ့ရန်"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"ညာအောက်ခြေသို့ ရွှေ့ပါ"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"ပူဖောင်းကွက် ပယ်ရန်"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"စကားဝိုင်းကို ပူဖောင်းကွက် မပြုလုပ်ပါနှင့်"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"ပူဖောင်းကွက် သုံး၍ ချတ်လုပ်ခြင်း"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"စကားဝိုင်းအသစ်များကို မျောနေသည့် သင်္ကေတများ သို့မဟုတ် ပူဖောင်းကွက်များအဖြစ် မြင်ရပါမည်။ ပူဖောင်းကွက်ကိုဖွင့်ရန် တို့ပါ။ ရွှေ့ရန် ၎င်းကို ဖိဆွဲပါ။"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 53806a7e4daa..aee2dd288262 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Logg"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Innkommende"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Ny"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Lydløs"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Varsler"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Samtaler"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Flytt til øverst til høyre"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Flytt til nederst til venstre"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Flytt til nederst til høyre"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Lukk boblen"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Ikke vis samtaler i bobler"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chat med bobler"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nye samtaler vises som flytende ikoner eller bobler. Trykk for å åpne bobler. Dra for å flytte dem."</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 19cf233fa092..fda85ccb542c 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -510,7 +510,8 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थित गर्नुहोस्"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"हालसालै प्राप्त भएका सूचनाहरू"</string> + <!-- no translation found for notification_section_header_incoming (850925217908095197) --> + <skip /> <string name="notification_section_header_gentle" msgid="6804099527336337197">"मौन"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"सूचनाहरू"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"वार्तालापहरू"</string> @@ -595,7 +596,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"एप पिन गरिएको छ"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न पछाडि र परिदृश्य बटनलाई छोइराख्नुहोस्।"</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न पछाडि र गृह नामक बटनहरूलाई छोइराख्नुहोस्।"</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"तपाईंले अनपिन नगरेसम्म यस कार्यले यसलाई दृश्यमा राख्छ। अनपिन गर्न माथितिर स्वाइप गरी होल्ड गर्नुहोस्।"</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"तपाईंले यो एप अनपिन नगरेसम्म यो एप यहाँ देखिइरहने छ। अनपिन गर्न माथितिर स्वाइप गरी होल्ड गर्नुहोस्।"</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न परिदृश्य बटनलाई छोइराख्नुहोस्।"</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न गृह नामक बटनलाई छोइराख्नुहोस्।"</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"स्क्रिनमा व्यक्तिगत डेटा (जस्तै सम्पर्क ठेगाना र इमेलको सामग्री) देखिन सक्छ।"</string> @@ -1002,8 +1003,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"सिरानमा दायाँतिर सार्नुहोस्"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"पुछारमा बायाँतिर सार्नुहोस्"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"पुछारमा दायाँतिर सार्नुहोस्"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"बबल खारेज गर्नुहोस्"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"वार्तालाप बबलको रूपमा नदेखाइयोस्"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"बबलहरू प्रयोग गरी कुराकानी गर्नुहोस्"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"नयाँ वार्तालापहरू तैरने आइकन वा बबलका रूपमा देखिन्छन्। बबल खोल्न ट्याप गर्नुहोस्। बबल सार्न सो बबललाई ड्र्याग गर्नुहोस्।"</string> @@ -1014,17 +1014,14 @@ <string name="notification_content_system_nav_changed" msgid="5077913144844684544">"प्रणालीको नेभिगेसन अद्यावधिक गरियो। परिवर्तन गर्न सेटिङमा जानुहोस्।"</string> <string name="notification_content_gesture_nav_available" msgid="4431460803004659888">"प्रणालीको नेभिगेसन अद्यावधिक गर्न सेटिङमा जानुहोस्"</string> <string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"स्ट्यान्डबाई"</string> - <!-- no translation found for priority_onboarding_title (2893070698479227616) --> - <skip /> - <!-- no translation found for priority_onboarding_behavior (5342816047020432929) --> - <skip /> + <string name="priority_onboarding_title" msgid="2893070698479227616">"वार्तालापको प्राथमिकता निर्धारण गरी महत्त्वपूर्ण बनाइयो"</string> + <string name="priority_onboarding_behavior" msgid="5342816047020432929">"महत्वपूर्ण वार्तालापहरू यहाँ देखिने छन्:"</string> <string name="priority_onboarding_show_at_top_text" msgid="1678400241025513541">"वार्तालाप खण्डको सिरानमा देखाइयोस्"</string> <string name="priority_onboarding_show_avatar_text" msgid="5756291381124091508">"लक स्क्रिनमा प्रोफाइल तस्बिर देखाइयोस्"</string> <string name="priority_onboarding_appear_as_bubble_text" msgid="4227039772250263122">"एपहरूमाथि तैरिने बबलका रूपमा देखाइयोस्"</string> <string name="priority_onboarding_ignores_dnd_text" msgid="2918952762719600529">"बाधा नपुऱ्याउनुहोस् मोडलाई बेवास्ता गरियोस्"</string> <string name="priority_onboarding_done_button_title" msgid="4569550984286506007">"बुझेँ"</string> - <!-- no translation found for priority_onboarding_settings_button_title (6663601574303585927) --> - <skip /> + <string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"सेटिङ"</string> <string name="magnification_overlay_title" msgid="6584179429612427958">"म्याग्निफिकेसन ओभरले विन्डो"</string> <string name="magnification_window_title" msgid="4863914360847258333">"म्याग्निफिकेसन विन्डो"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"म्याग्निफिकेसन विन्डोका नियन्त्रणहरू"</string> @@ -1070,8 +1067,8 @@ <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय छ, एप जाँच गर्नु…"</string> <string name="controls_error_retryable" msgid="864025882878378470">"त्रुटि भयो, फेरि प्रयास गर्दै…"</string> <string name="controls_error_removed" msgid="6675638069846014366">"फेला परेन"</string> - <string name="controls_error_removed_title" msgid="1207794911208047818">"नियन्त्रण सुविधा उपलब्ध छैन"</string> - <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> माथि पहुँच राख्न सकिएन। यो नियन्त्रण सुविधा अझै पनि उपलब्ध छ र <xliff:g id="APPLICATION">%2$s</xliff:g> एपका सेटिङ परिवर्तन गरिएका छैनन् भन्ने कुरा सुनिश्चित गर्न यो एप जाँच्नुहोस्।"</string> + <string name="controls_error_removed_title" msgid="1207794911208047818">"नियन्त्रण उपलब्ध छैन"</string> + <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> प्रयोग गर्न सकिएन। यो नियन्त्रण सुविधा अझै पनि उपलब्ध छ र <xliff:g id="APPLICATION">%2$s</xliff:g> एपका सेटिङ परिवर्तन गरिएका छैनन् भन्ने कुरा सुनिश्चित गर्न यो एप जाँच्नुहोस्।"</string> <string name="controls_open_app" msgid="483650971094300141">"एप खोल्नुहोस्"</string> <string name="controls_error_generic" msgid="352500456918362905">"वस्तुस्थिति लोड गर्न सकिएन"</string> <string name="controls_error_failed" msgid="960228639198558525">"त्रुटि भयो, फेरि प्रयास गर्नु…"</string> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 50261e1b2139..4fdeb6fa4a92 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -29,9 +29,4 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> </style> - <style name="qs_security_footer" parent="@style/qs_theme"> - <item name="android:textColor">#B3FFFFFF</item> <!-- 70% white --> - <item name="android:tint">#FFFFFFFF</item> - </style> - </resources> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 21e66ab67cd8..87f072b9e7dd 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -259,10 +259,10 @@ <string name="accessibility_bubble_dismissed" msgid="270358867566720729">"Bubbel gesloten."</string> <string name="accessibility_desc_notification_shade" msgid="5355229129428759989">"Meldingenpaneel."</string> <string name="accessibility_desc_quick_settings" msgid="4374766941484719179">"Snelle instellingen."</string> - <string name="accessibility_desc_lock_screen" msgid="5983125095181194887">"Vergrendelingsscherm."</string> + <string name="accessibility_desc_lock_screen" msgid="5983125095181194887">"Vergrendelscherm."</string> <string name="accessibility_desc_settings" msgid="6728577365389151969">"Instellingen"</string> <string name="accessibility_desc_recent_apps" msgid="1748675199348914194">"Overzicht."</string> - <string name="accessibility_desc_work_lock" msgid="4355620395354680575">"Vergrendelingsscherm voor werk"</string> + <string name="accessibility_desc_work_lock" msgid="4355620395354680575">"Vergrendelscherm voor werk"</string> <string name="accessibility_desc_close" msgid="8293708213442107755">"Sluiten"</string> <string name="accessibility_quick_settings_wifi" msgid="167707325133803052">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string> <string name="accessibility_quick_settings_wifi_changed_off" msgid="2230487165558877262">"Wifi uitgeschakeld."</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Beheren"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Geschiedenis"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Inkomend"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nieuw"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Stil"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Meldingen"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Gesprekken"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"App is vastgezet"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Het scherm blijft zichtbaar totdat je het losmaakt. Tik op Terug en Overzicht en houd deze vast om het scherm los te maken."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Het scherm blijft zichtbaar totdat je het losmaakt. Tik op Terug en Home en houd deze vast om het scherm los te maken."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Zo blijft het scherm zichtbaar totdat je dit losmaakt. Veeg omhoog en houd vast om los te maken."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Zo blijft het scherm zichtbaar totdat je dit losmaakt. Swipe omhoog en houd vast om los te maken."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Het scherm blijft zichtbaar totdat je het losmaakt. Tik op Overzicht en houd dit vast om het scherm los te maken."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Het scherm blijft zichtbaar totdat je het losmaakt. Tik op Home en houd dit vast om het scherm los te maken."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Persoonlijke informatie kan toegankelijk zijn (zoals contacten en e-mailcontent)."</string> @@ -687,7 +687,7 @@ <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Beheeropties voor meldingen met betrekking tot stroomverbruik"</string> <string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Aan"</string> <string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Uit"</string> - <string name="power_notification_controls_description" msgid="1334963837572708952">"Met beheeropties voor meldingen met betrekking tot stroomverbruik kun je een belangrijkheidsniveau van 0 tot 5 instellen voor de meldingen van een app. \n\n"<b>"Niveau 5"</b>" \n- Boven aan de lijst met meldingen weergeven \n- Onderbreking op volledig scherm toestaan \n- Altijd korte weergave \n\n"<b>"Niveau 4"</b>" \n- Geen onderbreking op volledig scherm \n- Altijd korte weergave \n\n"<b>"Niveau 3"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n\n"<b>"Niveau 2"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n- Nooit geluid laten horen of trillen \n\n"<b>"Niveau 1"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n- Nooit geluid laten horen of trillen \n- Verbergen op vergrendelingsscherm en statusbalk \n- Onder aan de lijst met meldingen weergeven \n\n"<b>"Niveau 0"</b>" \n- Alle meldingen van de app blokkeren"</string> + <string name="power_notification_controls_description" msgid="1334963837572708952">"Met beheeropties voor meldingen met betrekking tot stroomverbruik kun je een belangrijkheidsniveau van 0 tot 5 instellen voor de meldingen van een app. \n\n"<b>"Niveau 5"</b>" \n- Boven aan de lijst met meldingen weergeven \n- Onderbreking op volledig scherm toestaan \n- Altijd korte weergave \n\n"<b>"Niveau 4"</b>" \n- Geen onderbreking op volledig scherm \n- Altijd korte weergave \n\n"<b>"Niveau 3"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n\n"<b>"Niveau 2"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n- Nooit geluid laten horen of trillen \n\n"<b>"Niveau 1"</b>" \n- Geen onderbreking op volledig scherm \n- Nooit korte weergave \n- Nooit geluid laten horen of trillen \n- Verbergen op vergrendelscherm en statusbalk \n- Onder aan de lijst met meldingen weergeven \n\n"<b>"Niveau 0"</b>" \n- Alle meldingen van de app blokkeren"</string> <string name="notification_header_default_channel" msgid="225454696914642444">"Meldingen"</string> <string name="notification_channel_disabled" msgid="928065923928416337">"Meldingen worden niet meer weergegeven"</string> <string name="notification_channel_minimized" msgid="6892672757877552959">"Deze meldingen worden geminimaliseerd"</string> @@ -716,7 +716,7 @@ <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan overgaan of trillen op basis van de telefooninstellingen"</string> <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan overgaan of trillen op basis van de telefooninstellingen. Gesprekken uit <xliff:g id="APP_NAME">%1$s</xliff:g> worden standaard als bubbels weergegeven."</string> <string name="notification_channel_summary_bubble" msgid="7235935211580860537">"Trekt de aandacht met een zwevende snelkoppeling naar deze content."</string> - <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Wordt bovenaan het gedeelte met gesprekken weergegeven, verschijnt als zwevende bubbel, geeft de profielfoto weer op het vergrendelingsscherm"</string> + <string name="notification_channel_summary_priority" msgid="7952654515769021553">"Wordt bovenaan het gedeelte met gesprekken weergegeven, verschijnt als zwevende bubbel, geeft de profielfoto weer op het vergrendelscherm"</string> <string name="notification_conversation_channel_settings" msgid="2409977688430606835">"Instellingen"</string> <string name="notification_priority_title" msgid="2079708866333537093">"Prioriteit"</string> <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> ondersteunt geen gespreksfuncties"</string> @@ -907,7 +907,7 @@ <string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"<xliff:g id="ID_1">%s</xliff:g>-instellingen openen."</string> <string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"Volgorde van instellingen bewerken."</string> <string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> van <xliff:g id="ID_2">%2$d</xliff:g>"</string> - <string name="tuner_lock_screen" msgid="2267383813241144544">"Vergrendelingsscherm"</string> + <string name="tuner_lock_screen" msgid="2267383813241144544">"Vergrendelscherm"</string> <string name="pip_phone_expand" msgid="1424988917240616212">"Uitvouwen"</string> <string name="pip_phone_minimize" msgid="9057117033655996059">"Minimaliseren"</string> <string name="pip_phone_close" msgid="8801864042095341824">"Sluiten"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Naar rechtsboven verplaatsen"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Naar linksonder verplaatsen"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Naar rechtsonder verplaatsen"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Bubbel sluiten"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Gesprekken niet in bubbels weergeven"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chatten met bubbels"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nieuwe gesprekken worden weergegeven als zwevende iconen of \'bubbels\'. Tik om een bubbel te openen. Sleep om de bubbel te verplaatsen."</string> @@ -1017,7 +1016,7 @@ <string name="priority_onboarding_title" msgid="2893070698479227616">"Gesprek ingesteld als prioriteit"</string> <string name="priority_onboarding_behavior" msgid="5342816047020432929">"Bij prioriteitsgesprekken gebeurt het volgende:"</string> <string name="priority_onboarding_show_at_top_text" msgid="1678400241025513541">"Worden bovenaan het gespreksgedeelte weergegeven"</string> - <string name="priority_onboarding_show_avatar_text" msgid="5756291381124091508">"Tonen profielafbeelding op vergrendelingsscherm"</string> + <string name="priority_onboarding_show_avatar_text" msgid="5756291381124091508">"Tonen profielafbeelding op vergrendelscherm"</string> <string name="priority_onboarding_appear_as_bubble_text" msgid="4227039772250263122">"Worden als zwevende ballon weergegeven vóór apps"</string> <string name="priority_onboarding_ignores_dnd_text" msgid="2918952762719600529">"Onderbreken \'Niet storen\'"</string> <string name="priority_onboarding_done_button_title" msgid="4569550984286506007">"OK"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index f575f9868427..426bd2ec1ada 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -273,7 +273,7 @@ <string name="accessibility_quick_settings_airplane_on" msgid="8106176561295294255">"ଏୟାର୍ପ୍ଲେନ୍ ମୋଡ୍ ଅନ୍ ଅଛି।"</string> <string name="accessibility_quick_settings_airplane_changed_off" msgid="8880183481476943754">"ଏୟାର୍ପ୍ଲେନ୍ ମୋଡ୍କୁ ବନ୍ଦ କରାଯାଇଛି।"</string> <string name="accessibility_quick_settings_airplane_changed_on" msgid="6327378061894076288">"ଏୟାର୍ପ୍ଲେନ୍ ମୋଡ୍କୁ ଚାଲୁ କରାଯାଇଛି।"</string> - <string name="accessibility_quick_settings_dnd_none_on" msgid="3235552940146035383">"ସମ୍ପୂର୍ଣ୍ଣ ନିରବ"</string> + <string name="accessibility_quick_settings_dnd_none_on" msgid="3235552940146035383">"ସମ୍ପୂର୍ଣ୍ଣ ନୀରବତା"</string> <string name="accessibility_quick_settings_dnd_alarms_on" msgid="3375848309132140014">"କେବଳ ଆଲାର୍ମ"</string> <string name="accessibility_quick_settings_dnd" msgid="2415967452264206047">"ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ।"</string> <string name="accessibility_quick_settings_dnd_changed_off" msgid="1457150026842505799">"\"ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ\"କୁ ବନ୍ଦ କରାଯାଇଛି।"</string> @@ -345,7 +345,7 @@ <string name="quick_settings_dnd_label" msgid="7728690179108024338">"ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ"</string> <string name="quick_settings_dnd_priority_label" msgid="6251076422352664571">"କେବଳ ପ୍ରାଥମିକତା"</string> <string name="quick_settings_dnd_alarms_label" msgid="1241780970469630835">"କେବଳ ଆଲାର୍ମ"</string> - <string name="quick_settings_dnd_none_label" msgid="8420869988472836354">"ସମ୍ପୂର୍ଣ୍ଣ ନୀରବ"</string> + <string name="quick_settings_dnd_none_label" msgid="8420869988472836354">"ସମ୍ପୂର୍ଣ୍ଣ ନୀରବତା"</string> <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"ବ୍ଲୁଟୁଥ"</string> <string name="quick_settings_bluetooth_multiple_devices_label" msgid="6595808498429809855">"ବ୍ଲୁଟୂଥ୍ (<xliff:g id="NUMBER">%d</xliff:g>ଟି ଡିଭାଇସ୍)"</string> <string name="quick_settings_bluetooth_off_label" msgid="6375098046500790870">"ବ୍ଲୁଟୂଥ୍ ଅଫ୍"</string> @@ -460,7 +460,7 @@ <string name="voice_hint" msgid="7476017460191291417">"ଭଏସ୍ ସହାୟକ ପାଇଁ ଆଇକନରୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ"</string> <string name="camera_hint" msgid="4519495795000658637">"କ୍ୟାମେରା ପାଇଁ ଆଇକନରୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ"</string> <string name="interruption_level_none_with_warning" msgid="8394434073508145437">"ସମ୍ପୂର୍ଣ୍ଣ ନୀରବ। ଏହାଦ୍ୱାରା ସ୍କ୍ରୀନ୍ ରିଡର୍ ମଧ୍ୟ ନୀରବ ହୋଇଯିବ।"</string> - <string name="interruption_level_none" msgid="219484038314193379">"ସମ୍ପୂର୍ଣ୍ଣ ନୀରବ"</string> + <string name="interruption_level_none" msgid="219484038314193379">"ସମ୍ପୂର୍ଣ୍ଣ ନୀରବତା"</string> <string name="interruption_level_priority" msgid="661294280016622209">"କେବଳ ପ୍ରାଥମିକତା"</string> <string name="interruption_level_alarms" msgid="2457850481335846959">"କେବଳ ଆଲାର୍ମ"</string> <string name="interruption_level_none_twoline" msgid="8579382742855486372">"ସମ୍ପୂର୍ଣ୍ଣ\nନୀରବ"</string> @@ -510,7 +510,8 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"ସମସ୍ତ ଖାଲି କରନ୍ତୁ"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"ପରିଚାଳନା କରନ୍ତୁ"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ଇତିହାସ"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"ଇନକମିଂ"</string> + <!-- no translation found for notification_section_header_incoming (850925217908095197) --> + <skip /> <string name="notification_section_header_gentle" msgid="6804099527336337197">"ନୀରବ"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ"</string> @@ -595,7 +596,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"ଆପକୁ ପିନ୍ କରାଯାଇଛି"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"ଆପଣ ଅନପିନ୍ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହା ଦେଖାଉଥିବ। ଅନପିନ୍ କରିବାକୁ ସ୍ପର୍ଶ କରି ଧରିରଖନ୍ତୁ ଓ ଦେଖନ୍ତୁ।"</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"ଆପଣ ଅନପିନ୍ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହା ଦେଖାଉଥିବ। ଅନପିନ୍ କରିବା ପାଇଁ ହୋମ୍ ଓ ବ୍ୟାକ୍ ବଟନ୍କୁ ଧରିରଖନ୍ତୁ।"</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"ଆପଣ ଅନ୍ପିନ୍ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହା ଦେଖାଯାଉଥିବ। ଅନ୍ପିନ୍ କରିବା ପାଇଁ ଉପରକୁ ସ୍ୱାଇପ୍ କରି ଧରି ରଖନ୍ତୁ"</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"ଆପଣ ଅନ୍ପିନ୍ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହା ଦେଖାଯାଉଥିବ। ଅନ୍ପିନ୍ କରିବା ପାଇଁ ଉପରକୁ ସ୍ୱାଇପ୍ କରି ଧରି ରଖନ୍ତୁ।"</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"ଆପଣ ଅନପିନ୍ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହା ଦେଖାଉଥିବ। ଅନପିନ୍ କରିବାକୁ ସ୍ପର୍ଶ କରନ୍ତୁ ଏବଂ ଓଭରଭ୍ୟୁକୁ ଧରିରଖନ୍ତୁ।"</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"ଆପଣ ଅନପିନ୍ ନକରିବା ପର୍ଯ୍ୟନ୍ତ ଏହା ଦେଖାଉଥିବ। ଅନପିନ୍ କରିବା ପର୍ଯ୍ୟନ୍ତ ହୋମ୍କୁ ଦାବିଧରନ୍ତୁ।"</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"ବ୍ୟକ୍ତିଗତ ଡାଟାକୁ ଆକ୍ସେସ୍ କରାଯାଇପାରେ (ଯେପରିକି ଯୋଗାଯୋଗଗୁଡ଼ିକ ଏବଂ ଇମେଲ୍ ବିଷୟବସ୍ତୁ)।"</string> @@ -1002,8 +1003,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ଉପର-ଡାହାଣକୁ ନିଅନ୍ତୁ"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"ତଳ ବାମକୁ ନିଅନ୍ତୁ"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"ତଳ ଡାହାଣକୁ ନିଅନ୍ତୁ"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"ବବଲ୍ ଖାରଜ କରନ୍ତୁ"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"ବାର୍ତ୍ତାଳାପକୁ ବବଲ୍ କରନ୍ତୁ ନାହିଁ"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"ବବଲଗୁଡ଼ିକୁ ବ୍ୟବହାର କରି ଚାଟ୍ କରନ୍ତୁ"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"ନୂଆ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ଫ୍ଲୋଟିଂ ଆଇକନ୍ କିମ୍ବା ବବଲ୍ ଭାବେ ଦେଖାଯିବ। ବବଲ୍ ଖୋଲିବାକୁ ଟାପ୍ କରନ୍ତୁ। ଏହାକୁ ମୁଭ୍ କରିବାକୁ ଟାଣନ୍ତୁ।"</string> @@ -1014,17 +1014,14 @@ <string name="notification_content_system_nav_changed" msgid="5077913144844684544">"ସିଷ୍ଟମ୍ ନାଭିଗେସନ୍ ଅପ୍ଡେଟ୍ ହୋଇଛି। ପରିବର୍ତ୍ତନ କରିବା ପାଇଁ, ସେଟିଂସ୍କୁ ଯାଆନ୍ତୁ।"</string> <string name="notification_content_gesture_nav_available" msgid="4431460803004659888">"ସିଷ୍ଟମ୍ ନାଭିଗେସନ୍ ଅପ୍ଡେଟ୍ କରିବା ପାଇଁ ସେଟିଂସ୍କୁ ଯାଆନ୍ତୁ"</string> <string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"ଷ୍ଟାଣ୍ଡବାଏ"</string> - <!-- no translation found for priority_onboarding_title (2893070698479227616) --> - <skip /> - <!-- no translation found for priority_onboarding_behavior (5342816047020432929) --> - <skip /> + <string name="priority_onboarding_title" msgid="2893070698479227616">"ବାର୍ତ୍ତାଳାପ ପ୍ରାଥମିକରେ ସେଟ୍ କରାଯାଇଛି"</string> + <string name="priority_onboarding_behavior" msgid="5342816047020432929">"ପ୍ରାଥମିକ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ଏଠାରେ ଦେଖାଯିବ:"</string> <string name="priority_onboarding_show_at_top_text" msgid="1678400241025513541">"ବାର୍ତ୍ତାଳାପ ବିଭାଗର ଶୀର୍ଷରେ ଦେଖାନ୍ତୁ"</string> <string name="priority_onboarding_show_avatar_text" msgid="5756291381124091508">"ଲକ୍ ସ୍କ୍ରିନରେ ପ୍ରୋଫାଇଲ୍ ଛବି ଦେଖାନ୍ତୁ"</string> <string name="priority_onboarding_appear_as_bubble_text" msgid="4227039772250263122">"ଆପଗୁଡ଼ିକ ଉପରେ ଫ୍ଲୋଟିଂ ବବଲ୍ ପରି ଦେଖାଯିବ"</string> <string name="priority_onboarding_ignores_dnd_text" msgid="2918952762719600529">"\'ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ\' ମୋଡରେ ବାଧା"</string> <string name="priority_onboarding_done_button_title" msgid="4569550984286506007">"ବୁଝିଗଲି"</string> - <!-- no translation found for priority_onboarding_settings_button_title (6663601574303585927) --> - <skip /> + <string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ସେଟିଂସ୍"</string> <string name="magnification_overlay_title" msgid="6584179429612427958">"ମ୍ୟାଗ୍ନିଫିକେସନ୍ ଓଭର୍ଲେ ୱିଣ୍ଡୋ"</string> <string name="magnification_window_title" msgid="4863914360847258333">"ମ୍ୟାଗ୍ନିଫିକେସନ୍ ୱିଣ୍ଡୋ"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"ମ୍ୟାଗ୍ନିଫିକେସନ୍ ୱିଣ୍ଡୋ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ"</string> @@ -1069,9 +1066,9 @@ <string name="controls_media_resume" msgid="1933520684481586053">"ପୁଣି ଆରମ୍ଭ କରନ୍ତୁ"</string> <string name="controls_error_timeout" msgid="794197289772728958">"ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ"</string> <string name="controls_error_retryable" msgid="864025882878378470">"ତ୍ରୁଟି, ପୁଣି ଚେଷ୍ଟା କରୁଛି…"</string> - <string name="controls_error_removed" msgid="6675638069846014366">"ମିଳୁ ନାହିଁ"</string> + <string name="controls_error_removed" msgid="6675638069846014366">"ମିଳିଲା ନାହିଁ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ନିୟନ୍ତ୍ରଣ ଉପଲବ୍ଧ ନାହିଁ"</string> - <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g>କୁ ଆକ୍ସେସ୍ କରିହେଲା ନାହିଁ। ନିୟନ୍ତ୍ରଣ ଏବେ ବି ଉପଲବ୍ଧ ଅଛି ଏବଂ ଆପ୍ ସେଟିଂସ୍ ବଦଳାଯାଇ ନାହିଁ ବୋଲି ସୁନିଶ୍ଚିତ କରିବାକୁ <xliff:g id="APPLICATION">%2$s</xliff:g> ଆପ୍ ଯାଞ୍ଚ କରନ୍ତୁ।"</string> + <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g>କୁ ଆକ୍ସେସ୍ କରିହେଲା ନାହିଁ। ନିୟନ୍ତ୍ରଣ ଏବେ ବି ଉପଲବ୍ଧ ଅଛି ଏବଂ ଆପ୍ ସେଟିଂସ୍ ବଦଳାଯାଇ ନାହିଁ ବୋଲି ସୁନିଶ୍ଚିତ କରିବାକୁ <xliff:g id="APPLICATION">%2$s</xliff:g> ଆପକୁ ଯାଞ୍ଚ କରନ୍ତୁ।"</string> <string name="controls_open_app" msgid="483650971094300141">"ଆପ୍ ଖୋଲନ୍ତୁ"</string> <string name="controls_error_generic" msgid="352500456918362905">"ସ୍ଥିତି ଲୋଡ୍ କରାଯାଇପାରିବ ନାହିଁ"</string> <string name="controls_error_failed" msgid="960228639198558525">"ତ୍ରୁଟି ହୋଇଛି, ପୁଣି ଚେଷ୍ଟା କର"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 3389a18c8f9c..ccafc49614bb 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ਇਤਿਹਾਸ"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"ਇਨਕਮਿੰਗ"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"ਨਵਾਂ"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"ਸ਼ਾਂਤ"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"ਸੂਚਨਾਵਾਂ"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"ਗੱਲਾਂਬਾਤਾਂ"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ਉੱਪਰ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"ਹੇਠਾਂ ਵੱਲ ਖੱਬੇ ਲਿਜਾਓ"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"ਹੇਠਾਂ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕਰੋ"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"ਗੱਲਬਾਤ \'ਤੇ ਬਬਲ ਨਾ ਲਾਓ"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"ਬਬਲ ਵਰਤਦੇ ਹੋਏ ਚੈਟ ਕਰੋ"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"ਨਵੀਆਂ ਗੱਲਾਂਬਾਤਾਂ ਫਲੋਟਿੰਗ ਪ੍ਰਤੀਕਾਂ ਜਾਂ ਬਬਲ ਦੇ ਰੂਪ ਵਿੱਚ ਦਿਸਦੀਆਂ ਹਨ। ਬਬਲ ਨੂੰ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ। ਇਸਨੂੰ ਲਿਜਾਣ ਲਈ ਘਸੀਟੋ।"</string> @@ -1068,7 +1067,7 @@ <string name="controls_error_retryable" msgid="864025882878378470">"ਗੜਬੜ, ਮੁੜ ਕੋਸ਼ਿਸ਼ ਹੋ ਰਹੀ ਹੈ…"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ਨਹੀਂ ਮਿਲਿਆ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ਕੰਟਰੋਲ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string> - <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ। ਇਹ ਪੱਕਾ ਕਰਨ ਲਈ <xliff:g id="APPLICATION">%2$s</xliff:g> ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ ਕਿ ਕੰਟਰੋਲ ਹਾਲੇ ਵੀ ਉਪਲਬਧ ਹੈ ਅਤੇ ਐਪ ਸੈਟਿੰਗ ਬਦਲੀ ਨਹੀਂ ਹੈ।"</string> + <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ। ਇਹ ਪੱਕਾ ਕਰਨ ਲਈ <xliff:g id="APPLICATION">%2$s</xliff:g> ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ ਕਿ ਕੰਟਰੋਲ ਹਾਲੇ ਉਪਲਬਧ ਹੈ ਅਤੇ ਐਪ ਸੈਟਿੰਗਾਂ ਨਹੀਂ ਬਦਲੀਆਂ ਹਨ।"</string> <string name="controls_open_app" msgid="483650971094300141">"ਐਪ ਖੋਲ੍ਹੋ"</string> <string name="controls_error_generic" msgid="352500456918362905">"ਸਥਿਤੀ ਲੋਡ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ"</string> <string name="controls_error_failed" msgid="960228639198558525">"ਗੜਬੜ, ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index e85b76f228ac..cf63b1122da7 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -168,7 +168,7 @@ <string name="biometric_dialog_last_password_attempt_before_wipe_profile" msgid="8538032972389729253">"Jeśli następnym razem podasz nieprawidłowe hasło, profil służbowy oraz powiązane z nim dane zostaną usunięte."</string> <string name="biometric_dialog_failed_attempts_now_wiping_device" msgid="6585503524026243042">"Zbyt wiele nieudanych prób. Dane na urządzeniu zostaną usunięte."</string> <string name="biometric_dialog_failed_attempts_now_wiping_user" msgid="7015008539146949115">"Zbyt wiele nieudanych prób. Użytkownik zostanie usunięty."</string> - <string name="biometric_dialog_failed_attempts_now_wiping_profile" msgid="5239378521440749682">"Zbyt wiele nieudanych prób. Profil do pracy i powiązane z nim dane zostaną usunięte."</string> + <string name="biometric_dialog_failed_attempts_now_wiping_profile" msgid="5239378521440749682">"Zbyt wiele nieudanych prób. Profil służbowy i powiązane z nim dane zostaną usunięte."</string> <string name="biometric_dialog_now_wiping_dialog_dismiss" msgid="7189432882125106154">"Zamknij"</string> <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Dotknij czytnika linii papilarnych"</string> <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="4465698996175640549">"Ikona odcisku palca"</string> @@ -516,7 +516,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Zarządzaj"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Przychodzące"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nowe"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Ciche"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Powiadomienia"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Rozmowy"</string> @@ -604,7 +604,7 @@ <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ekran będzie widoczny, dopóki go nie odepniesz. Przesuń palcem w górę i przytrzymaj, by odpiąć."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ekran będzie widoczny, dopóki go nie odepniesz. Aby to zrobić, kliknij i przytrzymaj Przegląd."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Ekran będzie widoczny, dopóki go nie odepniesz. Aby to zrobić, naciśnij i przytrzymaj Ekran główny."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Dane osobowe mogą być dostępne (np. kontakty czy treść e-maili)."</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Dane osobowe (np. kontakty czy treść e-maili) mogą być dostępne."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Przypięta aplikacja może otwierać inne aplikacje."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Aby odpiąć tę aplikację, naciśnij i przytrzymaj przyciski Wstecz oraz Przegląd"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Aby odpiąć tę aplikację, naciśnij i przytrzymaj przyciski Wstecz oraz Ekran główny"</string> @@ -1012,8 +1012,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Przenieś w prawy górny róg"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Przenieś w lewy dolny róg"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Przenieś w prawy dolny róg"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Zamknij dymek"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Nie wyświetlaj rozmowy jako dymku"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Czatuj, korzystając z dymków"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nowe rozmowy będą wyświetlane jako pływające ikony lub dymki. Kliknij, by otworzyć dymek. Przeciągnij, by go przenieść."</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 2259c305022e..9b3976eaffb8 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Recebidas"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Novas"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silenciosas"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificações"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"O app está fixado"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Ela é mantida à vista até que seja liberada. Toque em Voltar e em Visão geral e mantenha essas opções pressionadas para liberar."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Ela é mantida à vista até que seja liberada. Toque em Voltar e em Início e mantenha essas opções pressionadas para liberar."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ela é mantida à vista até que seja liberada. Deslize para cima e a mantenha pressionada para liberar."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ele é mantido à vista até que seja liberado. Deslize para cima e mantenha pressionado para liberar."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ela é mantida à vista até que seja liberada. Toque em Visão geral e mantenha essa opção pressionada para liberar."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Ela é mantida à vista até que seja liberada. Toque em Início e mantenha essa opção pressionada para liberar."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Dados pessoais podem ficar acessíveis (como contatos e conteúdo de e-mail)."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Mover para canto superior direito"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Mover para canto inferior esquerdo"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Mover para canto inferior direito"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Dispensar balão"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Não criar balões de conversa"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Converse usando balões"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Novas conversas aparecerão como ícones flutuantes, ou balões. Toque para abrir o balão. Arraste para movê-lo."</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index d90b8b2eeb13..55b5af121f37 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gerir"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"A receber"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nova"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silencioso"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificações"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"A app está fixada"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Esta opção mantém o item visível até o soltar. Toque sem soltar em Anterior e em Vista geral para soltar."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Esta opção mantém o item visível até o soltar. Toque sem soltar em Anterior e em Página inicial para soltar."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Esta opção mantém o item visível até o soltar. Deslize rapidamente para cima e mantenha o gesto para soltar."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Esta opção mantém o item visível até o soltar. Deslize rapidamente para cima e mantenha pressionado para soltar."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Esta opção mantém o item visível até o soltar. Toque sem soltar em Vista geral para soltar."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Esta opção mantém o item visível até o soltar. Toque sem soltar em Página inicial para soltar."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Os dados pessoais podem ficar acessíveis (tais como contactos e conteúdo do email)."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Mover parte superior direita"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Mover p/ parte infer. esquerda"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Mover parte inferior direita"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Ignorar balão"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Não apresentar a conversa em balões"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Converse no chat através de balões"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"As novas conversas aparecem como ícones flutuantes ou balões. Toque para abrir o balão. Arraste para o mover."</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 2259c305022e..9b3976eaffb8 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Recebidas"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Novas"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silenciosas"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificações"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"O app está fixado"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Ela é mantida à vista até que seja liberada. Toque em Voltar e em Visão geral e mantenha essas opções pressionadas para liberar."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Ela é mantida à vista até que seja liberada. Toque em Voltar e em Início e mantenha essas opções pressionadas para liberar."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ela é mantida à vista até que seja liberada. Deslize para cima e a mantenha pressionada para liberar."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ele é mantido à vista até que seja liberado. Deslize para cima e mantenha pressionado para liberar."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ela é mantida à vista até que seja liberada. Toque em Visão geral e mantenha essa opção pressionada para liberar."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Ela é mantida à vista até que seja liberada. Toque em Início e mantenha essa opção pressionada para liberar."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Dados pessoais podem ficar acessíveis (como contatos e conteúdo de e-mail)."</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Mover para canto superior direito"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Mover para canto inferior esquerdo"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Mover para canto inferior direito"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Dispensar balão"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Não criar balões de conversa"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Converse usando balões"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Novas conversas aparecerão como ícones flutuantes, ou balões. Toque para abrir o balão. Arraste para movê-lo."</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index c90c04c666b1..0f0f598cebf4 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -513,7 +513,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Ștergeți toate notificările"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Gestionați"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Istoric"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Primite"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Noi"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silențioase"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificări"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Conversații"</string> @@ -598,7 +598,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Aplicația este fixată"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunile Înapoi și Recente pentru a anula fixarea."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunile Înapoi și Acasă pentru a anula fixarea."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Astfel rămâne afișat până anulați fixarea. Glisați în sus și țineți apăsat pentru a anula fixarea."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Astfel rămâne afișată până anulați fixarea. Glisați în sus și țineți apăsat pentru a anula fixarea."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunea Recente pentru a anula fixarea."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Astfel rămâne afișat până anulați fixarea. Atingeți lung opțiunea Acasă pentru a anula fixarea."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Pot fi accesate date cu caracter personal (cum ar fi agenda și conținutul e-mailurilor)."</string> @@ -1007,10 +1007,9 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Mutați în dreapta sus"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Mutați în stânga jos"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Mutați în dreapta jos"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Închideți balonul"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Nu afișați conversația în balon"</string> - <string name="bubbles_user_education_title" msgid="5547017089271445797">"Discutați pe chat folosind baloanele"</string> + <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chat cu baloane"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Conversațiile noi apar ca pictograme flotante sau baloane. Atingeți pentru a deschide balonul. Trageți pentru a-l muta."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Controlați oricând baloanele"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Atingeți Gestionați pentru a dezactiva baloanele din această aplicație"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 35f4b745830a..d2bda6e4e259 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -516,7 +516,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Входящие"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Новое"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Без звука"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Уведомления"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Разговоры"</string> @@ -601,7 +601,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Приложение закреплено"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Приложение останется активным, пока вы не отмените блокировку, нажав и удерживая кнопки \"Назад\" и \"Обзор\"."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Приложение останется активным, пока вы не отмените блокировку, нажав и удерживая кнопки \"Назад\" и \"Главный экран\"."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Экран будет зафиксирован, пока вы не отмените блокировку (для этого нужно провести вверх и удерживать)."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Оно будет показываться на экране, пока вы его не открепите (для этого нужно провести вверх и удерживать)."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Приложение останется активным, пока вы не отмените блокировку, нажав и удерживая кнопку \"Обзор\"."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Приложение останется активным, пока вы не отмените блокировку, нажав и удерживая кнопку \"Главный экран\"."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Может быть получен доступ к персональным данным (например, контактам и содержимому электронных писем)."</string> @@ -727,7 +727,7 @@ <string name="notification_priority_title" msgid="2079708866333537093">"Приоритет"</string> <string name="no_shortcut" msgid="8257177117568230126">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" не поддерживает функции разговоров."</string> <string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Нет недавних всплывающих чатов"</string> - <string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Здесь будут появляться недавние и закрытые всплывающие чаты."</string> + <string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Здесь будут появляться недавние и скрытые всплывающие чаты."</string> <string name="notification_unblockable_desc" msgid="2073030886006190804">"Эти уведомления нельзя изменить."</string> <string name="notification_multichannel_desc" msgid="7414593090056236179">"Эту группу уведомлений нельзя настроить здесь."</string> <string name="notification_delegate_header" msgid="1264510071031479920">"Уведомление отправлено через прокси-сервер."</string> @@ -1012,10 +1012,9 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Перенести в правый верхний угол"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Перенести в левый нижний угол"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Перенести в правый нижний угол"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Закрыть всплывающий чат"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Не показывать всплывающие чаты для разговоров"</string> - <string name="bubbles_user_education_title" msgid="5547017089271445797">"Всплывающие чаты для разговоров"</string> + <string name="bubbles_user_education_title" msgid="5547017089271445797">"Всплывающие чаты"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Новые разговоры будут появляться в виде плавающих значков, или всплывающих чатов. Чтобы открыть чат, нажмите на него, а чтобы переместить – перетащите."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Настройки всплывающих чатов"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Чтобы отключить всплывающие чаты от приложения, нажмите \"Настроить\"."</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index be88cbafe59b..b9b95ac45f81 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"කළමනාකරණය කරන්න"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ඉතිහාසය"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"එන"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"නව"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"නිහඬ"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"දැනුම් දීම්"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"සංවාද"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ඉහළ දකුණට ගෙන යන්න"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"පහළ වමට ගෙන යන්න"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"පහළ දකුණට ගෙන යන්න"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"බුබුලු ඉවත ලන්න"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"සංවාදය බුබුලු නොදමන්න"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"බුබුලු භාවිතයෙන් කතාබහ කරන්න"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"නව සංවාද පාවෙන අයිකන හෝ බුබුලු ලෙස දිස් වේ. බුබුල විවෘත කිරීමට තට්ටු කරන්න. එය ගෙන යාමට අදින්න."</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index e0ba13dbfd98..c776f2e7a3f0 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -516,7 +516,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Spravovať"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"História"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Prichádzajúce"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Nové"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Ticho"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Upozornenia"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Konverzácie"</string> @@ -601,10 +601,10 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Aplikácia je pripnutá"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Obsah bude pripnutý v zobrazení, dokým ho neuvoľníte. Uvoľníte ho stlačením a podržaním tlačidiel Späť a Prehľad."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Obsah bude pripnutý v zobrazení, dokým ho neuvoľníte. Uvoľníte ho pridržaním tlačidiel Späť a Domov."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Táto možnosť ponechá položku v zobrazení, dokým ju neodopnete. Odpojíte potiahnutím a pridržaním."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Táto možnosť ponechá položku v zobrazení, dokým ju neodopnete. Odpojíte ju potiahnutím nahor a pridržaním."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Obsah bude pripnutý v zobrazení, dokým ho neuvoľníte. Uvoľníte ho stlačením a podržaním tlačidla Prehľad."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Obsah bude pripnutý v zobrazení, dokým ho neuvoľníte. Uvoľníte ho pridržaním tlačidla Domov."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Môžu byť prístupné osobné údaje (napríklad kontakty a obsah správ)."</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Môže mať prístup k osobným údajom (napríklad kontaktom a obsahu správ)."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Pripnutá aplikácia môže otvoriť iné aplikácie."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Túto aplikáciu odopnete pridržaním tlačidiel Späť a Prehľad"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Túto aplikáciu odopnete pridržaním tlačidiel Späť a Domov"</string> @@ -1012,8 +1012,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Presunúť doprava nahor"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Presunúť doľava nadol"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Presunúť doprava nadol"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Zavrieť bublinu"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Nezobrazovať konverzáciu ako bublinu"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Čet pomocou bublín"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nové konverzácie sa zobrazujú ako plávajúce ikony či bubliny. Bublinu otvoríte klepnutím. Premiestnite ju presunutím."</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 7066e8dc1612..326ae97cdec3 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -516,7 +516,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljanje"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Zgodovina"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Dohodno"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Novo"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Tiho"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Obvestila"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Pogovori"</string> @@ -599,11 +599,11 @@ <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"onemogoči"</string> <string name="accessibility_output_chooser" msgid="7807898688967194183">"Izbira druge izhodne naprave"</string> <string name="screen_pinning_title" msgid="9058007390337841305">"Aplikacija je pripeta"</string> - <string name="screen_pinning_description" msgid="8699395373875667743">"S tem ostane zaslon viden, dokler ga ne odpnete. Če ga želite odpeti, hkrati pridržite gumba za nazaj in pregled."</string> - <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"S tem ostane zaslon viden, dokler ga ne odpnete. Če ga želite odpeti, hkrati pridržite gumba za nazaj in za začetni zaslon."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"S tem ostane zaslon viden, dokler ga ne odpnete. Če ga želite odpeti, povlecite navzgor in pridržite."</string> - <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"S tem ostane zaslon viden, dokler ga ne odpnete. Če ga želite odpeti, pridržite gumb za pregled."</string> - <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"S tem ostane zaslon viden, dokler ga ne odpnete. Če ga želite odpeti, pridržite gumb za začetni zaslon."</string> + <string name="screen_pinning_description" msgid="8699395373875667743">"S tem ostane vidna, dokler je ne odpnete. Če jo želite odpeti, hkrati pridržite gumba za nazaj in pregled."</string> + <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"S tem ostane vidna, dokler je ne odpnete. Če jo želite odpeti, hkrati pridržite gumba za nazaj in za začetni zaslon."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"S tem ostane vidna, dokler je ne odpnete. Če jo želite odpeti, povlecite navzgor in pridržite."</string> + <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"S tem ostane vidna, dokler je ne odpnete. Če jo želite odpeti, pridržite gumb za pregled."</string> + <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"S tem ostane vidna, dokler je ne odpnete. Če jo želite odpeti, pridržite gumb za začetni zaslon."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Dostopni so lahko osebni podatki (na primer stiki in vsebina e-poštnih sporočil)."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Pripeta aplikacija lahko odpre druge aplikacije."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Če želite odpeti to aplikacijo, hkrati pridržite gumba za nazaj in za pregled."</string> @@ -1012,8 +1012,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Premakni zgoraj desno"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Premakni spodaj levo"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Premakni spodaj desno"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Opusti oblaček"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Pogovora ne prikaži v oblačku"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Klepet z oblački"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Novi pogovori so prikazani kot lebdeče ikone ali oblački. Če želite odpreti oblaček, se ga dotaknite. Če ga želite premakniti, ga povlecite."</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index a23a85bf97fa..d0d16c367b2c 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -35,7 +35,7 @@ <string name="battery_low_why" msgid="2056750982959359863">"Cilësimet"</string> <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Të aktivizohet \"Kursyesi i baterisë\"?"</string> <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"Rreth \"Kursyesit të baterisë\""</string> - <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ndiz"</string> + <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivizo"</string> <string name="battery_saver_start_action" msgid="4553256017945469937">"Aktivizo \"Kursyesin e baterisë\""</string> <string name="status_bar_settings_settings_button" msgid="534331565185171556">"Cilësimet"</string> <string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"Wi-Fi"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Menaxho"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historiku"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Hyrëse"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Të reja"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Në heshtje"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Njoftimet"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Bisedat"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Aplikacioni është i gozhduar"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Kjo e ruan në pamje deri sa ta heqësh nga gozhdimi. Prek dhe mbaj të shtypur \"Prapa\" dhe \"Përmbledhje\" për ta hequr nga gozhdimi."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Kjo e ruan në pamje deri sa ta heqësh nga gozhdimi. Prek dhe mbaj të shtypur \"Prapa\" dhe \"Kreu\" për ta hequr nga gozhdimi."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Kjo e ruan në pamje deri sa ta zhgozhdosh. Rrëshqit shpejt lart dhe mbaje të shtypur për ta hequr zhgozhduar."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Kjo e ruan në pamje deri sa ta zhgozhdosh. Rrëshqit shpejt lart dhe mbaje të shtypur për ta zhgozhduar."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Kjo e ruan në pamje deri sa ta heqësh nga gozhdimi. Prek dhe mbaj të shtypur \"Përmbledhje\" për ta hequr nga gozhdimi."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Kjo e ruan në pamje deri sa ta heqësh nga gozhdimi. Prek dhe mbaj të shtypur \"Kreu\" për ta hequr nga gozhdimi."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Të dhënat personale mund të jenë të qasshme (si kontaktet dhe përmbajtja e email-eve)"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Lëviz lart djathtas"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Zhvendos poshtë majtas"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Lëvize poshtë djathtas"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Hiqe flluskën"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Mos e vendos bisedën në flluskë"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Bisedo duke përdorur flluskat"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Bisedat e reja shfaqen si ikona pluskuese ose flluska. Trokit për të hapur flluskën. Zvarrit për ta zhvendosur."</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index fde6413e4b97..f692e1cc20d9 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -513,7 +513,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Управљајте"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Долазно"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Ново"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Нечујно"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Обавештења"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Конверзације"</string> @@ -598,7 +598,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Апликација је закачена"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"На овај начин се ово стално приказује док га не откачите. Додирните и задржите Назад и Преглед да бисте га откачили."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"На овај начин се ово стално приказује док га не откачите. Додирните и задржите Назад и Почетна да бисте га откачили."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"На овај начин се стално приказује док га не откачите. Превуците нагоре и задржите да бисте га откачили."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Стално ће се приказивати док је не откачите. Превуците нагоре и задржите да бисте је откачили."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"На овај начин се ово стално приказује док га не откачите. Додирните и задржите Преглед да бисте га откачили."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"На овај начин се ово стално приказује док га не откачите. Додирните и задржите Почетна да бисте га откачили."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Могу да буду доступни лични подаци (као што су контакти и садржај имејлова)."</string> @@ -1007,8 +1007,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Премести горе десно"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Премести доле лево"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Премести доле десно"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Одбацивање облачића"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Не користи облачиће за конверзацију"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Ћаскајте у облачићима"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Нове конверзације се приказују као плутајуће иконе или облачићи. Додирните да бисте отворили облачић. Превуците да бисте га преместили."</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index af3839bc8e8b..f7d19ac3472d 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Hantera"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Inkommande"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Ny"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Ljudlöst"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Aviseringar"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Konversationer"</string> @@ -599,7 +599,7 @@ <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Skärmen visas tills du lossar den. Tryck länge på Översikt om du vill lossa skärmen."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Skärmen visas tills du lossar den. Tryck länge på Startsida om du vill lossa skärmen."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Personliga uppgifter kan bli tillgängliga (t.ex. kontakter och innehåll i e-post)."</string> - <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Det kan gå att öppna andra appar med appen du har fäst."</string> + <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Den fästa appen kan öppna andra appar."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Om du vill lossa appen trycker du länge på knapparna Tillbaka och Översikt"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Om du vill lossa appen trycker du länge på knapparna Tillbaka och Startsida"</string> <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"Svep uppåt och håll kvar fingret om du vill lossa appen"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Flytta högst upp till höger"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Flytta längst ned till vänster"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Flytta längst ned till höger"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Stäng bubbla"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Visa inte konversationen i bubblor"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Chatta med bubblor"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Nya konversationer visas som flytande ikoner, så kallade bubblor. Tryck på bubblan om du vill öppna den. Dra den om du vill flytta den."</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index f5ec81bf8fec..1a0b9ec653ae 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -92,7 +92,7 @@ <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Inachakata rekodi ya skrini"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"Arifa inayoendelea ya kipindi cha kurekodi skrini"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"Ungependa kuanza kurekodi?"</string> - <string name="screenrecord_description" msgid="1123231719680353736">"Wakati wa kurekodi, Mfumo wa Android unaweza kusana maelezo yoyote nyeti yanayoonekana kwenye skrini au yanayochezwa kwenye kifaa chako. Hii ni pamoja na manenosiri, maelezo ya malipo, picha, ujumbe na sauti."</string> + <string name="screenrecord_description" msgid="1123231719680353736">"Wakati wa kurekodi, Mfumo wa Android unaweza kunasa maelezo yoyote nyeti yanayoonekana kwenye skrini au yanayochezwa kwenye kifaa chako. Hii ni pamoja na manenosiri, maelezo ya malipo, picha, ujumbe na sauti."</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"Rekodi sauti"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Sauti ya kifaa"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sauti kutoka kwenye kifaa chako, kama vile muziki, simu na milio ya simu"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Dhibiti"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Simu inayoingia"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Mpya"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Kimya"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Arifa"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Mazungumzo"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Sogeza juu kulia"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Sogeza chini kushoto"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Sogeza chini kulia"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Ondoa kiputo"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Usiweke viputo kwenye mazungumzo"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Piga gumzo ukitumia viputo"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Mazungumzo mapya huonekena kama aikoni au viputo vinavyoelea. Gusa ili ufungue kiputo. Buruta ili ukisogeze."</string> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index c4c467152281..3b00ad1bf0c4 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -15,8 +15,5 @@ ~ limitations under the License --> <resources> - <!-- Whether QuickSettings is in a phone landscape --> - <bool name="quick_settings_wide">false</bool> - <integer name="quick_settings_num_columns">3</integer> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml new file mode 100644 index 000000000000..40838f362f5c --- /dev/null +++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<resources> + <!-- Size of the panel of large phones on portrait. This shouldn't fill, but have some padding on the side --> + <dimen name="notification_panel_width">416dp</dimen> + +</resources> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 8f73d231c732..fdf4e3b1b796 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -16,8 +16,6 @@ */ --> <resources> - <!-- Standard notification width + gravity --> - <dimen name="notification_panel_width">416dp</dimen> <!-- Diameter of outer shape drawable shown in navbar search--> <dimen name="navbar_search_outerring_diameter">430dip</dimen> diff --git a/packages/SystemUI/res/values-sw900dp-land/dimen.xml b/packages/SystemUI/res/values-sw900dp-land/dimen.xml deleted file mode 100644 index 1e0600ed5fe0..000000000000 --- a/packages/SystemUI/res/values-sw900dp-land/dimen.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (c) 2012, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ ---> -<resources> - <!-- Standard notification width + gravity for tablet large screen device --> - <dimen name="notification_panel_width">544dp</dimen> - -</resources> - diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 4a32ecf8d229..29a186f481cc 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -146,7 +146,7 @@ <string name="biometric_dialog_face_icon_description_authenticating" msgid="3401633342366146535">"உங்கள் முகத்தை அங்கீகரிக்கிறது"</string> <string name="biometric_dialog_face_icon_description_authenticated" msgid="2242167416140740920">"முகம் அங்கீகரிக்கப்பட்டது"</string> <string name="biometric_dialog_face_icon_description_confirmed" msgid="7918067993953940778">"உறுதிப்படுத்தப்பட்டது"</string> - <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"முடிக்க \'உறுதிப்படுத்து\' என்பதை தட்டவும்"</string> + <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"முடிக்க \'உறுதிப்படுத்துக\' என்பதை தட்டவும்"</string> <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"அங்கீகரிக்கப்பட்டது"</string> <string name="biometric_dialog_use_pin" msgid="8385294115283000709">"பின்னைப் பயன்படுத்து"</string> <string name="biometric_dialog_use_pattern" msgid="2315593393167211194">"பேட்டர்னைப் பயன்படுத்து"</string> @@ -168,7 +168,7 @@ <string name="biometric_dialog_last_password_attempt_before_wipe_profile" msgid="8538032972389729253">"அடுத்த முறை தவறான கடவுச்சொல்லை உள்ளிட்டால் உங்கள் பணிக் கணக்கும் அதன் தரவும் நீக்கப்படும்."</string> <string name="biometric_dialog_failed_attempts_now_wiping_device" msgid="6585503524026243042">"பலமுறை தவறாக முயன்ற காரணத்தால் இந்தச் சாதனத்தின் தரவு நீக்கப்படும்."</string> <string name="biometric_dialog_failed_attempts_now_wiping_user" msgid="7015008539146949115">"பலமுறை தவறாக முயன்ற காரணத்தால் இந்தப் பயனர் நீக்கப்படுவார்."</string> - <string name="biometric_dialog_failed_attempts_now_wiping_profile" msgid="5239378521440749682">"பலமுறை தவறாக முயன்ற காரணத்தால் இந்தப் பணிக் கணக்கும் அதன் தரவும் நீக்கப்படும்."</string> + <string name="biometric_dialog_failed_attempts_now_wiping_profile" msgid="5239378521440749682">"பலமுறை தவறாக முயன்றதால், இந்தப் பணிக் கணக்கும் அதன் தரவும் நீக்கப்படும்"</string> <string name="biometric_dialog_now_wiping_dialog_dismiss" msgid="7189432882125106154">"நிராகரி"</string> <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"கைரேகை சென்சாரைத் தொடவும்"</string> <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="4465698996175640549">"கைரேகை ஐகான்"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"அறிவிப்புகளை நிர்வகி"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"வரலாறு"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"உள்வருவது"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"புதிது"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"நிசப்தம்"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"அறிவிப்புகள்"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"உரையாடல்கள்"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"மேலே வலப்புறமாக நகர்த்து"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"கீழே இடப்புறமாக நகர்த்து"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"கீழே வலதுபுறமாக நகர்த்து"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"குமிழை அகற்று"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"உரையாடலைக் குமிழாக்காதே"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"குமிழ்களைப் பயன்படுத்தி அரட்டையடியுங்கள்"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"புதிய உரையாடல்கள் மிதக்கும் ஐகான்களாகவோ குமிழ்களாகவோ தோன்றும். குமிழைத் திறக்க தட்டவும். நகர்த்த இழுக்கவும்."</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 73754a20cccc..4b56e157b34f 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"నిర్వహించండి"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"చరిత్ర"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"ఇన్కమింగ్"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"కొత్తవి"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"నిశ్శబ్దం"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"నోటిఫికేషన్లు"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"సంభాషణలు"</string> @@ -721,7 +721,7 @@ <string name="notification_priority_title" msgid="2079708866333537093">"ప్రాధాన్యత"</string> <string name="no_shortcut" msgid="8257177117568230126">"<xliff:g id="APP_NAME">%1$s</xliff:g> సంభాషణ ఫీచర్లను సపోర్ట్ చేయదు"</string> <string name="bubble_overflow_empty_title" msgid="3120029421991510842">"ఇటీవలి బబుల్స్ ఏవీ లేవు"</string> - <string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"ఇటీవలి బబుల్స్, తీసివేసిన బబుల్స్ ఇక్కడ కనిపిస్తాయి"</string> + <string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"ఇటీవలి బబుల్స్ మరియు తీసివేసిన బబుల్స్ ఇక్కడ కనిపిస్తాయి"</string> <string name="notification_unblockable_desc" msgid="2073030886006190804">"ఈ నోటిఫికేషన్లను సవరించడం వీలుపడదు."</string> <string name="notification_multichannel_desc" msgid="7414593090056236179">"ఈ నోటిఫికేషన్ల సమూహాన్ని ఇక్కడ కాన్ఫిగర్ చేయలేము"</string> <string name="notification_delegate_header" msgid="1264510071031479920">"ప్రాక్సీ చేయబడిన నోటిఫికేషన్"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ఎగువ కుడివైపునకు జరుపు"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"దిగువ ఎడమవైపునకు తరలించు"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"దిగవు కుడివైపునకు జరుపు"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"బబుల్ను విస్మరించు"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"సంభాషణను బబుల్ చేయవద్దు"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"బబుల్స్ను ఉపయోగించి చాట్ చేయండి"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"కొత్త సంభాషణలు తేలియాడే చిహ్నాలుగా లేదా బబుల్స్ లాగా కనిపిస్తాయి. బబుల్ని తెరవడానికి నొక్కండి. తరలించడానికి లాగండి."</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index d61533d0be01..ba16febe18c3 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -92,7 +92,7 @@ <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"กำลังประมวลผลการอัดหน้าจอ"</string> <string name="screenrecord_channel_description" msgid="4147077128486138351">"การแจ้งเตือนต่อเนื่องสำหรับเซสชันการบันทึกหน้าจอ"</string> <string name="screenrecord_start_label" msgid="1750350278888217473">"เริ่มบันทึกเลยไหม"</string> - <string name="screenrecord_description" msgid="1123231719680353736">"ขณะบันทึก ระบบ Android จะบันทึกข้อมูลที่ละเอียดอ่อนที่ปรากฏบนหน้าจอหรือเล่นในอุปกรณ์ได้ ซึ่งรวมถึงรหัสผ่าน ข้อมูลการชำระเงิน รูปภาพ ข้อความ และเสียง"</string> + <string name="screenrecord_description" msgid="1123231719680353736">"ขณะบันทึก ระบบ Android อาจบันทึกข้อมูลที่ละเอียดอ่อนที่ปรากฏบนหน้าจอหรือเล่นในอุปกรณ์ได้ ซึ่งรวมถึงรหัสผ่าน ข้อมูลการชำระเงิน รูปภาพ ข้อความ และเสียง"</string> <string name="screenrecord_audio_label" msgid="6183558856175159629">"บันทึกเสียง"</string> <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"เสียงจากอุปกรณ์"</string> <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"เสียงจากอุปกรณ์ เช่น เพลง การโทร และเสียงเรียกเข้า"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"จัดการ"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"ประวัติ"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"เข้ามาใหม่"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"ใหม่"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"เงียบ"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"การแจ้งเตือน"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"การสนทนา"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"ตรึงแอปอยู่"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"การดำเนินการนี้จะแสดงหน้าจอนี้ไว้เสมอจนกว่าคุณจะเลิกตรึง แตะ \"กลับ\" และ \"ภาพรวม\" ค้างไว้เพื่อเลิกตรึง"</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"การดำเนินการนี้จะแสดงหน้าจอนี้ไว้เสมอจนกว่าคุณจะเลิกตรึง แตะ \"กลับ\" และ \"หน้าแรก\" ค้างไว้เพื่อเลิกตรึง"</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"วิธีนี้ช่วยให้เห็นหน้าจอตลอดจนกว่าจะเลิกตรึง เลื่อนขึ้นค้างไว้เพื่อเลิกตรึง"</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"วิธีนี้ช่วยให้เห็นแอปบนหน้าจอตลอดจนกว่าจะเลิกตรึง เลื่อนขึ้นค้างไว้เพื่อเลิกตรึง"</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"การดำเนินการนี้จะแสดงหน้าจอนี้ไว้เสมอจนกว่าคุณจะเลิกตรึง แตะ \"ภาพรวม\" ค้างไว้เพื่อเลิกตรึง"</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"การดำเนินการนี้จะแสดงหน้าจอนี้ไว้เสมอจนกว่าคุณจะเลิกตรึง แตะ \"หน้าแรก\" ค้างไว้เพื่อเลิกตรึง"</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"อาจมีการเข้าถึงข้อมูลส่วนตัว (เช่น รายชื่อติดต่อและเนื้อหาในอีเมล)"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"ย้ายไปด้านขวาบน"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"ย้ายไปด้านซ้ายล่าง"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"ย้ายไปด้านขาวล่าง"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"ปิดบับเบิล"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"ไม่ต้องแสดงการสนทนาเป็นบับเบิล"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"แชทโดยใช้บับเบิล"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"การสนทนาใหม่ๆ จะปรากฏเป็นไอคอนแบบลอยหรือบับเบิล แตะเพื่อเปิดบับเบิล ลากเพื่อย้ายที่"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 041214f2111e..e25e52bdf4ab 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -122,7 +122,7 @@ <string name="accessibility_back" msgid="6530104400086152611">"Bumalik"</string> <string name="accessibility_home" msgid="5430449841237966217">"Home"</string> <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string> - <string name="accessibility_accessibility_button" msgid="4089042473497107709">"Pagiging Accessible"</string> + <string name="accessibility_accessibility_button" msgid="4089042473497107709">"Accessibility"</string> <string name="accessibility_rotate_button" msgid="1238584767612362586">"I-rotate ang screen"</string> <string name="accessibility_recent" msgid="901641734769533575">"Overview"</string> <string name="accessibility_search_light" msgid="524741790416076988">"Hanapin"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Pamahalaan"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Papasok"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Bago"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Naka-silent"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Mga Notification"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Mga Pag-uusap"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"Naka-pin ang app"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"Pinapanatili nitong nakikita ito hanggang sa mag-unpin ka. Pindutin nang matagal ang Bumalik at Overview upang mag-unpin."</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Pinapanatili nitong nakikita ito hanggang sa mag-unpin ka. Pindutin nang matagal ang Bumalik at Home upang mag-unpin."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Pinapanatili nitong nakikita ito hanggang sa mag-unpin ka. Mag-swipe pataas at i-hold para i-unpin."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Mananatiling nakikita ang app hanggang sa mag-unpin ka. Mag-swipe pataas at i-hold para i-unpin."</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Pinapanatili nitong nakikita ito hanggang sa mag-unpin ka. Pindutin nang matagal ang Overview upang mag-unpin."</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Pinapanatili nitong nakikita ito hanggang sa mag-unpin ka. Pindutin nang matagal ang Home upang mag-unpin."</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Puwedeng ma-access ang personal na data (tulad ng mga contact at content ng email)."</string> @@ -618,7 +618,7 @@ <string name="stream_notification" msgid="7930294049046243939">"Notification"</string> <string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetooth"</string> <string name="stream_dtmf" msgid="7322536356554673067">"Dual multi tone frequency"</string> - <string name="stream_accessibility" msgid="3873610336741987152">"Pagiging Accessible"</string> + <string name="stream_accessibility" msgid="3873610336741987152">"Accessibility"</string> <string name="ring_toggle_title" msgid="5973120187287633224">"Mga Tawag"</string> <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ipa-ring"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"I-vibrate"</string> @@ -1002,10 +1002,9 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Ilipat sa kanan sa itaas"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Ilipat sa kaliwa sa ibaba"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Ilipat sa kanan sa ibaba"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"I-dismiss ang bubble"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Huwag ipakita sa bubble ang mga pag-uusap"</string> - <string name="bubbles_user_education_title" msgid="5547017089271445797">"Makipag-chat gamit ang mga bubble"</string> + <string name="bubbles_user_education_title" msgid="5547017089271445797">"Mag-chat gamit ang bubbles"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Lumalabas bilang mga nakalutang na icon o bubble ang mga bagong pag-uusap. I-tap para buksan ang bubble. I-drag para ilipat ito."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Kontrolin ang mga bubble anumang oras"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"I-tap ang Pamahalaan para i-off ang mga bubble mula sa app na ito"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index e4892cf934a3..6c6b5bf47b3b 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -139,7 +139,7 @@ <string name="voice_assist_label" msgid="3725967093735929020">"sesli yardımı aç"</string> <string name="camera_label" msgid="8253821920931143699">"kamerayı aç"</string> <string name="cancel" msgid="1089011503403416730">"İptal"</string> - <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Onaylayın"</string> + <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Onayla"</string> <string name="biometric_dialog_try_again" msgid="8575345628117768844">"Tekrar dene"</string> <string name="biometric_dialog_empty_space_description" msgid="3330555462071453396">"Kimlik doğrulama işlemini iptal etmek için dokunun"</string> <string name="biometric_dialog_face_icon_description_idle" msgid="4351777022315116816">"Lütfen tekrar deneyin"</string> @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Yönet"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Geçmiş"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Gelen"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Yeni"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Sessiz"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Bildirimler"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Görüşmeler"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Sağ üste taşı"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Sol alta taşı"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Sağ alta taşı"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Baloncuğu kapat"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Görüşmeyi baloncuk olarak görüntüleme"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Baloncukları kullanarak sohbet edin"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Yeni görüşmeler kayan simgeler veya baloncuk olarak görünür. Açmak için baloncuğa dokunun. Baloncuğu taşımak için sürükleyin."</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 4c39f43a8845..e192c120922f 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -516,7 +516,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Керувати"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Історія"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Нові"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Нові"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Без звуку"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Сповіщення"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Розмови"</string> @@ -609,7 +609,7 @@ <string name="screen_pinning_toast" msgid="8177286912533744328">"Щоб відкріпити цей додаток, натисніть і утримуйте кнопки \"Назад\" та \"Огляд\""</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Щоб відкріпити цей додаток, натисніть і утримуйте кнопки \"Назад\" та \"Головний екран\""</string> <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"Щоб відкріпити цей додаток, проведіть пальцем вгору й утримуйте його на екрані"</string> - <string name="screen_pinning_positive" msgid="3285785989665266984">"Зрозуміло"</string> + <string name="screen_pinning_positive" msgid="3285785989665266984">"OK"</string> <string name="screen_pinning_negative" msgid="6882816864569211666">"Ні, дякую"</string> <string name="screen_pinning_start" msgid="7483998671383371313">"Додаток закріплено"</string> <string name="screen_pinning_exit" msgid="4553787518387346893">"Додаток відкріплено"</string> @@ -673,7 +673,7 @@ <string name="tuner_warning_title" msgid="7721976098452135267">"Це цікаво, але будьте обачні"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner пропонує нові способи налаштувати та персоналізувати інтерфейс користувача Android. Ці експериментальні функції можуть змінюватися, не працювати чи зникати в майбутніх версіях. Будьте обачні."</string> <string name="tuner_persistent_warning" msgid="230466285569307806">"Ці експериментальні функції можуть змінюватися, не працювати чи зникати в майбутніх версіях. Будьте обачні."</string> - <string name="got_it" msgid="477119182261892069">"Зрозуміло"</string> + <string name="got_it" msgid="477119182261892069">"OK"</string> <string name="tuner_toast" msgid="3812684836514766951">"Вітаємо! System UI Tuner установлено в додатку Налаштування"</string> <string name="remove_from_settings" msgid="633775561782209994">"Видалити з додатка Налаштування"</string> <string name="remove_from_settings_prompt" msgid="551565437265615426">"Видалити інструмент System UI Tuner із додатка Налаштування та припинити користуватися всіма його функціями?"</string> @@ -1012,14 +1012,13 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Перемістити праворуч угору"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Перемістити ліворуч униз"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Перемістити праворуч униз"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Закрити підказку"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Не показувати спливаючі чати для розмов"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Спливаючий чат"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Нові повідомлення чату з\'являються у вигляді спливаючих значків. Щоб відкрити чат, натисніть його, а щоб перемістити – перетягніть."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Налаштовуйте спливаючі чати будь-коли"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Натисніть \"Налаштувати\", щоб вимкнути спливаючі чати від цього додатка"</string> - <string name="bubbles_user_education_got_it" msgid="8282812431953161143">"Зрозуміло"</string> + <string name="bubbles_user_education_got_it" msgid="8282812431953161143">"OK"</string> <string name="bubbles_app_settings" msgid="5779443644062348657">"Налаштування параметра \"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\""</string> <string name="notification_content_system_nav_changed" msgid="5077913144844684544">"Навігацію в системі оновлено. Щоб внести зміни, перейдіть у налаштування."</string> <string name="notification_content_gesture_nav_available" msgid="4431460803004659888">"Перейдіть у налаштування, щоб оновити навігацію в системі"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index feb894a56eb0..d382f4237c47 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"نظم کریں"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"سرگزشت"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"اِن کمنگ"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"نیا"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"خاموش"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"اطلاعات"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"گفتگوئیں"</string> @@ -595,7 +595,7 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"ایپ کو پن کر دیا گیا ہے"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"یہ اسے اس وقت تک نظر میں رکھتا ہے جب تک آپ اس سے پن ہٹا نہیں دیتے۔ پن ہٹانے کیلئے پیچھے اور مجموعی جائزہ بٹنز کو ٹچ کریں اور دبائے رکھیں۔"</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"یہ اس کو اس وقت تک مد نظر رکھتا ہے جب تک آپ اس سے پن نہیں ہٹا دیتے۔ پن ہٹانے کیلئے \"پیچھے\" اور \"ہوم\" بٹنز کو ٹچ کریں اور دبائے رکھیں۔"</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"یہ اس کو اس وقت تک مد نظر رکھتا ہے جب تک آپ اس سے پن نہیں ہٹا دیتے۔ پن ہٹانے کے لیے سوائپ کریں اور پکڑ کر رکھیں۔"</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"یہ اس کو اس وقت تک مد نظر رکھتا ہے جب تک آپ اس سے پن نہیں ہٹا دیتے۔ پن ہٹانے کے لیے اوپر سوائپ کریں اور پکڑ کر رکھیں۔"</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"یہ اسے اس وقت تک نظر میں رکھتا ہے جب تک آپ اس سے پن ہٹا نہیں دیتے۔ پن ہٹانے کیلئے مجموعی جائزہ بٹن کو ٹچ کریں اور دبائے رکھیں۔"</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"یہ اس کو اس وقت تک مد نظر رکھتا ہے جب تک آپ اس سے پن نہیں ہٹا دیتے۔ پن ہٹانے کیلئے \"ہوم\" بٹن کو ٹچ کریں اور دبائے رکھیں۔"</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"ذاتی ڈیٹا قابل رسائی ہو سکتا ہے (جیسے رابطے اور ای میل کا مواد)۔"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"اوپر دائیں جانب لے جائيں"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"نیچے بائیں جانب لے جائیں"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"نیچے دائیں جانب لے جائیں"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"بلبلہ برخاست کریں"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"بلبلہ گفتگو نہ کریں"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"بلبلے کے ذریعے چیٹ کریں"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"نئی گفتگوئیں فلوٹنگ آئیکن یا بلبلے کے طور پر ظاہر ہوں گی۔ بلبلہ کھولنے کے لیے تھپتھپائیں۔ اسے منتقل کرنے کے لیے گھسیٹیں۔"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 1145fa7b92df..3099270c0d80 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Boshqarish"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Tarix"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Kiruvchi"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Yangi"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Sokin"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Bildirishnomalar"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Suhbatlar"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Yuqori oʻngga surish"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Quyi chapga surish"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Quyi oʻngga surish"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Pufakni yopish"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Suhbatlar bulutchalar shaklida chiqmasin"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Bulutchalar yordamida subhatlashish"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Yangi xabarlar qalqib chiquvchi belgilar yoki bulutchalar kabi chiqadi. Xabarni ochish uchun bildirishnoma ustiga bosing. Xabarni qayta joylash uchun bildirishnomani suring."</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 8cd238c8529a..cc6a8fd25826 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Quản lý"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Lịch sử"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Hiển thị gần đây"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Mới"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Im lặng"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Thông báo"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Cuộc trò chuyện"</string> @@ -593,12 +593,12 @@ <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"tắt"</string> <string name="accessibility_output_chooser" msgid="7807898688967194183">"Chuyển đổi thiết bị đầu ra"</string> <string name="screen_pinning_title" msgid="9058007390337841305">"Đã ghim ứng dụng"</string> - <string name="screen_pinning_description" msgid="8699395373875667743">"Thao tác này sẽ duy trì hiển thị màn hình cho đến khi bạn bỏ ghim. Hãy chạm và giữ Quay lại và Tổng quan để bỏ ghim."</string> - <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Thao tác này sẽ duy trì hiển thị màn hình cho đến khi bạn bỏ ghim. Hãy chạm và giữ nút Quay lại và nút Màn hình chính để bỏ ghim."</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Màn hình tiếp tục hiển thị cho tới khi bạn bỏ ghim. Hãy vuốt lên và giữ để bỏ ghim."</string> - <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Thao tác này sẽ duy trì hiển thị màn hình cho đến khi bạn bỏ ghim. Hãy chạm và giữ Tổng quan để bỏ ghim."</string> - <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Thao tác này sẽ duy trì hiển thị màn hình cho đến khi bạn bỏ ghim. Hãy chạm và giữ nút Màn hình chính để bỏ ghim."</string> - <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Dữ liệu cá nhân có thể bị truy cập (chẳng hạn như danh bạ và nội dung email)."</string> + <string name="screen_pinning_description" msgid="8699395373875667743">"Ứng dụng này sẽ ở cố định trên màn hình cho đến khi bạn bỏ ghim. Hãy chạm và giữ Quay lại và Tổng quan để bỏ ghim."</string> + <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Ứng dụng này sẽ ở cố định trên màn hình cho đến khi bạn bỏ ghim. Hãy chạm và giữ nút Quay lại và nút Màn hình chính để bỏ ghim."</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ứng dụng này sẽ ở cố định trên màn hình cho đến khi bạn bỏ ghim. Hãy vuốt lên và giữ để bỏ ghim."</string> + <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ứng dụng này sẽ ở cố định trên màn hình cho đến khi bạn bỏ ghim. Hãy chạm và giữ Tổng quan để bỏ ghim."</string> + <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Ứng dụng này sẽ ở cố định trên màn hình cho đến khi bạn bỏ ghim. Hãy chạm và giữ nút Màn hình chính để bỏ ghim."</string> + <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Ứng dụng này có thể truy cập dữ liệu cá nhân (chẳng hạn như danh bạ và nội dung email)."</string> <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Ứng dụng đã ghim có thể mở các ứng dụng khác."</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"Để bỏ ghim ứng dụng này, hãy chạm và giữ nút Quay lại và nút Tổng quan"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Để bỏ ghim ứng dụng này, hãy chạm và giữ nút Quay lại và nút Màn hình chính"</string> @@ -1002,11 +1002,10 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Chuyển lên trên cùng bên phải"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Chuyển tới dưới cùng bên trái"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Chuyển tới dưới cùng bên phải"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Đóng bong bóng"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Dừng trò chuyện bằng bong bóng"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Trò chuyện bằng bong bóng trò chuyện"</string> - <string name="bubbles_user_education_description" msgid="1160281719576715211">"Các cuộc trò chuyện mới hiển thị dưới dạng biểu tượng nổi hoặc bong bóng trò chuyện. Nhấn để mở bong bóng trò chuyện. Kéo để di chuyển bong bóng trò chuyện."</string> + <string name="bubbles_user_education_description" msgid="1160281719576715211">"Các cuộc trò chuyện mới sẽ xuất hiện dưới dạng biểu tượng nổi hoặc bong bóng trò chuyện. Nhấn để mở bong bóng trò chuyện. Kéo để di chuyển bong bóng trò chuyện."</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"Kiểm soát tùy chọn cài đặt bong bóng trò chuyện bất mọi lúc"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"Nhấn vào nút Quản lý để tắt bong bóng trò chuyện từ ứng dụng này"</string> <string name="bubbles_user_education_got_it" msgid="8282812431953161143">"OK"</string> diff --git a/packages/SystemUI/res/values-w550dp-land/config.xml b/packages/SystemUI/res/values-w550dp-land/config.xml index 16d5317636a2..a33f1312521f 100644 --- a/packages/SystemUI/res/values-w550dp-land/config.xml +++ b/packages/SystemUI/res/values-w550dp-land/config.xml @@ -20,9 +20,5 @@ <!-- These resources are around just to allow their values to be customized for different hardware and product builds. --> <resources> - - <!-- Whether QuickSettings is in a phone landscape --> - <bool name="quick_settings_wide">true</bool> - - <integer name="quick_settings_num_columns">4</integer> + <integer name="quick_settings_num_columns">6</integer> </resources> diff --git a/packages/SystemUI/res/values-w550dp-land/dimens.xml b/packages/SystemUI/res/values-w550dp-land/dimens.xml deleted file mode 100644 index 017ca6987820..000000000000 --- a/packages/SystemUI/res/values-w550dp-land/dimens.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (c) 2016, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ ---> -<resources> - <!-- Standard notification width + gravity --> - <dimen name="notification_panel_width">544dp</dimen> - -</resources> diff --git a/packages/SystemUI/res/values-w650dp-land/dimens.xml b/packages/SystemUI/res/values-w650dp-land/dimens.xml new file mode 100644 index 000000000000..108d6cf16fec --- /dev/null +++ b/packages/SystemUI/res/values-w650dp-land/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<resources> + <!-- Standard notification width + gravity --> + <dimen name="notification_panel_width">644dp</dimen> + +</resources> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 86f43c9d55f0..73fac041f446 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"历史记录"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"收到的通知"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"新"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"静音"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"通知"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"对话"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"移至右上角"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"移至左下角"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"移至右下角"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"关闭对话泡"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"不以对话泡形式显示对话"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"使用对话泡聊天"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"新对话会以浮动图标或对话泡形式显示。点按即可打开对话泡。拖动即可移动对话泡。"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 6d9ee9913981..5f5c85fcc8ef 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"記錄"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"收到的通知"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"新"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"靜音"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"通知"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"對話"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"移去右上角"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"移去左下角"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"移去右下角"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"關閉對話氣泡"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"不要透過小視窗顯示對話"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"使用小視窗進行即時通訊"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"新對話會以浮動圖示 (小視窗) 顯示。輕按即可開啟小視窗。拖曳即可移動小視窗。"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 11e2be069a2f..9847871c2efc 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"記錄"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"收到的通知"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"最新"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"靜音"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"通知"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"對話"</string> @@ -595,11 +595,11 @@ <string name="screen_pinning_title" msgid="9058007390337841305">"應用程式已固定"</string> <string name="screen_pinning_description" msgid="8699395373875667743">"這會讓目前的螢幕畫面保持顯示狀態,直到取消固定為止。按住 [返回] 按鈕和 [總覽] 按鈕即可取消固定。"</string> <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"這會讓應用程式顯示在螢幕上,直到取消固定為止。按住 [返回] 按鈕和主螢幕按鈕即可取消固定。"</string> - <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"這會讓目前的螢幕畫面保持顯示狀態,直到取消固定為止。向上滑動並按住即可取消固定。"</string> + <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"這會讓目前的螢幕畫面保持顯示,直到取消固定為止。向上滑動並按住即可取消固定。"</string> <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"這會讓目前的螢幕畫面保持顯示狀態,直到取消固定為止。按住 [總覽] 按鈕即可取消固定。"</string> <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"這會讓應用程式顯示在螢幕上,直到取消固定為止。按住主螢幕按鈕即可取消固定。"</string> <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"個人資料 (例如聯絡人和電子郵件內容) 可能會遭存取。"</string> - <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"已設為固定的應用程式或許仍可開啟其他應用程式。"</string> + <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"已設為固定的應用程式仍可開啟其他應用程式。"</string> <string name="screen_pinning_toast" msgid="8177286912533744328">"如要取消固定這個應用程式,請按住「返回」按鈕和「總覽」按鈕"</string> <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"如要取消固定這個應用程式,請按住「返回」按鈕和主畫面按鈕"</string> <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"如要取消固定這個應用程式,請向上滑動並按住"</string> @@ -1002,10 +1002,9 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"移至右上方"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"移至左下方"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"移至右下方"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"關閉對話框"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"不要以對話框形式顯示對話"</string> - <string name="bubbles_user_education_title" msgid="5547017089271445797">"使用對話框進行即時通訊"</string> + <string name="bubbles_user_education_title" msgid="5547017089271445797">"透過對話框來聊天"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"新的對話會以浮動圖示或對話框形式顯示。輕觸即可開啟對話框,拖曳則可移動對話框。"</string> <string name="bubbles_user_education_manage_title" msgid="2848511858160342320">"你隨時可以控管對話框的各項設定"</string> <string name="bubbles_user_education_manage" msgid="1391639189507036423">"輕觸 [管理] 即可關閉來自這個應用程式的對話框"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 358792161575..9b76660e6cd3 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -510,7 +510,7 @@ <string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string> <string name="manage_notifications_text" msgid="6885645344647733116">"Phatha"</string> <string name="manage_notifications_history_text" msgid="57055985396576230">"Umlando"</string> - <string name="notification_section_header_incoming" msgid="5295312809341711367">"Okungenayo"</string> + <string name="notification_section_header_incoming" msgid="850925217908095197">"Okusha"</string> <string name="notification_section_header_gentle" msgid="6804099527336337197">"Kuthulile"</string> <string name="notification_section_header_alerting" msgid="5581175033680477651">"Izaziso"</string> <string name="notification_section_header_conversations" msgid="821834744538345661">"Izingxoxo"</string> @@ -1002,8 +1002,7 @@ <string name="bubble_accessibility_action_move_top_right" msgid="6916868852433483569">"Hambisa phezulu ngakwesokudla"</string> <string name="bubble_accessibility_action_move_bottom_left" msgid="6339015902495504715">"Hambisa inkinobho ngakwesokunxele"</string> <string name="bubble_accessibility_action_move_bottom_right" msgid="7471571700628346212">"Hambisa inkinobho ngakwesokudla"</string> - <!-- no translation found for bubble_dismiss_text (1314082410868930066) --> - <skip /> + <string name="bubble_dismiss_text" msgid="1314082410868930066">"Cashisa ibhamuza"</string> <string name="bubbles_dont_bubble_conversation" msgid="1033040343437428822">"Ungayibhamuzi ingxoxo"</string> <string name="bubbles_user_education_title" msgid="5547017089271445797">"Xoxa usebenzisa amabhamuza"</string> <string name="bubbles_user_education_description" msgid="1160281719576715211">"Izingxoxo ezintsha zivela njengezithonjana ezintantayo, noma amabhamuza. Thepha ukuze uvule ibhamuza. Hudula ukuze ulihambise."</string> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 4fc904b21741..40a4b5074413 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -203,8 +203,8 @@ <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black --> <!-- Bubbles --> - <color name="bubbles_pointer_light">#FFFFFF</color> - <color name="bubbles_pointer_dark">@color/GM2_grey_800</color> + <color name="bubbles_light">#FFFFFF</color> + <color name="bubbles_dark">@color/GM2_grey_800</color> <!-- GM2 colors --> <color name="GM2_grey_50">#F8F9FA</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 00537ff0466d..848cdb1e831c 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -95,9 +95,6 @@ <!-- The maximum number of tiles in the QuickQSPanel --> <integer name="quick_qs_panel_max_columns">6</integer> - <!-- Whether QuickSettings is in a phone landscape --> - <bool name="quick_settings_wide">false</bool> - <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index abc560bed595..9bcfdc43fbbf 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -631,10 +631,10 @@ <dimen name="notification_section_divider_height">@dimen/notification_side_paddings</dimen> <!-- Size of the face pile shown on one-line (children of a group) conversation notifications --> - <dimen name="conversation_single_line_face_pile_size">36dp</dimen> + <dimen name="conversation_single_line_face_pile_size">25dp</dimen> <!-- Size of an avatar shown on one-line (children of a group) conversation notifications --> - <dimen name="conversation_single_line_avatar_size">24dp</dimen> + <dimen name="conversation_single_line_avatar_size">20dp</dimen> <!-- Border width for avatars in the face pile shown on one-line (children of a group) conversation notifications --> <dimen name="conversation_single_line_face_pile_protection_width">1dp</dimen> @@ -1075,8 +1075,7 @@ <dimen name="edge_margin">8dp</dimen> <!-- The absolute side margins of quick settings --> - <dimen name="quick_settings_expanded_bottom_margin">16dp</dimen> - <dimen name="quick_settings_media_extra_bottom_margin">6dp</dimen> + <dimen name="quick_settings_bottom_margin_media">16dp</dimen> <dimen name="rounded_corner_content_padding">0dp</dimen> <dimen name="nav_content_padding">0dp</dimen> <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen> @@ -1264,8 +1263,8 @@ <dimen name="qs_media_panel_outer_padding">16dp</dimen> <dimen name="qs_media_album_size">52dp</dimen> <dimen name="qs_seamless_icon_size">20dp</dimen> - <dimen name="qqs_media_spacing">8dp</dimen> - <dimen name="qqs_horizonal_tile_padding_bottom">8dp</dimen> + <dimen name="qqs_media_spacing">16dp</dimen> + <dimen name="qs_footer_horizontal_margin">22dp</dimen> <dimen name="magnification_border_size">5dp</dimen> <dimen name="magnification_frame_move_short">5dp</dimen> @@ -1326,8 +1325,8 @@ <dimen name="controls_management_editing_list_margin">48dp</dimen> <dimen name="controls_management_editing_divider_margin">24dp</dimen> <dimen name="controls_management_apps_extra_side_margin">8dp</dimen> - <dimen name="controls_management_apps_top_margin"></dimen> <dimen name="controls_management_zone_top_margin">32dp</dimen> + <dimen name="controls_management_status_side_margin">16dp</dimen> <dimen name="controls_management_page_indicator_height">24dp</dimen> <dimen name="controls_management_checkbox_size">25dp</dimen> <dimen name="controls_title_size">24sp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 48ff5c681853..73568eab5eac 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2640,7 +2640,7 @@ <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string> <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]--> <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string> - <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=20] --> + <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] --> <string name="bubble_dismiss_text">Dismiss bubble</string> <!-- Button text to stop a conversation from bubbling [CHAR LIMIT=60]--> <string name="bubbles_dont_bubble_conversation">Don\u2019t bubble conversation</string> @@ -2741,8 +2741,10 @@ <!-- Controls management favorites screen, See other apps with changes made [CHAR LIMIT=NONE] --> <string name="controls_favorite_toast_no_changes">Changes not saved</string> - <!-- Controls management controls screen error on load message [CHAR LIMIT=60] --> - <string name="controls_favorite_load_error">The list of all controls could not be loaded.</string> + <!-- Controls management controls screen error on load message [CHAR LIMIT=NONE] --> + <string name="controls_favorite_load_error">Controls could not be loaded. Check the <xliff:g id="app" example="System UI">%s</xliff:g> app to make sure that the app settings haven\u2019t changed.</string> + <!-- Controls management controls screen no controls found on load message [CHAR LIMIT=NONE] --> + <string name="controls_favorite_load_none">Compatible controls unavailable</string> <!-- Controls management controls screen header for Other zone [CHAR LIMIT=60] --> <string name="controls_favorite_other_zone_header">Other</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 8097c01cb042..68c2a38f53c3 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -387,11 +387,6 @@ <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item> </style> - <style name="qs_security_footer" parent="@style/qs_theme"> - <item name="android:textColor">?android:attr/textColorSecondary</item> - <item name="android:tint">?android:attr/textColorSecondary</item> - </style> - <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light"> <item name="android:colorAccent">@color/remote_input_accent</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java new file mode 100644 index 000000000000..9f26d851f775 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java @@ -0,0 +1,37 @@ +/* + * 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.shared.system; + +import android.app.ActivityManager; +import android.os.SystemProperties; + +public abstract class BlurUtils { + + private static boolean mBlurSupportedSysProp = SystemProperties + .getBoolean("ro.surface_flinger.supports_background_blur", false); + private static boolean mBlurDisabledSysProp = SystemProperties + .getBoolean("persist.sys.sf.disable_blurs", false); + + /** + * If this device can render blurs. + * + * @return {@code true} when supported. + */ + public static boolean supportsBlursOnWindows() { + return mBlurSupportedSysProp && !mBlurDisabledSysProp && ActivityManager.isHighEndGfx(); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java index 31fe22e57084..82e6251a4484 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java @@ -115,7 +115,6 @@ public class SyncRtSurfaceTransactionApplierCompat { t.deferTransactionUntil(surfaceParams.surface, mBarrierSurfaceControl, frame); surfaceParams.applyTo(t); } - t.setEarlyWakeup(); t.apply(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java index bdb6c063521f..b966f9356849 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java @@ -99,8 +99,8 @@ public class TransactionCompat { return this; } + @Deprecated public TransactionCompat setEarlyWakeup() { - mTransaction.setEarlyWakeup(); return this; } @@ -114,7 +114,7 @@ public class TransactionCompat { t.deferTransactionUntil(surfaceControl, barrier, frameNumber); } + @Deprecated public static void setEarlyWakeup(Transaction t) { - t.setEarlyWakeup(); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 1a4dd1e6dd36..1db2e32b8cdb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -25,12 +25,16 @@ import static com.android.systemui.DejankUtils.whitelistIpcs; import static java.lang.Integer.max; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.app.Activity; import android.app.AlertDialog; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.content.res.ColorStateList; +import android.graphics.Insets; import android.graphics.Rect; import android.metrics.LogMaker; import android.os.Handler; @@ -48,9 +52,13 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; +import android.view.WindowInsetsAnimationControlListener; +import android.view.WindowInsetsAnimationController; import android.view.WindowManager; import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringAnimation; @@ -64,6 +72,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.shared.system.SysUiStatsLog; @@ -100,6 +109,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl(); + private static final long IME_DISAPPEAR_DURATION_MS = 125; + private KeyguardSecurityModel mSecurityModel; private LockPatternUtils mLockPatternUtils; @@ -125,6 +136,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe private int mActivePointerId = -1; private boolean mIsDragging; private float mStartTouchY = -1; + private boolean mDisappearAnimRunning; private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { @@ -148,22 +160,29 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe public WindowInsets onProgress(WindowInsets windowInsets, List<WindowInsetsAnimation> list) { int translationY = 0; - for (WindowInsetsAnimation animation : list) { - if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) { - continue; + if (mDisappearAnimRunning) { + mSecurityViewFlipper.setTranslationY( + mInitialBounds.bottom - mFinalBounds.bottom); + } else { + for (WindowInsetsAnimation animation : list) { + if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) { + continue; + } + final int paddingBottom = (int) MathUtils.lerp( + mInitialBounds.bottom - mFinalBounds.bottom, 0, + animation.getInterpolatedFraction()); + translationY += paddingBottom; } - final int paddingBottom = (int) MathUtils.lerp( - mInitialBounds.bottom - mFinalBounds.bottom, 0, - animation.getInterpolatedFraction()); - translationY += paddingBottom; + mSecurityViewFlipper.setTranslationY(translationY); } - mSecurityViewFlipper.setTranslationY(translationY); return windowInsets; } @Override public void onEnd(WindowInsetsAnimation animation) { - mSecurityViewFlipper.setTranslationY(0); + if (!mDisappearAnimRunning) { + mSecurityViewFlipper.setTranslationY(0); + } } }; @@ -376,8 +395,51 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe } public boolean startDisappearAnimation(Runnable onFinishRunnable) { + mDisappearAnimRunning = true; if (mCurrentSecuritySelection == SecurityMode.Password) { - mSecurityViewFlipper.getWindowInsetsController().hide(WindowInsets.Type.ime()); + mSecurityViewFlipper.getWindowInsetsController().controlWindowInsetsAnimation(ime(), + IME_DISAPPEAR_DURATION_MS, + Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() { + + + @Override + public void onReady(@NonNull WindowInsetsAnimationController controller, + int types) { + ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f); + anim.addUpdateListener(animation -> { + if (controller.isCancelled()) { + return; + } + Insets shownInsets = controller.getShownStateInsets(); + Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0, + (int) (-shownInsets.bottom / 4 + * anim.getAnimatedFraction()))); + controller.setInsetsAndAlpha(insets, + (float) animation.getAnimatedValue(), + anim.getAnimatedFraction()); + }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + controller.finish(false); + } + }); + anim.setDuration(IME_DISAPPEAR_DURATION_MS); + anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); + anim.start(); + } + + @Override + public void onFinished( + @NonNull WindowInsetsAnimationController controller) { + mDisappearAnimRunning = false; + } + + @Override + public void onCancelled( + @Nullable WindowInsetsAnimationController controller) { + } + }); } if (mCurrentSecuritySelection != SecurityMode.None) { return getSecurityView(mCurrentSecuritySelection).startDisappearAnimation( @@ -887,6 +949,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe @Override public void reset() { mSecurityViewFlipper.reset(); + mDisappearAnimRunning = false; } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index ee31706c0b94..7914d864da0f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -79,6 +79,7 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyManager; +import android.util.EventLog; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -1074,6 +1075,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab != LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; } + private boolean isUserEncryptedOrLockdown(int userId) { + // Biometrics should not be started in this case. Think carefully before modifying this + // method, see b/79776455 + final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(userId); + final boolean isLockDown = + containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) + || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT); + return isLockDown || isEncrypted; + } + private boolean containsFlag(int haystack, int needle) { return (haystack & needle) != 0; } @@ -1903,6 +1915,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean shouldListenForFingerprint() { final boolean allowedOnBouncer = !(mFingerprintLockedOut && mBouncer && mCredentialAttempted); + final int user = getCurrentUser(); // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. @@ -1911,7 +1924,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming)) && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser()) && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser - && allowedOnBouncer; + && allowedOnBouncer && !isUserEncryptedOrLockdown(user); return shouldListen; } @@ -1925,12 +1938,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !statusBarShadeLocked; final int user = getCurrentUser(); final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user); - final boolean isLockDown = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) - || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - final boolean isEncryptedOrTimedOut = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT) - || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); + final boolean isTimedOut = + containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); boolean canBypass = mKeyguardBypassController != null && mKeyguardBypassController.canBypass(); @@ -1939,10 +1948,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // TrustAgents or biometrics are keeping the device unlocked. boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass; - // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing. + // Scan even when timeout to show a preemptive bouncer when bypassing. // Lock-down mode shouldn't scan, since it is more explicit. - boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass && !mBouncer) - && !isLockDown; + boolean strongAuthAllowsScanning = (!isTimedOut || canBypass && !mBouncer); // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. @@ -1952,7 +1960,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !mSwitchingUser && !isFaceDisabled(user) && becauseCannotSkipBouncer && !mKeyguardGoingAway && mFaceSettingEnabledForUser.get(user) && !mLockIconPressed && strongAuthAllowsScanning && mIsPrimaryUser - && !mSecureCameraLaunched; + && !mSecureCameraLaunched && !isUserEncryptedOrLockdown(user); // Aggregate relevant fields for debug logging. if (DEBUG_FACE || DEBUG_SPEW) { @@ -2025,6 +2033,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (mFingerprintCancelSignal != null) { mFingerprintCancelSignal.cancel(); } + + if (isUserEncryptedOrLockdown(userId)) { + // If this happens, shouldListenForFingerprint() is wrong. SafetyNet for b/79776455 + EventLog.writeEvent(0x534e4554, "79776455", "startListeningForFingerprint"); + } mFingerprintCancelSignal = new CancellationSignal(); mFpm.authenticate(null, mFingerprintCancelSignal, 0, mFingerprintAuthenticationCallback, null, userId); @@ -2043,6 +2056,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (mFaceCancelSignal != null) { mFaceCancelSignal.cancel(); } + + if (isUserEncryptedOrLockdown(userId)) { + // If this happens, shouldListenForFace() is wrong. SafetyNet for b/79776455 + EventLog.writeEvent(0x534e4554, "79776455", "startListeningForFace"); + } mFaceCancelSignal = new CancellationSignal(); mFaceManager.authenticate(null, mFaceCancelSignal, 0, mFaceAuthenticationCallback, null, userId); diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java index 525e98971266..7ae3e73caa23 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java @@ -164,7 +164,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac return Long.max(mShowAndGoEndsAt - SystemClock.elapsedRealtime(), 0); } - boolean areHandlesShowing() { + public boolean areHandlesShowing() { return mHandlesShowing; } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt new file mode 100644 index 000000000000..e65139275685 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt @@ -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.assist + +import android.content.ComponentName +import android.content.Context +import android.content.pm.PackageManager +import android.util.Log +import com.android.internal.app.AssistUtils +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import com.android.internal.logging.UiEventLogger +import com.android.internal.util.FrameworkStatsLog +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.assist.AssistantInvocationEvent.Companion.deviceStateFromLegacyDeviceState +import com.android.systemui.assist.AssistantInvocationEvent.Companion.eventFromLegacyInvocationType +import javax.inject.Inject +import javax.inject.Singleton + +/** Class for reporting events related to Assistant sessions. */ +@Singleton +open class AssistLogger @Inject constructor( + protected val context: Context, + protected val uiEventLogger: UiEventLogger, + private val assistUtils: AssistUtils, + private val phoneStateMonitor: PhoneStateMonitor, + private val assistHandleBehaviorController: AssistHandleBehaviorController +) { + + private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX) + + private var currentInstanceId: InstanceId? = null + + fun reportAssistantInvocationEventFromLegacy( + legacyInvocationType: Int, + isInvocationComplete: Boolean, + assistantComponent: ComponentName? = null, + legacyDeviceState: Int? = null + ) { + val deviceState = if (legacyDeviceState == null) { + null + } else { + deviceStateFromLegacyDeviceState(legacyDeviceState) + } + reportAssistantInvocationEvent( + eventFromLegacyInvocationType(legacyInvocationType, isInvocationComplete), + assistantComponent, + deviceState) + } + + fun reportAssistantInvocationEvent( + invocationEvent: AssistantInvocationEvent, + assistantComponent: ComponentName? = null, + deviceState: Int? = null + ) { + + val assistComponentFinal = assistantComponent ?: getAssistantComponentForCurrentUser() + + val assistantUid = getAssistantUid(assistComponentFinal) + + val deviceStateFinal = deviceState + ?: deviceStateFromLegacyDeviceState(phoneStateMonitor.phoneState) + + FrameworkStatsLog.write( + FrameworkStatsLog.ASSISTANT_INVOCATION_REPORTED, + invocationEvent.id, + assistantUid, + assistComponentFinal.flattenToString(), + getOrCreateInstanceId().id, + deviceStateFinal, + assistHandleBehaviorController.areHandlesShowing()) + reportAssistantInvocationExtraData() + } + + fun reportAssistantSessionEvent(sessionEvent: AssistantSessionEvent) { + val assistantComponent = getAssistantComponentForCurrentUser() + val assistantUid = getAssistantUid(assistantComponent) + uiEventLogger.logWithInstanceId( + sessionEvent, + assistantUid, + assistantComponent.flattenToString(), + getOrCreateInstanceId()) + + if (SESSION_END_EVENTS.contains(sessionEvent)) { + clearInstanceId() + } + } + + protected open fun reportAssistantInvocationExtraData() { + } + + protected fun getOrCreateInstanceId(): InstanceId { + val instanceId = currentInstanceId ?: instanceIdSequence.newInstanceId() + currentInstanceId = instanceId + return instanceId + } + + protected fun clearInstanceId() { + currentInstanceId = null + } + + protected fun getAssistantComponentForCurrentUser(): ComponentName { + return assistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser()) + } + + protected fun getAssistantUid(assistantComponent: ComponentName): Int { + var assistantUid = 0 + try { + assistantUid = context.packageManager.getApplicationInfo( + assistantComponent.packageName, /* flags = */ + 0).uid + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Unable to find Assistant UID", e) + } + return assistantUid + } + + companion object { + protected const val TAG = "AssistLogger" + + private const val INSTANCE_ID_MAX = 1 shl 20 + + private val SESSION_END_EVENTS = + setOf( + AssistantSessionEvent.ASSISTANT_SESSION_INVOCATION_CANCELLED, + AssistantSessionEvent.ASSISTANT_SESSION_CLOSE) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index a7533adc795e..6d179f27f4fb 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -40,11 +40,8 @@ import android.widget.ImageView; import com.android.internal.app.AssistUtils; import com.android.internal.app.IVoiceInteractionSessionListener; import com.android.internal.app.IVoiceInteractionSessionShowCallback; -import com.android.internal.logging.InstanceId; -import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.util.FrameworkStatsLog; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.R; @@ -124,7 +121,6 @@ public class AssistManager { private static final long TIMEOUT_SERVICE = 2500; private static final long TIMEOUT_ACTIVITY = 1000; - private static final int INSTANCE_ID_MAX = 1 << 20; protected final Context mContext; private final WindowManager mWindowManager; @@ -134,8 +130,7 @@ public class AssistManager { private final AssistHandleBehaviorController mHandleController; private final UiController mUiController; protected final Lazy<SysUiState> mSysUiState; - protected final InstanceIdSequence mInstanceIdSequence = - new InstanceIdSequence(INSTANCE_ID_MAX); + protected final AssistLogger mAssistLogger; private AssistOrbContainer mView; private final DeviceProvisionedController mDeviceProvisionedController; @@ -202,7 +197,9 @@ public class AssistManager { PhoneStateMonitor phoneStateMonitor, OverviewProxyService overviewProxyService, ConfigurationController configurationController, - Lazy<SysUiState> sysUiState) { + Lazy<SysUiState> sysUiState, + DefaultUiController defaultUiController, + AssistLogger assistLogger) { mContext = context; mDeviceProvisionedController = controller; mCommandQueue = commandQueue; @@ -211,6 +208,7 @@ public class AssistManager { mAssistDisclosure = new AssistDisclosure(context, new Handler()); mPhoneStateMonitor = phoneStateMonitor; mHandleController = handleController; + mAssistLogger = assistLogger; configurationController.addCallback(mConfigurationListener); @@ -221,7 +219,7 @@ public class AssistManager { mConfigurationListener.onConfigChanged(context.getResources().getConfiguration()); mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic(); - mUiController = new DefaultUiController(mContext); + mUiController = defaultUiController; mSysUiState = sysUiState; @@ -248,6 +246,8 @@ public class AssistManager { if (VERBOSE) { Log.v(TAG, "Voice open"); } + mAssistLogger.reportAssistantSessionEvent( + AssistantSessionEvent.ASSISTANT_SESSION_UPDATE); } @Override @@ -255,6 +255,8 @@ public class AssistManager { if (VERBOSE) { Log.v(TAG, "Voice closed"); } + mAssistLogger.reportAssistantSessionEvent( + AssistantSessionEvent.ASSISTANT_SESSION_CLOSE); } @Override @@ -298,15 +300,19 @@ public class AssistManager { if (args == null) { args = new Bundle(); } - int invocationType = args.getInt(INVOCATION_TYPE_KEY, 0); - if (invocationType == INVOCATION_TYPE_GESTURE) { + int legacyInvocationType = args.getInt(INVOCATION_TYPE_KEY, 0); + if (legacyInvocationType == INVOCATION_TYPE_GESTURE) { mHandleController.onAssistantGesturePerformed(); } - int phoneState = mPhoneStateMonitor.getPhoneState(); - args.putInt(INVOCATION_PHONE_STATE_KEY, phoneState); + int legacyDeviceState = mPhoneStateMonitor.getPhoneState(); + args.putInt(INVOCATION_PHONE_STATE_KEY, legacyDeviceState); args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.elapsedRealtime()); - logStartAssist(/* instanceId = */ null, invocationType, phoneState); - logStartAssistLegacy(invocationType, phoneState); + mAssistLogger.reportAssistantInvocationEventFromLegacy( + legacyInvocationType, + /* isInvocationComplete = */ true, + assistComponent, + legacyDeviceState); + logStartAssistLegacy(legacyInvocationType, legacyDeviceState); startAssistInternal(args, assistComponent, isService); } @@ -506,34 +512,6 @@ public class AssistManager { return toLoggingSubType(invocationType, mPhoneStateMonitor.getPhoneState()); } - protected void logStartAssist( - @Nullable InstanceId instanceId, int invocationType, int deviceState) { - InstanceId currentInstanceId = - instanceId == null ? mInstanceIdSequence.newInstanceId() : instanceId; - ComponentName assistantComponent = - mAssistUtils.getAssistComponentForUser(UserHandle.USER_CURRENT); - int assistantUid = 0; - try { - assistantUid = - mContext.getPackageManager() - .getApplicationInfo( - assistantComponent.getPackageName(), - /* flags = */ 0) - .uid; - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Unable to find Assistant UID", e); - } - - FrameworkStatsLog.write( - FrameworkStatsLog.ASSISTANT_INVOCATION_REPORTED, - AssistantInvocationEvent.Companion.eventIdFromLegacyInvocationType(invocationType), - assistantUid, - assistantComponent.flattenToString(), - currentInstanceId.getId(), - AssistantInvocationEvent.Companion.deviceStateFromLegacyDeviceState(deviceState), - mHandleController.areHandlesShowing()); - } - protected void logStartAssistLegacy(int invocationType, int phoneState) { MetricsLogger.action( new LogMaker(MetricsEvent.ASSISTANT) diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistantInvocationEvent.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistantInvocationEvent.kt index 1de7b8423617..fb5f1d1f725f 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistantInvocationEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistantInvocationEvent.kt @@ -50,33 +50,55 @@ enum class AssistantInvocationEvent(private val id: Int) : UiEventLogger.UiEvent ASSISTANT_INVOCATION_HOME_LONG_PRESS(447), @UiEvent(doc = "Assistant invoked by physical gesture") - ASSISTANT_INVOCATION_PHYSICAL_GESTURE(448); + ASSISTANT_INVOCATION_PHYSICAL_GESTURE(448), + + @UiEvent(doc = "Assistant invocation started by unknown method") + ASSISTANT_INVOCATION_START_UNKNOWN(530), + + @UiEvent(doc = "Assistant invocation started by touch gesture") + ASSISTANT_INVOCATION_START_TOUCH_GESTURE(531), + + @UiEvent(doc = "Assistant invocation started by physical gesture") + ASSISTANT_INVOCATION_START_PHYSICAL_GESTURE(532); override fun getId(): Int { return id } companion object { - fun eventIdFromLegacyInvocationType(legacyInvocationType: Int): Int { - return when (legacyInvocationType) { - AssistManager.INVOCATION_TYPE_GESTURE -> - ASSISTANT_INVOCATION_TOUCH_GESTURE - - AssistManager.INVOCATION_TYPE_OTHER -> - ASSISTANT_INVOCATION_PHYSICAL_GESTURE - - AssistManager.INVOCATION_TYPE_VOICE -> - ASSISTANT_INVOCATION_HOTWORD - - AssistManager.INVOCATION_TYPE_QUICK_SEARCH_BAR -> - ASSISTANT_INVOCATION_QUICK_SEARCH_BAR - - AssistManager.INVOCATION_HOME_BUTTON_LONG_PRESS -> - ASSISTANT_INVOCATION_HOME_LONG_PRESS - - else -> - ASSISTANT_INVOCATION_UNKNOWN - }.id + fun eventFromLegacyInvocationType(legacyInvocationType: Int, isInvocationComplete: Boolean) + : AssistantInvocationEvent { + return if (isInvocationComplete) { + when (legacyInvocationType) { + AssistManager.INVOCATION_TYPE_GESTURE -> + ASSISTANT_INVOCATION_TOUCH_GESTURE + + AssistManager.INVOCATION_TYPE_OTHER -> + ASSISTANT_INVOCATION_PHYSICAL_GESTURE + + AssistManager.INVOCATION_TYPE_VOICE -> + ASSISTANT_INVOCATION_HOTWORD + + AssistManager.INVOCATION_TYPE_QUICK_SEARCH_BAR -> + ASSISTANT_INVOCATION_QUICK_SEARCH_BAR + + AssistManager.INVOCATION_HOME_BUTTON_LONG_PRESS -> + ASSISTANT_INVOCATION_HOME_LONG_PRESS + + else -> + ASSISTANT_INVOCATION_UNKNOWN + } + } else { + when (legacyInvocationType) { + AssistManager.INVOCATION_TYPE_GESTURE -> + ASSISTANT_INVOCATION_START_TOUCH_GESTURE + + AssistManager.INVOCATION_TYPE_OTHER -> + ASSISTANT_INVOCATION_START_PHYSICAL_GESTURE + + else -> ASSISTANT_INVOCATION_START_UNKNOWN + } + } } fun deviceStateFromLegacyDeviceState(legacyDeviceState: Int): Int { diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt new file mode 100644 index 000000000000..8b953fa46441 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt @@ -0,0 +1,48 @@ +/* + * 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.assist + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +enum class AssistantSessionEvent(private val id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "Unknown assistant session event") + ASSISTANT_SESSION_UNKNOWN(523), + + @UiEvent(doc = "Assistant session dismissed due to timeout") + ASSISTANT_SESSION_TIMEOUT_DISMISS(524), + + @UiEvent(doc = "User began a gesture for invoking the Assistant") + ASSISTANT_SESSION_INVOCATION_START(525), + + @UiEvent(doc = + "User stopped a gesture for invoking the Assistant before the gesture was completed") + ASSISTANT_SESSION_INVOCATION_CANCELLED(526), + + @UiEvent(doc = "User manually dismissed the Assistant session") + ASSISTANT_SESSION_USER_DISMISS(527), + + @UiEvent(doc = "The Assistant session has changed modes") + ASSISTANT_SESSION_UPDATE(528), + + @UiEvent(doc = "The Assistant session completed") + ASSISTANT_SESSION_CLOSE(529); + + override fun getId(): Int { + return id + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java index 652ce6f04db0..257ad50eff61 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java @@ -104,7 +104,7 @@ public final class PhoneStateMonitor { }); } - int getPhoneState() { + public int getPhoneState() { int phoneState; if (isShadeFullscreen()) { phoneState = getPhoneLockscreenState(); diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java index 68242f0a0ac2..05f3617dd1d6 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java @@ -38,15 +38,21 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.assist.AssistHandleViewController; +import com.android.systemui.assist.AssistLogger; import com.android.systemui.assist.AssistManager; +import com.android.systemui.assist.AssistantSessionEvent; import com.android.systemui.statusbar.NavigationBarController; import java.util.Locale; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Default UiController implementation. Shows white edge lights along the bottom of the phone, * expanding from the corners to meet in the center. */ +@Singleton public class DefaultUiController implements AssistManager.UiController { private static final String TAG = "DefaultUiController"; @@ -58,6 +64,7 @@ public class DefaultUiController implements AssistManager.UiController { protected final FrameLayout mRoot; protected InvocationLightsView mInvocationLightsView; + protected final AssistLogger mAssistLogger; private final WindowManager mWindowManager; private final WindowManager.LayoutParams mLayoutParams; @@ -69,7 +76,9 @@ public class DefaultUiController implements AssistManager.UiController { private ValueAnimator mInvocationAnimator = new ValueAnimator(); - public DefaultUiController(Context context) { + @Inject + public DefaultUiController(Context context, AssistLogger assistLogger) { + mAssistLogger = assistLogger; mRoot = new FrameLayout(context); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); @@ -142,6 +151,11 @@ public class DefaultUiController implements AssistManager.UiController { if (VERBOSE) { Log.v(TAG, "Invocation started: type=" + type); } + mAssistLogger.reportAssistantInvocationEventFromLegacy( + type, + /* isInvocationComplete = */ false, + /* assistantComponent = */ null, + /* legacyDeviceState = */ null); MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT) .setType(MetricsEvent.TYPE_ACTION) .setSubtype(Dependency.get(AssistManager.class).toLoggingSubType(type))); @@ -152,6 +166,8 @@ public class DefaultUiController implements AssistManager.UiController { if (VERBOSE) { Log.v(TAG, "Invocation cancelled: type=" + type); } + mAssistLogger.reportAssistantSessionEvent( + AssistantSessionEvent.ASSISTANT_SESSION_INVOCATION_CANCELLED); MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT) .setType(MetricsEvent.TYPE_DISMISS) .setSubtype(DISMISS_REASON_INVOCATION_CANCELLED)); diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index 4269605cef12..67c0c620f136 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt @@ -16,8 +16,10 @@ package com.android.systemui.broadcast +import android.app.ActivityManager import android.content.BroadcastReceiver import android.content.Context +import android.content.Intent import android.content.IntentFilter import android.os.Handler import android.os.HandlerExecutor @@ -28,15 +30,11 @@ import android.text.TextUtils import android.util.SparseArray import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Dumpable -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dump.DumpManager import java.io.FileDescriptor import java.io.PrintWriter -import java.lang.IllegalStateException import java.util.concurrent.Executor -import javax.inject.Inject -import javax.inject.Singleton data class ReceiverData( val receiver: BroadcastReceiver, @@ -48,6 +46,8 @@ data class ReceiverData( private const val MSG_ADD_RECEIVER = 0 private const val MSG_REMOVE_RECEIVER = 1 private const val MSG_REMOVE_RECEIVER_FOR_USER = 2 +private const val MSG_USER_SWITCH = 3 +private const val MSG_SET_STARTING_USER = 99 private const val TAG = "BroadcastDispatcher" private const val DEBUG = true @@ -62,20 +62,27 @@ private const val DEBUG = true * permissions, schemes, data types, data authorities or priority different than 0. * Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery). */ -@Singleton -open class BroadcastDispatcher @Inject constructor ( +open class BroadcastDispatcher constructor ( private val context: Context, - @Main private val mainHandler: Handler, - @Background private val bgLooper: Looper, - dumpManager: DumpManager -) : Dumpable { + private val bgLooper: Looper, + private val dumpManager: DumpManager, + private val logger: BroadcastDispatcherLogger +) : Dumpable, BroadcastReceiver() { // Only modify in BG thread private val receiversByUser = SparseArray<UserBroadcastDispatcher>(20) - init { - // TODO: Don't do this in the constructor + fun initialize() { dumpManager.registerDumpable(javaClass.name, this) + handler.sendEmptyMessage(MSG_SET_STARTING_USER) + registerReceiver(this, IntentFilter(Intent.ACTION_USER_SWITCHED), null, UserHandle.ALL) + } + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_USER_SWITCHED) { + val user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) + handler.obtainMessage(MSG_USER_SWITCH, user, 0).sendToTarget() + } } /** @@ -87,7 +94,7 @@ open class BroadcastDispatcher @Inject constructor ( * have at least one action. * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. * @param user A user handle to determine which broadcast should be dispatched to this receiver. - * By default, it is the current user. + * By default, it is the user of the context (system user in SystemUI). * @throws IllegalArgumentException if the filter has other constraints that are not actions or * categories or the filter has no actions. */ @@ -113,7 +120,7 @@ open class BroadcastDispatcher @Inject constructor ( * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an * executor in the main thread (default). * @param user A user handle to determine which broadcast should be dispatched to this receiver. - * By default, it is the current user. + * By default, it is the user of the context (system user in SystemUI). * @throws IllegalArgumentException if the filter has other constraints that are not actions or * categories or the filter has no actions. */ @@ -156,7 +163,7 @@ open class BroadcastDispatcher @Inject constructor ( /** * Unregister receiver for a particular user. * - * @param receiver The receiver to unregister. It will be unregistered for all users. + * @param receiver The receiver to unregister. * @param user The user associated to the registered [receiver]. It can be [UserHandle.ALL]. */ open fun unregisterReceiverForUser(receiver: BroadcastReceiver, user: UserHandle) { @@ -166,10 +173,11 @@ open class BroadcastDispatcher @Inject constructor ( @VisibleForTesting protected open fun createUBRForUser(userId: Int) = - UserBroadcastDispatcher(context, userId, bgLooper) + UserBroadcastDispatcher(context, userId, bgLooper, logger) override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("Broadcast dispatcher:") + pw.println(" Current user: ${handler.currentUser}") for (index in 0 until receiversByUser.size()) { pw.println(" User ${receiversByUser.keyAt(index)}") receiversByUser.valueAt(index).dump(fd, pw, args) @@ -177,6 +185,8 @@ open class BroadcastDispatcher @Inject constructor ( } private val handler = object : Handler(bgLooper) { + var currentUser = UserHandle.USER_SYSTEM + override fun handleMessage(msg: Message) { when (msg.what) { MSG_ADD_RECEIVER -> { @@ -184,7 +194,7 @@ open class BroadcastDispatcher @Inject constructor ( // If the receiver asked to be registered under the current user, we register // under the actual current user. val userId = if (data.user.identifier == UserHandle.USER_CURRENT) { - context.userId + currentUser } else { data.user.identifier } @@ -207,6 +217,13 @@ open class BroadcastDispatcher @Inject constructor ( receiversByUser.get(msg.arg1)?.unregisterReceiver(msg.obj as BroadcastReceiver) } + MSG_USER_SWITCH -> { + currentUser = msg.arg1 + } + MSG_SET_STARTING_USER -> { + currentUser = ActivityManager.getCurrentUser() + } + else -> super.handleMessage(msg) } } diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt index 3272fb7545e2..96f5a1f6fbe8 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt @@ -30,6 +30,7 @@ import android.util.Log import androidx.annotation.VisibleForTesting import com.android.internal.util.Preconditions import com.android.systemui.Dumpable +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import java.io.FileDescriptor import java.io.PrintWriter import java.lang.IllegalArgumentException @@ -54,7 +55,8 @@ private const val DEBUG = false class UserBroadcastDispatcher( private val context: Context, private val userId: Int, - private val bgLooper: Looper + private val bgLooper: Looper, + private val logger: BroadcastDispatcherLogger ) : BroadcastReceiver(), Dumpable { companion object { @@ -109,10 +111,12 @@ class UserBroadcastDispatcher( } override fun onReceive(context: Context, intent: Intent) { - val id = if (DEBUG) index.getAndIncrement() else 0 + val id = index.getAndIncrement() if (DEBUG) Log.w(TAG, "[$id] Received $intent") + logger.logBroadcastReceived(id, userId, intent) bgHandler.post( - HandleBroadcastRunnable(actionsToReceivers, context, intent, pendingResult, id)) + HandleBroadcastRunnable( + actionsToReceivers, context, intent, pendingResult, id, logger)) } /** @@ -143,6 +147,7 @@ class UserBroadcastDispatcher( ArraySet() }.add(receiverData) } + logger.logReceiverRegistered(userId, receiverData.receiver) if (changed) { createFilterAndRegisterReceiverBG() } @@ -163,6 +168,7 @@ class UserBroadcastDispatcher( actionsToReceivers.remove(action) } } + logger.logReceiverUnregistered(userId, receiver) if (changed) { createFilterAndRegisterReceiverBG() } @@ -187,7 +193,8 @@ class UserBroadcastDispatcher( val context: Context, val intent: Intent, val pendingResult: PendingResult, - val index: Int + val index: Int, + val logger: BroadcastDispatcherLogger ) : Runnable { override fun run() { if (DEBUG) Log.w(TAG, "[$index] Dispatching $intent") @@ -199,6 +206,7 @@ class UserBroadcastDispatcher( it.executor.execute { if (DEBUG) Log.w(TAG, "[$index] Dispatching ${intent.action} to ${it.receiver}") + logger.logBroadcastDispatched(index, intent.action, it.receiver) it.receiver.pendingResult = pendingResult it.receiver.onReceive(context, intent) } @@ -215,6 +223,7 @@ class UserBroadcastDispatcher( if (registered.get()) { try { context.unregisterReceiver(this@UserBroadcastDispatcher) + logger.logContextReceiverUnregistered(userId) } catch (e: IllegalArgumentException) { Log.e(TAG, "Trying to unregister unregistered receiver for user $userId", IllegalStateException(e)) @@ -230,6 +239,7 @@ class UserBroadcastDispatcher( null, bgHandler) registered.set(true) + logger.logContextReceiverRegistered(userId, intentFilter) } } } diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt new file mode 100644 index 000000000000..123a8ae6307d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt @@ -0,0 +1,117 @@ +/* + * 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.broadcast.logging + +import android.content.BroadcastReceiver +import android.content.Intent +import android.content.IntentFilter +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.LogMessage +import com.android.systemui.log.dagger.BroadcastDispatcherLog +import javax.inject.Inject + +private const val TAG = "BroadcastDispatcherLog" + +class BroadcastDispatcherLogger @Inject constructor( + @BroadcastDispatcherLog private val buffer: LogBuffer +) { + + fun logBroadcastReceived(broadcastId: Int, user: Int, intent: Intent) { + val intentString = intent.toString() + log(INFO, { + int1 = broadcastId + int2 = user + str1 = intentString + }, { + "[$int1] Broadcast received for user $int2: $str1" + }) + } + + fun logBroadcastDispatched(broadcastId: Int, action: String?, receiver: BroadcastReceiver) { + val receiverString = receiver.toString() + log(DEBUG, { + int1 = broadcastId + str1 = action + str2 = receiverString + }, { + "Broadcast $int1 ($str1) dispatched to $str2" + }) + } + + fun logReceiverRegistered(user: Int, receiver: BroadcastReceiver) { + val receiverString = receiver.toString() + log(INFO, { + int1 = user + str1 = receiverString + }, { + "Receiver $str1 registered for user $int1" + }) + } + + fun logReceiverUnregistered(user: Int, receiver: BroadcastReceiver) { + val receiverString = receiver.toString() + log(INFO, { + int1 = user + str1 = receiverString + }, { + "Receiver $str1 unregistered for user $int1" + }) + } + + fun logContextReceiverRegistered(user: Int, filter: IntentFilter) { + val actions = filter.actionsIterator().asSequence() + .joinToString(separator = ",", prefix = "Actions(", postfix = ")") + val categories = if (filter.countCategories() != 0) { + filter.categoriesIterator().asSequence() + .joinToString(separator = ",", prefix = "Categories(", postfix = ")") + } else { + "" + } + log(INFO, { + int1 = user + str1 = if (categories != "") { + "${actions}\n$categories" + } else { + actions + } + }, { + """ + Receiver registered with Context for user $int1. + $str1 + """.trimIndent() + }) + } + + fun logContextReceiverUnregistered(user: Int) { + log(INFO, { + int1 = user + }, { + "Receiver unregistered with Context for user $int1." + }) + } + + private inline fun log( + logLevel: LogLevel, + initializer: LogMessage.() -> Unit, + noinline printer: LogMessage.() -> String + ) { + buffer.log(TAG, logLevel, initializer, printer) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java index 55be77ca3be5..a86a46960bcb 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -100,6 +100,7 @@ public class BadgedImageView extends ImageView { mDotRenderer = new DotRenderer(mBubbleBitmapSize, iconPath, DEFAULT_PATH_SIZE); setFocusable(true); + setClickable(true); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 7f78ddf2cf1c..6377b4f0a9c5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -15,7 +15,6 @@ */ package com.android.systemui.bubbles; -import static android.app.Notification.FLAG_BUBBLE; import static android.os.AsyncTask.Status.FINISHED; import static android.view.Display.INVALID_DISPLAY; @@ -29,21 +28,19 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Path; -import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Bundle; +import android.graphics.drawable.Icon; import android.os.UserHandle; import android.provider.Settings; -import android.service.notification.StatusBarNotification; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.InstanceId; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -57,17 +54,12 @@ import java.util.Objects; class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; - /** - * NotificationEntry associated with the bubble. A null value implies this bubble is loaded - * from disk. - */ - @Nullable - private NotificationEntry mEntry; private final String mKey; private long mLastUpdated; private long mLastAccessed; + @Nullable private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; /** Whether the bubble should show a dot for the notification indicating updated content. */ @@ -75,8 +67,6 @@ class Bubble implements BubbleViewProvider { /** Whether flyout text should be suppressed, regardless of any other flags or state. */ private boolean mSuppressFlyout; - /** Whether this bubble should auto expand regardless of the normal flag, used for overflow. */ - private boolean mShouldAutoExpand; // Items that are typically loaded later private String mAppName; @@ -92,6 +82,7 @@ class Bubble implements BubbleViewProvider { * Presentational info about the flyout. */ public static class FlyoutMessage { + @Nullable public Icon senderIcon; @Nullable public Drawable senderAvatar; @Nullable public CharSequence senderName; @Nullable public CharSequence message; @@ -109,16 +100,39 @@ class Bubble implements BubbleViewProvider { private UserHandle mUser; @NonNull private String mPackageName; + @Nullable + private String mTitle; + @Nullable + private Icon mIcon; + private boolean mIsBubble; + private boolean mIsVisuallyInterruptive; + private boolean mIsClearable; + private boolean mShouldSuppressNotificationDot; + private boolean mShouldSuppressNotificationList; + private boolean mShouldSuppressPeek; private int mDesiredHeight; @DimenRes private int mDesiredHeightResId; + /** for logging **/ + @Nullable + private InstanceId mInstanceId; + @Nullable + private String mChannelId; + private int mNotificationId; + private int mAppUid = -1; + + @Nullable + private PendingIntent mIntent; + @Nullable + private PendingIntent mDeleteIntent; + /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, - final int desiredHeight, final int desiredHeightResId) { + final int desiredHeight, final int desiredHeightResId, @Nullable final String title) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); mShortcutInfo = shortcutInfo; @@ -126,8 +140,10 @@ class Bubble implements BubbleViewProvider { mFlags = 0; mUser = shortcutInfo.getUserHandle(); mPackageName = shortcutInfo.getPackage(); + mIcon = shortcutInfo.getIcon(); mDesiredHeight = desiredHeight; mDesiredHeightResId = desiredHeightResId; + mTitle = title; } /** Used in tests when no UI is required. */ @@ -145,12 +161,6 @@ class Bubble implements BubbleViewProvider { return mKey; } - @Nullable - public NotificationEntry getEntry() { - return mEntry; - } - - @NonNull public UserHandle getUser() { return mUser; } @@ -203,14 +213,7 @@ class Bubble implements BubbleViewProvider { @Nullable public String getTitle() { - final CharSequence titleCharSeq; - if (mEntry == null) { - titleCharSeq = null; - } else { - titleCharSeq = mEntry.getSbn().getNotification().extras.getCharSequence( - Notification.EXTRA_TITLE); - } - return titleCharSeq != null ? titleCharSeq.toString() : null; + return mTitle; } /** @@ -331,17 +334,45 @@ class Bubble implements BubbleViewProvider { void setEntry(@NonNull final NotificationEntry entry) { Objects.requireNonNull(entry); Objects.requireNonNull(entry.getSbn()); - mEntry = entry; mLastUpdated = entry.getSbn().getPostTime(); - mFlags = entry.getSbn().getNotification().flags; + mIsBubble = entry.getSbn().getNotification().isBubbleNotification(); mPackageName = entry.getSbn().getPackageName(); mUser = entry.getSbn().getUser(); + mTitle = getTitle(entry); + mIsClearable = entry.isClearable(); + mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot(); + mShouldSuppressNotificationList = entry.shouldSuppressNotificationList(); + mShouldSuppressPeek = entry.shouldSuppressPeek(); + mChannelId = entry.getSbn().getNotification().getChannelId(); + mNotificationId = entry.getSbn().getId(); + mAppUid = entry.getSbn().getUid(); + mInstanceId = entry.getSbn().getInstanceId(); + mFlyoutMessage = BubbleViewInfoTask.extractFlyoutMessage(entry); + mShortcutInfo = (entry.getBubbleMetadata() != null + && entry.getBubbleMetadata().getShortcutId() != null + && entry.getRanking() != null) ? entry.getRanking().getShortcutInfo() : null; + if (entry.getRanking() != null) { + mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive(); + } if (entry.getBubbleMetadata() != null) { + mFlags = entry.getBubbleMetadata().getFlags(); mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight(); mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId(); + mIcon = entry.getBubbleMetadata().getIcon(); + mIntent = entry.getBubbleMetadata().getIntent(); + mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent(); } } + @Nullable + Icon getIcon() { + return mIcon; + } + + boolean isVisuallyInterruptive() { + return mIsVisuallyInterruptive; + } + /** * @return the last time this bubble was updated or accessed, whichever is most recent. */ @@ -364,6 +395,19 @@ class Bubble implements BubbleViewProvider { return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY; } + public InstanceId getInstanceId() { + return mInstanceId; + } + + @Nullable + public String getChannelId() { + return mChannelId; + } + + public int getNotificationId() { + return mNotificationId; + } + /** * Should be invoked whenever a Bubble is accessed (selected while expanded). */ @@ -384,24 +428,19 @@ class Bubble implements BubbleViewProvider { * Whether this notification should be shown in the shade. */ boolean showInShade() { - if (mEntry == null) return false; - return !shouldSuppressNotification() || !mEntry.isClearable(); + return !shouldSuppressNotification() || !mIsClearable; } /** * Sets whether this notification should be suppressed in the shade. */ void setSuppressNotification(boolean suppressNotification) { - if (mEntry == null) return; boolean prevShowInShade = showInShade(); - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - int flags = data.getFlags(); if (suppressNotification) { - flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; } else { - flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; } - data.setFlags(flags); if (showInShade() != prevShowInShade && mSuppressionListener != null) { mSuppressionListener.onBubbleNotificationSuppressionChange(this); @@ -424,9 +463,8 @@ class Bubble implements BubbleViewProvider { */ @Override public boolean showDot() { - if (mEntry == null) return false; return mShowBubbleUpdateDot - && !mEntry.shouldSuppressNotificationDot() + && !mShouldSuppressNotificationDot && !shouldSuppressNotification(); } @@ -434,10 +472,9 @@ class Bubble implements BubbleViewProvider { * Whether the flyout for the bubble should be shown. */ boolean showFlyout() { - if (mEntry == null) return false; - return !mSuppressFlyout && !mEntry.shouldSuppressPeek() + return !mSuppressFlyout && !mShouldSuppressPeek && !shouldSuppressNotification() - && !mEntry.shouldSuppressNotificationList(); + && !mShouldSuppressNotificationList; } /** @@ -480,25 +517,14 @@ class Bubble implements BubbleViewProvider { } } - /** - * Whether shortcut information should be used to populate the bubble. - * <p> - * To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}. - * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}. - */ - boolean usingShortcutInfo() { - return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null - || mShortcutInfo != null; + @Nullable + PendingIntent getBubbleIntent() { + return mIntent; } @Nullable - PendingIntent getBubbleIntent() { - if (mEntry == null) return null; - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - if (data != null) { - return data.getIntent(); - } - return null; + PendingIntent getDeleteIntent() { + return mDeleteIntent; } Intent getSettingsIntent(final Context context) { @@ -514,8 +540,12 @@ class Bubble implements BubbleViewProvider { return intent; } + public int getAppUid() { + return mAppUid; + } + private int getUid(final Context context) { - if (mEntry != null) return mEntry.getSbn().getUid(); + if (mAppUid != -1) return mAppUid; final PackageManager pm = context.getPackageManager(); if (pm == null) return -1; try { @@ -548,24 +578,27 @@ class Bubble implements BubbleViewProvider { } private boolean shouldSuppressNotification() { - if (mEntry == null) return true; - return mEntry.getBubbleMetadata() != null - && mEntry.getBubbleMetadata().isNotificationSuppressed(); + return isEnabled(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); } - boolean shouldAutoExpand() { - if (mEntry == null) return mShouldAutoExpand; - Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); - return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand; + public boolean shouldAutoExpand() { + return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); } void setShouldAutoExpand(boolean shouldAutoExpand) { - mShouldAutoExpand = shouldAutoExpand; + if (shouldAutoExpand) { + enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + } else { + disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + } + } + + public void setIsBubble(final boolean isBubble) { + mIsBubble = isBubble; } public boolean isBubble() { - if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0; - return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0; + return mIsBubble; } public void enable(int option) { @@ -576,6 +609,10 @@ class Bubble implements BubbleViewProvider { mFlags &= ~option; } + public boolean isEnabled(int option) { + return (mFlags & option) != 0; + } + @Override public String toString() { return "Bubble{" + mKey + '}'; @@ -610,34 +647,24 @@ class Bubble implements BubbleViewProvider { @Override public void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index) { - if (this.getEntry() == null - || this.getEntry().getSbn() == null) { - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - null /* package name */, - null /* notification channel */, - 0 /* notification ID */, - 0 /* bubble position */, - bubbleCount, - action, - normalX, - normalY, - false /* unread bubble */, - false /* on-going bubble */, - false /* isAppForeground (unused) */); - } else { - StatusBarNotification notification = this.getEntry().getSbn(); - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - notification.getPackageName(), - notification.getNotification().getChannelId(), - notification.getId(), - index, - bubbleCount, - action, - normalX, - normalY, - this.showInShade(), - false /* isOngoing (unused) */, - false /* isAppForeground (unused) */); - } + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, + mPackageName, + mChannelId, + mNotificationId, + index, + bubbleCount, + action, + normalX, + normalY, + showInShade(), + false /* isOngoing (unused) */, + false /* isAppForeground (unused) */); + } + + @Nullable + private static String getTitle(@NonNull final NotificationEntry e) { + final CharSequence titleCharSeq = e.getSbn().getNotification().extras.getCharSequence( + Notification.EXTRA_TITLE); + return titleCharSeq == null ? null : titleCharSeq.toString(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 873b0785bd7f..c42920965ed3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -67,6 +67,7 @@ import android.util.Log; import android.util.Pair; import android.util.SparseSetArray; import android.view.Display; +import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -394,6 +395,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi : statusBarService; mBubbleScrim = new ScrimView(mContext); + mBubbleScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); mSavedBubbleKeysPerUser = new SparseSetArray<>(); mCurrentUserId = mNotifUserManager.getCurrentUserId(); @@ -505,8 +507,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi addNotifCallback(new NotifCallback() { @Override public void removeNotification(NotificationEntry entry, int reason) { - mNotificationEntryManager.performRemoveNotification(entry.getSbn(), - reason); + mNotificationEntryManager.performRemoveNotification(entry.getSbn(), reason); } @Override @@ -637,8 +638,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mStackView.setExpandListener(mExpandListener); } - mStackView.setUnbubbleConversationCallback(notificationEntry -> - onUserChangedBubble(notificationEntry, false /* shouldBubble */)); + mStackView.setUnbubbleConversationCallback(key -> { + final NotificationEntry entry = + mNotificationEntryManager.getPendingOrActiveNotif(key); + if (entry != null) { + onUserChangedBubble(entry, false /* shouldBubble */); + } + }); } addToWindowManagerMaybe(); @@ -714,6 +720,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * the new params if the stack has been added. */ private void updateWmFlags() { + if (mStackView == null) { + return; + } if (isStackExpanded() && !mImeVisible) { // If we're expanded, and the IME isn't visible, we want to be focusable. This ensures // that any taps within Bubbles (including on the ActivityView) results in Bubbles @@ -725,7 +734,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; } - if (mStackView != null && mAddedToWindowManager) { + if (mAddedToWindowManager) { try { mWindowManager.updateViewLayout(mStackView, mWmLayoutParams); } catch (IllegalArgumentException e) { @@ -910,7 +919,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); bubble.setInflateSynchronously(mInflateSynchronously); bubble.setShouldAutoExpand(true); - bubble.markUpdatedAt(System.currentTimeMillis()); + bubble.markAsAccessedAt(System.currentTimeMillis()); setIsBubble(bubble, true /* isBubble */); } @@ -1021,10 +1030,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @param entry the notification to change bubble state for. * @param shouldBubble whether the notification should show as a bubble or not. */ - public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) { - if (entry == null) { - return; - } + public void onUserChangedBubble(@NonNull final NotificationEntry entry, boolean shouldBubble) { NotificationChannel channel = entry.getChannel(); final String appPkg = entry.getSbn().getPackageName(); final int appUid = entry.getSbn().getUid(); @@ -1100,7 +1106,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleData.removeSuppressedSummary(groupKey); // Remove any associated bubble children with the summary - final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager); for (int i = 0; i < bubbleChildren.size(); i++) { removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED); } @@ -1158,21 +1165,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) { Objects.requireNonNull(b); - if (isBubble) { - b.enable(FLAG_BUBBLE); - } else { - b.disable(FLAG_BUBBLE); - } - if (b.getEntry() != null) { + b.setIsBubble(isBubble); + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(b.getKey()); + if (entry != null) { // Updating the entry to be a bubble will trigger our normal update flow - setIsBubble(b.getEntry(), isBubble, b.shouldAutoExpand()); + setIsBubble(entry, isBubble, b.shouldAutoExpand()); } else if (isBubble) { - // If we have no entry to update, it's a persisted bubble so - // we need to add it to the stack ourselves + // If bubble doesn't exist, it's a persisted bubble so we need to add it to the + // stack ourselves Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */); inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */, !bubble.shouldAutoExpand() /* showInShade */); - } } @@ -1211,6 +1215,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (reason == DISMISS_NOTIF_CANCEL) { bubblesToBeRemovedFromRepository.add(bubble); } + final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif( + bubble.getKey()); if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey()) && (!bubble.showInShade() @@ -1219,60 +1225,65 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble is now gone & the notification is hidden from the shade, so // time to actually remove it for (NotifCallback cb : mCallbacks) { - if (bubble.getEntry() != null) { - cb.removeNotification(bubble.getEntry(), REASON_CANCEL); + if (entry != null) { + cb.removeNotification(entry, REASON_CANCEL); } } } else { if (bubble.isBubble()) { setIsBubble(bubble, false /* isBubble */); } - if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) { - bubble.getEntry().getRow().updateBubbleButton(); + if (entry != null && entry.getRow() != null) { + entry.getRow().updateBubbleButton(); } } } - if (bubble.getEntry() != null) { - final String groupKey = bubble.getEntry().getSbn().getGroupKey(); - if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { + if (entry != null) { + final String groupKey = entry.getSbn().getGroupKey(); + if (mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager).isEmpty()) { // Time to potentially remove the summary for (NotifCallback cb : mCallbacks) { - cb.maybeCancelSummary(bubble.getEntry()); + cb.maybeCancelSummary(entry); } } } } mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository); - if (update.addedBubble != null) { + if (update.addedBubble != null && mStackView != null) { mDataRepository.addBubble(mCurrentUserId, update.addedBubble); mStackView.addBubble(update.addedBubble); - } - if (update.updatedBubble != null) { + if (update.updatedBubble != null && mStackView != null) { mStackView.updateBubble(update.updatedBubble); } // At this point, the correct bubbles are inflated in the stack. // Make sure the order in bubble data is reflected in bubble row. - if (update.orderChanged) { + if (update.orderChanged && mStackView != null) { mDataRepository.addBubbles(mCurrentUserId, update.bubbles); mStackView.updateBubbleOrder(update.bubbles); } - if (update.selectionChanged) { + if (update.selectionChanged && mStackView != null) { mStackView.setSelectedBubble(update.selectedBubble); - if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) { - mNotificationGroupManager.updateSuppression( - update.selectedBubble.getEntry()); + if (update.selectedBubble != null) { + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(update.selectedBubble.getKey()); + if (entry != null) { + mNotificationGroupManager.updateSuppression(entry); + } } } // Expanding? Apply this last. if (update.expandedChanged && update.expanded) { - mStackView.setExpanded(true); + if (mStackView != null) { + mStackView.setExpanded(true); + } } for (NotifCallback cb : mCallbacks) { @@ -1337,7 +1348,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } String groupKey = entry.getSbn().getGroupKey(); - ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager); boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey())); boolean isSummary = entry.getSbn().getNotification().isGroupSummary(); @@ -1357,9 +1369,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // As far as group manager is concerned, once a child is no longer shown // in the shade, it is essentially removed. Bubble bubbleChild = mBubbleData.getAnyBubbleWithkey(child.getKey()); - mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry()); - bubbleChild.setSuppressNotification(true); - bubbleChild.setShowDot(false /* show */); + if (bubbleChild != null) { + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(bubbleChild.getKey()); + if (entry != null) { + mNotificationGroupManager.onEntryRemoved(entry); + } + bubbleChild.setSuppressNotification(true); + bubbleChild.setShowDot(false /* show */); + } } else { // non-bubbled children can be removed for (NotifCallback cb : mCallbacks) { @@ -1378,7 +1396,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** - * Lets any listeners know if bubble state has changed. * Updates the visibility of the bubbles based on current state. * Does not un-bubble, just hides or un-hides. * Updates stack description for TalkBack focus. diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 24d44d5cb291..c8706126c1ad 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -22,7 +22,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.NonNull; -import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.util.Log; @@ -34,6 +33,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController.DismissReason; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.io.FileDescriptor; @@ -256,8 +256,7 @@ public class BubbleData { } mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); - suppressFlyout |= bubble.getEntry() == null - || !bubble.getEntry().getRanking().visuallyInterruptive(); + suppressFlyout |= !bubble.isVisuallyInterruptive(); if (prevBubble == null) { // Create a new bubble @@ -335,13 +334,15 @@ public class BubbleData { * Retrieves any bubbles that are part of the notification group represented by the provided * group key. */ - ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) { + ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey, @NonNull + NotificationEntryManager nem) { ArrayList<Bubble> bubbleChildren = new ArrayList<>(); if (groupKey == null) { return bubbleChildren; } for (Bubble b : mBubbles) { - if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) { + final NotificationEntry entry = nem.getPendingOrActiveNotif(b.getKey()); + if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) { bubbleChildren.add(b); } } @@ -417,7 +418,8 @@ public class BubbleData { if (mBubbles.size() == 1) { // Going to become empty, handle specially. setExpandedInternal(false); - setSelectedBubbleInternal(null); + // Don't use setSelectedBubbleInternal because we don't want to trigger an applyUpdate + mSelectedBubble = null; } if (indexToRemove < mBubbles.size() - 1) { // Removing anything but the last bubble means positions will change. @@ -438,9 +440,7 @@ public class BubbleData { Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } - if (bubbleToRemove.getEntry() != null) { - maybeSendDeleteIntent(reason, bubbleToRemove.getEntry()); - } + maybeSendDeleteIntent(reason, bubbleToRemove); } void overflowBubble(@DismissReason int reason, Bubble bubble) { @@ -610,21 +610,14 @@ public class BubbleData { return true; } - private void maybeSendDeleteIntent(@DismissReason int reason, - @NonNull final NotificationEntry entry) { - if (reason == BubbleController.DISMISS_USER_GESTURE) { - Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata(); - PendingIntent deleteIntent = bubbleMetadata != null - ? bubbleMetadata.getDeleteIntent() - : null; - if (deleteIntent != null) { - try { - deleteIntent.send(); - } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "Failed to send delete intent for bubble with key: " - + entry.getKey()); - } - } + private void maybeSendDeleteIntent(@DismissReason int reason, @NonNull final Bubble bubble) { + if (reason != BubbleController.DISMISS_USER_GESTURE) return; + PendingIntent deleteIntent = bubble.getDeleteIntent(); + if (deleteIntent == null) return; + try { + deleteIntent.send(); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Failed to send delete intent for bubble with key: " + bubble.getKey()); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt index d20f40559b5d..0c25d144938c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt @@ -74,11 +74,15 @@ internal class BubbleDataRepository @Inject constructor( private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> - var shortcutId = b.shortcutInfo?.id - if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId - if (shortcutId == null) return@mapNotNull null - BubbleEntity(userId, b.packageName, shortcutId, b.key, b.rawDesiredHeight, - b.rawDesiredHeightResId) + BubbleEntity( + userId, + b.packageName, + b.shortcutInfo?.id ?: return@mapNotNull null, + b.key, + b.rawDesiredHeight, + b.rawDesiredHeightResId, + b.title + ) } } @@ -159,8 +163,13 @@ internal class BubbleDataRepository @Inject constructor( val bubbles = entities.mapNotNull { entity -> shortcutMap[ShortcutKey(entity.userId, entity.packageName)] ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id } - ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo, entity.desiredHeight, - entity.desiredHeightResId) } + ?.let { shortcutInfo -> Bubble( + entity.key, + shortcutInfo, + entity.desiredHeight, + entity.desiredHeightResId, + entity.title + ) } } uiScope.launch { cb(bubbles) } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index ec875f8f0e1a..959130bbdd0f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -35,6 +35,7 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.SuppressLint; +import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.ActivityView; @@ -126,6 +127,7 @@ public class BubbleExpandedView extends LinearLayout { private BubbleController mBubbleController = Dependency.get(BubbleController.class); private WindowManager mWindowManager; + private ActivityManager mActivityManager; private BubbleStackView mStackView; private View mVirtualImeView; @@ -163,8 +165,13 @@ public class BubbleExpandedView extends LinearLayout { Log.d(TAG, "onActivityViewReady: calling startActivity, " + "bubble=" + getBubbleKey()); } + if (mActivityView == null) { + mBubbleController.removeBubble(getBubbleKey(), + BubbleController.DISMISS_INVALID_INTENT); + return; + } try { - if (!mIsOverflow && mBubble.usingShortcutInfo()) { + if (!mIsOverflow && mBubble.getShortcutInfo() != null) { options.setApplyActivityFlagsForBubbles(true); mActivityView.startShortcutActivity(mBubble.getShortcutInfo(), options, null /* sourceBounds */); @@ -186,6 +193,10 @@ public class BubbleExpandedView extends LinearLayout { } }); mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED; + break; + case ACTIVITY_STARTED: + post(() -> mActivityManager.moveTaskToFront(mTaskId, 0)); + break; } } @@ -247,6 +258,7 @@ public class BubbleExpandedView extends LinearLayout { int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); updateDimensions(); + mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); } void updateDimensions() { @@ -350,7 +362,10 @@ public class BubbleExpandedView extends LinearLayout { // ActivityView's vertical bounds. These events are part of a back gesture, and so they // should not collapse the stack (which all other touches on areas around the AV would // do). - if (motionEvent.getRawY() >= avBounds.top && motionEvent.getRawY() <= avBounds.bottom) { + if (motionEvent.getRawY() >= avBounds.top + && motionEvent.getRawY() <= avBounds.bottom + && (motionEvent.getRawX() < avBounds.left + || motionEvent.getRawX() > avBounds.right)) { return true; } @@ -429,10 +444,10 @@ public class BubbleExpandedView extends LinearLayout { getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; switch (mode) { case Configuration.UI_MODE_NIGHT_NO: - mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_pointer_light)); + mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_light)); break; case Configuration.UI_MODE_NIGHT_YES: - mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_pointer_dark)); + mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_dark)); break; } mPointerView.setBackground(mPointerDrawable); @@ -653,7 +668,7 @@ public class BubbleExpandedView extends LinearLayout { desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight); } float height = Math.min(desiredHeight, getMaxExpandedHeight()); - height = Math.max(height, mIsOverflow? mOverflowHeight : mMinHeight); + height = Math.max(height, mMinHeight); ViewGroup.LayoutParams lp = mActivityView.getLayoutParams(); mNeedsNewHeight = lp.height != height; if (!mKeyboardVisible) { @@ -738,11 +753,7 @@ public class BubbleExpandedView extends LinearLayout { if (mActivityView == null) { return; } - switch (mActivityViewStatus) { - case INITIALIZED: - case ACTIVITY_STARTED: - mActivityView.release(); - } + mActivityView.release(); if (mTaskId != -1) { try { ActivityTaskManager.getService().removeTask(mTaskId); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java index 8c76cda3290f..1fa3aaae5e61 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java @@ -31,6 +31,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.graphics.RectF; +import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.text.TextUtils; import android.view.LayoutInflater; @@ -223,9 +224,10 @@ public class BubbleFlyoutView extends FrameLayout { float[] dotCenter, boolean hideDot) { - if (flyoutMessage.senderAvatar != null && flyoutMessage.isGroupChat) { + final Drawable senderAvatar = flyoutMessage.senderAvatar; + if (senderAvatar != null && flyoutMessage.isGroupChat) { mSenderAvatar.setVisibility(VISIBLE); - mSenderAvatar.setImageDrawable(flyoutMessage.senderAvatar); + mSenderAvatar.setImageDrawable(senderAvatar); } else { mSenderAvatar.setVisibility(GONE); mSenderAvatar.setTranslationX(0); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java index 74231c648f00..a799f2d739e5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java @@ -15,7 +15,8 @@ */ package com.android.systemui.bubbles; -import android.app.Notification; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; @@ -50,15 +51,14 @@ public class BubbleIconFactory extends BaseIconFactory { /** * Returns the drawable that the developer has provided to display in the bubble. */ - Drawable getBubbleDrawable(Context context, ShortcutInfo shortcutInfo, - Notification.BubbleMetadata metadata) { + Drawable getBubbleDrawable(@NonNull final Context context, + @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) { if (shortcutInfo != null) { LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE); int density = context.getResources().getConfiguration().densityDpi; return launcherApps.getShortcutIconDrawable(shortcutInfo, density); } else { - Icon ic = metadata.getIcon(); if (ic != null) { if (ic.getType() == Icon.TYPE_URI || ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java index c5faae0d703e..c1dd8c36ff6f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java @@ -16,8 +16,6 @@ package com.android.systemui.bubbles; -import android.service.notification.StatusBarNotification; - import com.android.internal.logging.UiEventLoggerImpl; /** @@ -32,12 +30,11 @@ public class BubbleLoggerImpl extends UiEventLoggerImpl implements BubbleLogger * @param e UI event */ public void log(Bubble b, UiEventEnum e) { - if (b.getEntry() == null) { + if (b.getInstanceId() == null) { // Added from persistence -- TODO log this with specific event? return; } - StatusBarNotification sbn = b.getEntry().getSbn(); - logWithInstanceId(e, sbn.getUid(), sbn.getPackageName(), sbn.getInstanceId()); + logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java index 0c62e9f9f548..dadcb3a3a7c4 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java @@ -22,9 +22,9 @@ import static android.view.View.GONE; import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE; import android.content.Context; -import android.content.res.TypedArray; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Path; import android.graphics.drawable.AdaptiveIconDrawable; @@ -88,19 +88,23 @@ public class BubbleOverflow implements BubbleViewProvider { false /* attachToRoot */); mOverflowBtn.setContentDescription(mContext.getResources().getString( R.string.bubble_overflow_button_content_description)); + Resources res = mContext.getResources(); - TypedArray ta = mContext.obtainStyledAttributes( - new int[]{android.R.attr.colorBackgroundFloating}); - int bgColor = ta.getColor(0, Color.WHITE /* default */); - ta.recycle(); - + // Set color for button icon and dot TypedValue typedValue = new TypedValue(); mContext.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); int colorAccent = mContext.getColor(typedValue.resourceId); mOverflowBtn.getDrawable().setTint(colorAccent); mDotColor = colorAccent; - ColorDrawable bg = new ColorDrawable(bgColor); + // Set color for button and activity background + ColorDrawable bg = new ColorDrawable(res.getColor(R.color.bubbles_light)); + final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + if (mode == Configuration.UI_MODE_NIGHT_YES) { + bg = new ColorDrawable(res.getColor(R.color.bubbles_dark)); + } + + // Apply icon inset InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), mBitmapSize - mIconBitmapSize /* inset */); AdaptiveIconDrawable adaptiveIconDrawable = new AdaptiveIconDrawable(bg, fg); @@ -110,6 +114,7 @@ public class BubbleOverflow implements BubbleViewProvider { null /* user */, true /* shrinkNonAdaptiveIcons */).icon; + // Get path with dot location float scale = iconFactory.getNormalizer().getScale(mOverflowBtn.getDrawable(), null /* outBounds */, null /* path */, null /* outMaskShape */); float radius = DEFAULT_PATH_SIZE / 2f; @@ -120,14 +125,9 @@ public class BubbleOverflow implements BubbleViewProvider { radius /* pivot y */); mPath.transform(matrix); - mOverflowBtn.setVisibility(GONE); mOverflowBtn.setRenderedBubble(this); } - ImageView getBtn() { - return mOverflowBtn; - } - void setVisible(int visible) { mOverflowBtn.setVisibility(visible); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index b4672c14b49a..b9437078a330 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -100,7 +100,6 @@ public class BubbleOverflowActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.bubble_overflow_activity); - setBackgroundColor(); mEmptyState = findViewById(R.id.bubble_overflow_empty_state); mRecyclerView = findViewById(R.id.bubble_overflow_recycler); @@ -141,34 +140,25 @@ public class BubbleOverflowActivity extends Activity { * Handle theme changes. */ void updateTheme() { - final int mode = - getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + Resources res = getResources(); + final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; switch (mode) { - case Configuration.UI_MODE_NIGHT_NO: - if (DEBUG_OVERFLOW) { - Log.d(TAG, "Set overflow UI to light mode"); - } + case Configuration.UI_MODE_NIGHT_YES: mEmptyStateImage.setImageDrawable( - getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_light)); + res.getDrawable(R.drawable.ic_empty_bubble_overflow_dark)); + findViewById(android.R.id.content) + .setBackgroundColor(res.getColor(R.color.bubbles_dark)); break; - case Configuration.UI_MODE_NIGHT_YES: - if (DEBUG_OVERFLOW) { - Log.d(TAG, "Set overflow UI to dark mode"); - } + + case Configuration.UI_MODE_NIGHT_NO: mEmptyStateImage.setImageDrawable( - getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_dark)); + res.getDrawable(R.drawable.ic_empty_bubble_overflow_light)); + findViewById(android.R.id.content) + .setBackgroundColor(res.getColor(R.color.bubbles_light)); break; } } - void setBackgroundColor() { - final TypedArray ta = getApplicationContext().obtainStyledAttributes( - new int[]{android.R.attr.colorBackgroundFloating}); - int bgColor = ta.getColor(0, Color.WHITE); - ta.recycle(); - findViewById(android.R.id.content).setBackgroundColor(bgColor); - } - void onDataChanged(List<Bubble> bubbles) { mOverflowBubbles.clear(); mOverflowBubbles.addAll(bubbles); @@ -300,9 +290,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V }); // If the bubble was persisted, the entry is null but it should have shortcut info - ShortcutInfo info = b.getEntry() == null - ? b.getShortcutInfo() - : b.getEntry().getRanking().getShortcutInfo(); + ShortcutInfo info = b.getShortcutInfo(); if (info == null) { Log.d(TAG, "ShortcutInfo required to bubble but none found for " + b); } else { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 1437501a9ea1..640475eb1be4 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -49,7 +49,9 @@ import android.graphics.RectF; import android.graphics.Region; import android.graphics.drawable.TransitionDrawable; import android.os.Bundle; +import android.os.Handler; import android.provider.Settings; +import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.Choreographer; import android.view.DisplayCutout; @@ -91,9 +93,7 @@ import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; import com.android.systemui.util.RelativeTouchListener; @@ -145,6 +145,12 @@ public class BubbleStackView extends FrameLayout @VisibleForTesting static final int FLYOUT_HIDE_AFTER = 5000; + /** + * How long to wait to animate the stack temporarily invisible after a drag/flyout hide + * animation ends, if we are in fact temporarily invisible. + */ + private static final int ANIMATE_TEMPORARILY_INVISIBLE_DELAY = 1000; + private static final PhysicsAnimator.SpringConfig FLYOUT_IME_ANIMATION_SPRING_CONFIG = new PhysicsAnimator.SpringConfig( StackAnimationController.IME_ANIMATION_STIFFNESS, @@ -161,6 +167,12 @@ public class BubbleStackView extends FrameLayout SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY); /** + * Handler to use for all delayed animations - this way, we can easily cancel them before + * starting a new animation. + */ + private final Handler mDelayedAnimationHandler = new Handler(); + + /** * Interface to synchronize {@link View} state and the screen. * * {@hide} @@ -275,6 +287,9 @@ public class BubbleStackView extends FrameLayout /** Whether or not the stack is temporarily invisible off the side of the screen. */ private boolean mTemporarilyInvisible = false; + /** Whether we're in the middle of dragging the stack around by touch. */ + private boolean mIsDraggingStack = false; + /** Description of current animation controller state. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Stack view state:"); @@ -288,12 +303,13 @@ public class BubbleStackView extends FrameLayout private BubbleController.BubbleExpandListener mExpandListener; /** Callback to run when we want to unbubble the given notification's conversation. */ - private Consumer<NotificationEntry> mUnbubbleConversationCallback; + private Consumer<String> mUnbubbleConversationCallback; private SysUiState mSysUiState; private boolean mViewUpdatedRequested = false; private boolean mIsExpansionAnimating = false; + private boolean mIsBubbleSwitchAnimating = false; private boolean mShowingDismiss = false; /** The view to desaturate/darken when magneted to the dismiss target. */ @@ -471,6 +487,15 @@ public class BubbleStackView extends FrameLayout private OnClickListener mBubbleClickListener = new OnClickListener() { @Override public void onClick(View view) { + mIsDraggingStack = false; // If the touch ended in a click, we're no longer dragging. + + // Bubble clicks either trigger expansion/collapse or a bubble switch, both of which we + // shouldn't interrupt. These are quick transitions, so it's not worth trying to adjust + // the animations inflight. + if (mIsExpansionAnimating || mIsBubbleSwitchAnimating) { + return; + } + final Bubble clickedBubble = mBubbleData.getBubbleWithView(view); // If the bubble has since left us, ignore the click. @@ -535,6 +560,8 @@ public class BubbleStackView extends FrameLayout mMagneticTarget, mIndividualBubbleMagnetListener); + hideImeFromExpandedBubble(); + // Save the magnetized individual bubble so we can dispatch touch events to it. mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut(); } else { @@ -547,6 +574,12 @@ public class BubbleStackView extends FrameLayout // Also, save the magnetized stack so we can dispatch touch events to it. mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget); mMagnetizedObject.setMagnetListener(mStackMagnetListener); + + mIsDraggingStack = true; + + // Cancel animations to make the stack temporarily invisible, since we're now + // dragging it. + updateTemporarilyInvisibleAnimation(false /* hideImmediately */); } passEventToMagnetizedObject(ev); @@ -608,6 +641,11 @@ public class BubbleStackView extends FrameLayout hideDismissTarget(); } + + mIsDraggingStack = false; + + // Hide the stack after a delay, if needed. + updateTemporarilyInvisibleAnimation(false /* hideImmediately */); } }; @@ -852,14 +890,7 @@ public class BubbleStackView extends FrameLayout (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { mExpandedAnimationController.updateResources(mOrientation, mDisplaySize); mStackAnimationController.updateResources(mOrientation); - - // Reposition & adjust the height for new orientation - if (mIsExpanded) { - mExpandedViewContainer.setTranslationY(getExpandedViewY()); - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().updateView(getLocationOnScreen()); - } - } + mBubbleOverflow.updateDimensions(); // Need to update the padding around the view WindowInsets insets = getRootWindowInsets(); @@ -883,9 +914,15 @@ public class BubbleStackView extends FrameLayout if (mIsExpanded) { // Re-draw bubble row and pointer for new orientation. + beforeExpandedViewAnimation(); + updateOverflowVisibility(); + updatePointerPosition(); mExpandedAnimationController.expandFromStack(() -> { - updatePointerPosition(); + afterExpandedViewAnimation(); } /* after */); + mExpandedViewContainer.setTranslationX(0); + mExpandedViewContainer.setTranslationY(getExpandedViewY()); + mExpandedViewContainer.setAlpha(1f); } if (mVerticalPosPercentBeforeRotation >= 0) { mStackAnimationController.moveStackToSimilarPositionAfterRotation( @@ -951,14 +988,35 @@ public class BubbleStackView extends FrameLayout */ public void setTemporarilyInvisible(boolean invisible) { mTemporarilyInvisible = invisible; - animateTemporarilyInvisible(); + + // If we are animating out, hide immediately if possible so we animate out with the status + // bar. + updateTemporarilyInvisibleAnimation(invisible /* hideImmediately */); } /** - * Animates onto or off the screen depending on whether we're temporarily invisible, and whether - * a flyout is visible. + * Animates the stack to be temporarily invisible, if needed. + * + * If we're currently dragging the stack, or a flyout is visible, the stack will remain visible. + * regardless of the value of {@link #mTemporarilyInvisible}. This method is called on ACTION_UP + * as well as whenever a flyout hides, so we will animate invisible at that point if needed. */ - private void animateTemporarilyInvisible() { + private void updateTemporarilyInvisibleAnimation(boolean hideImmediately) { + removeCallbacks(mAnimateTemporarilyInvisibleImmediate); + + if (mIsDraggingStack) { + // If we're dragging the stack, don't animate it invisible. + return; + } + + final boolean shouldHide = + mTemporarilyInvisible && mFlyout.getVisibility() != View.VISIBLE; + + postDelayed(mAnimateTemporarilyInvisibleImmediate, + shouldHide && !hideImmediately ? ANIMATE_TEMPORARILY_INVISIBLE_DELAY : 0); + } + + private final Runnable mAnimateTemporarilyInvisibleImmediate = () -> { if (mTemporarilyInvisible && mFlyout.getVisibility() != View.VISIBLE) { if (mStackAnimationController.isStackOnLeftSide()) { animate().translationX(-mBubbleSize).start(); @@ -968,7 +1026,7 @@ public class BubbleStackView extends FrameLayout } else { animate().translationX(0).start(); } - } + }; private void setUpManageMenu() { if (mManageMenu != null) { @@ -998,10 +1056,7 @@ public class BubbleStackView extends FrameLayout mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener( view -> { showManageMenu(false /* show */); - final Bubble bubble = mBubbleData.getSelectedBubble(); - if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { - mUnbubbleConversationCallback.accept(bubble.getEntry()); - } + mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey()); }); mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener( @@ -1011,10 +1066,8 @@ public class BubbleStackView extends FrameLayout if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { final Intent intent = bubble.getSettingsIntent(mContext); collapseStack(() -> { - mContext.startActivityAsUser(intent, bubble.getUser()); - logBubbleClickEvent( - bubble, + logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS); }); } @@ -1099,13 +1152,17 @@ public class BubbleStackView extends FrameLayout mBubbleOverflow = new BubbleOverflow(getContext()); mBubbleOverflow.setUpOverflow(mBubbleContainer, this); } else { - mBubbleContainer.removeView(mBubbleOverflow.getBtn()); + mBubbleContainer.removeView(mBubbleOverflow.getIconView()); mBubbleOverflow.setUpOverflow(mBubbleContainer, this); overflowBtnIndex = mBubbleContainer.getChildCount(); } - mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex, + mBubbleContainer.addView(mBubbleOverflow.getIconView(), overflowBtnIndex, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - mBubbleOverflow.getBtn().setOnClickListener(v -> setSelectedBubble(mBubbleOverflow)); + mBubbleOverflow.getIconView().setOnClickListener(v -> { + setSelectedBubble(mBubbleOverflow); + showManageMenu(false); + }); + updateOverflowVisibility(); } /** * Handle theme changes. @@ -1349,7 +1406,7 @@ public class BubbleStackView extends FrameLayout /** Sets the function to call to un-bubble the given conversation. */ public void setUnbubbleConversationCallback( - Consumer<NotificationEntry> unbubbleConversationCallback) { + Consumer<String> unbubbleConversationCallback) { mUnbubbleConversationCallback = unbubbleConversationCallback; } @@ -1515,7 +1572,15 @@ public class BubbleStackView extends FrameLayout // expanded view becomes visible on the screen. See b/126856255 mExpandedViewContainer.setAlpha(0.0f); mSurfaceSynchronizer.syncSurfaceAndRun(() -> { - previouslySelected.setContentVisibility(false); + if (previouslySelected != null) { + previouslySelected.setContentVisibility(false); + } + if (previouslySelected != null && previouslySelected.getExpandedView() != null) { + // Hide the currently expanded bubble's IME if it's visible before switching + // to a new bubble. + previouslySelected.getExpandedView().hideImeIfVisible(); + } + updateExpandedBubble(); requestUpdate(); @@ -1723,6 +1788,8 @@ public class BubbleStackView extends FrameLayout } private void animateExpansion() { + cancelDelayedExpandCollapseSwitchAnimations(); + mIsExpanded = true; hideStackUserEducation(true /* fromExpansion */); beforeExpandedViewAnimation(); @@ -1771,34 +1838,43 @@ public class BubbleStackView extends FrameLayout mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); } - postDelayed(() -> PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) - .spring(AnimatableScaleMatrix.SCALE_X, - AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), - mScaleInSpringConfig) - .spring(AnimatableScaleMatrix.SCALE_Y, - AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), - mScaleInSpringConfig) - .addUpdateListener((target, values) -> { - mExpandedViewContainerMatrix.postTranslate( - mExpandedBubble.getIconView().getTranslationX() - - bubbleWillBeAtX, - 0); - mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); - }) - .withEndActions(() -> { - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); - } - }) - .start(), startDelay); - + mDelayedAnimationHandler.postDelayed(() -> + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .addUpdateListener((target, values) -> { + if (mExpandedBubble.getIconView() == null) { + return; + } + mExpandedViewContainerMatrix.postTranslate( + mExpandedBubble.getIconView().getTranslationX() + - bubbleWillBeAtX, + 0); + mExpandedViewContainer.setAnimationMatrix( + mExpandedViewContainerMatrix); + }) + .withEndActions(() -> { + if (mExpandedBubble != null + && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView() + .setSurfaceZOrderedOnTop(false); + } + }) + .start(), startDelay); } private void animateCollapse() { + cancelDelayedExpandCollapseSwitchAnimations(); + // Hide the menu if it's visible. showManageMenu(false); mIsExpanded = false; + mIsExpansionAnimating = true; mBubbleContainer.cancelAllAnimations(); @@ -1812,14 +1888,16 @@ public class BubbleStackView extends FrameLayout mExpandedBubble.getExpandedView().hideImeIfVisible(); } + // Let the expanded animation controller know that it shouldn't animate child adds/reorders + // since we're about to animate collapsed. + mExpandedAnimationController.notifyPreparingToCollapse(); + final long startDelay = (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f); - postDelayed(() -> mExpandedAnimationController.collapseBackToStack( + mDelayedAnimationHandler.postDelayed(() -> mExpandedAnimationController.collapseBackToStack( mStackAnimationController.getStackPositionAlongNearestHorizontalEdge() /* collapseTo */, - () -> { - mBubbleContainer.setActiveController(mStackAnimationController); - }), startDelay); + () -> mBubbleContainer.setActiveController(mStackAnimationController)), startDelay); // We want to visually collapse into this bubble during the animation. final View expandingFromBubble = mExpandedBubble.getIconView(); @@ -1868,12 +1946,21 @@ public class BubbleStackView extends FrameLayout updateOverflowVisibility(); afterExpandedViewAnimation(); - previouslySelected.setContentVisibility(false); + if (previouslySelected != null) { + previouslySelected.setContentVisibility(false); + } }) .start(); } private void animateSwitchBubbles() { + // If we're no longer expanded, this is meaningless. + if (!mIsExpanded) { + return; + } + + mIsBubbleSwitchAnimating = true; + // The surface contains a screenshot of the animating out bubble, so we just need to animate // it out (and then release the GraphicBuffer). PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel(); @@ -1886,9 +1973,11 @@ public class BubbleStackView extends FrameLayout .withEndActions(this::releaseAnimatingOutBubbleBuffer) .start(); + boolean isOverflow = mExpandedBubble != null + && mExpandedBubble.getKey().equals(BubbleOverflow.KEY); float expandingFromBubbleDestinationX = - mExpandedAnimationController.getBubbleLeft( - mBubbleData.getBubbles().indexOf(mExpandedBubble)); + mExpandedAnimationController.getBubbleLeft(isOverflow ? getBubbleCount() + : mBubbleData.getBubbles().indexOf(mExpandedBubble)); mExpandedViewContainer.setAlpha(1f); mExpandedViewContainer.setVisibility(View.VISIBLE); @@ -1897,8 +1986,9 @@ public class BubbleStackView extends FrameLayout 0f, 0f, expandingFromBubbleDestinationX + mBubbleSize / 2f, getExpandedViewY()); mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); - mExpandedViewContainer.postDelayed(() -> { + mDelayedAnimationHandler.postDelayed(() -> { if (!mIsExpanded) { + mIsBubbleSwitchAnimating = false; return; } @@ -1916,11 +2006,24 @@ public class BubbleStackView extends FrameLayout if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); } + + mIsBubbleSwitchAnimating = false; }) .start(); }, 25); } + /** + * Cancels any delayed steps for expand/collapse and bubble switch animations, and resets the is + * animating flags for those animations. + */ + private void cancelDelayedExpandCollapseSwitchAnimations() { + mDelayedAnimationHandler.removeCallbacksAndMessages(null); + + mIsExpansionAnimating = false; + mIsBubbleSwitchAnimating = false; + } + private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) { if (mExpandListener != null && bubble != null) { mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey()); @@ -2237,7 +2340,9 @@ public class BubbleStackView extends FrameLayout BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE); mFlyout.setVisibility(INVISIBLE); - animateTemporarilyInvisible(); + + // Hide the stack after a delay, if needed. + updateTemporarilyInvisibleAnimation(false /* hideImmediately */); }; mFlyout.setVisibility(INVISIBLE); @@ -2255,7 +2360,7 @@ public class BubbleStackView extends FrameLayout final Runnable expandFlyoutAfterDelay = () -> { mAnimateInFlyout = () -> { mFlyout.setVisibility(VISIBLE); - animateTemporarilyInvisible(); + updateTemporarilyInvisibleAnimation(false /* hideImmediately */); mFlyoutDragDeltaX = mStackAnimationController.isStackOnLeftSide() ? -mFlyout.getWidth() @@ -2404,6 +2509,10 @@ public class BubbleStackView extends FrameLayout .spring(DynamicAnimation.SCALE_Y, 1f) .spring(DynamicAnimation.TRANSLATION_X, targetX) .spring(DynamicAnimation.TRANSLATION_Y, targetY) + .withEndActions(() -> { + View child = mManageMenu.getChildAt(0); + child.requestAccessibilityFocus(); + }) .start(); mManageMenu.setVisibility(View.VISIBLE); @@ -2427,8 +2536,6 @@ public class BubbleStackView extends FrameLayout Log.d(TAG, "updateExpandedBubble()"); } - hideImeFromExpandedBubble(); - mExpandedViewContainer.removeAllViews(); if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { @@ -2649,16 +2756,28 @@ public class BubbleStackView extends FrameLayout /** * Logs the bubble UI event. * - * @param bubble the bubble that is being interacted on. Null value indicates that - * the user interaction is not specific to one bubble. + * @param provider the bubble view provider that is being interacted on. Null value indicates + * that the user interaction is not specific to one bubble. * @param action the user interaction enum. */ - private void logBubbleEvent(@Nullable BubbleViewProvider bubble, int action) { - if (bubble == null) { + private void logBubbleEvent(@Nullable BubbleViewProvider provider, int action) { + if (provider == null || provider.getKey().equals(BubbleOverflow.KEY)) { + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, + mContext.getApplicationInfo().packageName, + provider == null ? null : BubbleOverflow.KEY /* notification channel */, + 0 /* notification ID */, + 0 /* bubble position */, + getBubbleCount(), + action, + getNormalizedXPosition(), + getNormalizedYPosition(), + false /* unread bubble */, + false /* on-going bubble */, + false /* isAppForeground (unused) */); return; } - bubble.logUIEvent(getBubbleCount(), action, getNormalizedXPosition(), - getNormalizedYPosition(), getBubbleIndex(bubble)); + provider.logUIEvent(getBubbleCount(), action, getNormalizedXPosition(), + getNormalizedYPosition(), getBubbleIndex(provider)); } /** @@ -2697,20 +2816,4 @@ public class BubbleStackView extends FrameLayout } return bubbles; } - - /** - * Logs bubble UI click event. - * - * @param bubble the bubble notification entry that user is interacting with. - * @param action the user interaction enum. - */ - private void logBubbleClickEvent(Bubble bubble, int action) { - bubble.logUIEvent( - getBubbleCount(), - action, - getNormalizedXPosition(), - getNormalizedYPosition(), - getBubbleIndex(getExpandedBubble()) - ); - } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index 525d5b56cc8e..3e4ff5262bbd 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -37,8 +37,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.os.Parcelable; -import android.os.UserHandle; -import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; import android.util.PathParser; @@ -53,6 +51,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.lang.ref.WeakReference; import java.util.List; +import java.util.Objects; /** * Simple task to inflate views & load necessary info to display a bubble. @@ -129,35 +128,10 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask @Nullable static BubbleViewInfo populate(Context c, BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) { - final NotificationEntry entry = b.getEntry(); - if (entry == null) { - // populate from ShortcutInfo when NotificationEntry is not available - final ShortcutInfo s = b.getShortcutInfo(); - return populate(c, stackView, iconFactory, skipInflation || b.isInflated(), - s.getPackage(), s.getUserHandle(), s, null); - } - final StatusBarNotification sbn = entry.getSbn(); - final String bubbleShortcutId = entry.getBubbleMetadata().getShortcutId(); - final ShortcutInfo si = bubbleShortcutId == null - ? null : entry.getRanking().getShortcutInfo(); - return populate( - c, stackView, iconFactory, skipInflation || b.isInflated(), - sbn.getPackageName(), sbn.getUser(), si, entry); - } - - private static BubbleViewInfo populate( - @NonNull final Context c, - @NonNull final BubbleStackView stackView, - @NonNull final BubbleIconFactory iconFactory, - final boolean isInflated, - @NonNull final String packageName, - @NonNull final UserHandle user, - @Nullable final ShortcutInfo shortcutInfo, - @Nullable final NotificationEntry entry) { BubbleViewInfo info = new BubbleViewInfo(); // View inflation: only should do this once per bubble - if (!isInflated) { + if (!skipInflation && !b.isInflated()) { LayoutInflater inflater = LayoutInflater.from(c); info.imageView = (BadgedImageView) inflater.inflate( R.layout.bubble_view, stackView, false /* attachToRoot */); @@ -167,8 +141,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask info.expandedView.setStackView(stackView); } - if (shortcutInfo != null) { - info.shortcutInfo = shortcutInfo; + if (b.getShortcutInfo() != null) { + info.shortcutInfo = b.getShortcutInfo(); } // App name & app icon @@ -178,7 +152,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Drawable appIcon; try { appInfo = pm.getApplicationInfo( - packageName, + b.getPackageName(), PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_UNAWARE @@ -186,17 +160,17 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (appInfo != null) { info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); } - appIcon = pm.getApplicationIcon(packageName); - badgedIcon = pm.getUserBadgedIcon(appIcon, user); + appIcon = pm.getApplicationIcon(b.getPackageName()); + badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); } catch (PackageManager.NameNotFoundException exception) { // If we can't find package... don't think we should show the bubble. - Log.w(TAG, "Unable to find package: " + packageName); + Log.w(TAG, "Unable to find package: " + b.getPackageName()); return null; } // Badged bubble image Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, - entry == null ? null : entry.getBubbleMetadata()); + b.getIcon()); if (bubbleDrawable == null) { // Default to app icon bubbleDrawable = appIcon; @@ -222,8 +196,10 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Color.WHITE, WHITE_SCRIM_ALPHA); // Flyout - if (entry != null) { - info.flyoutMessage = extractFlyoutMessage(c, entry); + info.flyoutMessage = b.getFlyoutMessage(); + if (info.flyoutMessage != null) { + info.flyoutMessage.senderAvatar = + loadSenderAvatar(c, info.flyoutMessage.senderIcon); } return info; } @@ -235,8 +211,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask * notification, based on its type. Returns null if there should not be an update message. */ @NonNull - static Bubble.FlyoutMessage extractFlyoutMessage(Context context, - NotificationEntry entry) { + static Bubble.FlyoutMessage extractFlyoutMessage(NotificationEntry entry) { + Objects.requireNonNull(entry); final Notification underlyingNotif = entry.getSbn().getNotification(); final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle(); @@ -264,20 +240,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (latestMessage != null) { bubbleMessage.message = latestMessage.getText(); Person sender = latestMessage.getSenderPerson(); - bubbleMessage.senderName = sender != null - ? sender.getName() - : null; - + bubbleMessage.senderName = sender != null ? sender.getName() : null; bubbleMessage.senderAvatar = null; - if (sender != null && sender.getIcon() != null) { - if (sender.getIcon().getType() == Icon.TYPE_URI - || sender.getIcon().getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { - context.grantUriPermission(context.getPackageName(), - sender.getIcon().getUri(), - Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - bubbleMessage.senderAvatar = sender.getIcon().loadDrawable(context); - } + bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null; return bubbleMessage; } } else if (Notification.InboxStyle.class.equals(style)) { @@ -306,4 +271,15 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask return bubbleMessage; } + + @Nullable + static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { + Objects.requireNonNull(context); + if (icon == null) return null; + if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + context.grantUriPermission(context.getPackageName(), + icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + return icon.loadDrawable(context); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 86fe10dddc2c..8e232520a196 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -92,6 +92,14 @@ public class ExpandedAnimationController private int mScreenOrientation; private boolean mAnimatingExpand = false; + + /** + * Whether we are animating other Bubbles UI elements out in preparation for a call to + * {@link #collapseBackToStack}. If true, we won't animate bubbles in response to adds or + * reorders. + */ + private boolean mPreparingToCollapse = false; + private boolean mAnimatingCollapse = false; private @Nullable Runnable mAfterExpand; private Runnable mAfterCollapse; @@ -150,6 +158,7 @@ public class ExpandedAnimationController */ public void expandFromStack( @Nullable Runnable after, @Nullable Runnable leadBubbleEndAction) { + mPreparingToCollapse = false; mAnimatingCollapse = false; mAnimatingExpand = true; mAfterExpand = after; @@ -165,9 +174,20 @@ public class ExpandedAnimationController expandFromStack(after, null /* leadBubbleEndAction */); } + /** + * Sets that we're animating the stack collapsed, but haven't yet called + * {@link #collapseBackToStack}. This will temporarily suspend animations for bubbles that are + * added or re-ordered, since the upcoming collapse animation will handle positioning those + * bubbles in the collapsed stack. + */ + public void notifyPreparingToCollapse() { + mPreparingToCollapse = true; + } + /** Animate collapsing the bubbles back to their stacked position. */ public void collapseBackToStack(PointF collapsePoint, Runnable after) { mAnimatingExpand = false; + mPreparingToCollapse = false; mAnimatingCollapse = true; mAfterCollapse = after; mCollapsePoint = collapsePoint; @@ -183,12 +203,22 @@ public class ExpandedAnimationController public void updateResources(int orientation, Point displaySize) { mScreenOrientation = orientation; mDisplaySize = displaySize; - if (mLayout != null) { - Resources res = mLayout.getContext().getResources(); - mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); - mStatusBarHeight = res.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); + if (mLayout == null) { + return; } + Resources res = mLayout.getContext().getResources(); + mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); + mStatusBarHeight = res.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); + mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); + + // Includes overflow button. + float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2) + - (mBubblesMaxRendered + 1) * mBubbleSizePx; + mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered; } /** @@ -444,18 +474,7 @@ public class ExpandedAnimationController @Override void onActiveControllerForLayout(PhysicsAnimationLayout layout) { - final Resources res = layout.getResources(); - mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); - mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); - mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size); - mStatusBarHeight = - res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); - mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); - - // Includes overflow button. - float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2) - - (mBubblesMaxRendered + 1) * mBubbleSizePx; - mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered; + updateResources(mScreenOrientation, mDisplaySize); // Ensure that all child views are at 1x scale, and visible, in case they were animating // in. @@ -501,12 +520,18 @@ public class ExpandedAnimationController startOrUpdatePathAnimation(false /* expanding */); } else { child.setTranslationX(getBubbleLeft(index)); - animationForChild(child) - .translationY( - getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ - getExpandedY() /* to */) - .start(); - updateBubblePositions(); + + // If we're preparing to collapse, don't start animations since the collapse animation + // will take over and animate the new bubble into the correct (stacked) position. + if (!mPreparingToCollapse) { + animationForChild(child) + .translationY( + getExpandedY() + - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ + getExpandedY() /* to */) + .start(); + updateBubblePositions(); + } } } @@ -532,12 +557,20 @@ public class ExpandedAnimationController @Override void onChildReordered(View child, int oldIndex, int newIndex) { - updateBubblePositions(); + if (mPreparingToCollapse) { + // If a re-order is received while we're preparing to collapse, ignore it. Once started, + // the collapse animation will animate all of the bubbles to their correct (stacked) + // position. + return; + } - // We expect reordering during collapse, since we'll put the last selected bubble on top. - // Update the collapse animation so they end up in the right stacked positions. if (mAnimatingCollapse) { + // If a re-order is received during collapse, update the animation so that the bubbles + // end up in the correct (stacked) position. startOrUpdatePathAnimation(false /* expanding */); + } else { + // Otherwise, animate the bubbles around to reflect their new order. + updateBubblePositions(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt index 355c4b115c8d..24768cd84a76 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt @@ -24,5 +24,6 @@ data class BubbleEntity( val shortcutId: String, val key: String, val desiredHeight: Int, - @DimenRes val desiredHeightResId: Int + @DimenRes val desiredHeightResId: Int, + val title: String? = null ) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt index a8faf258da07..66fff3386ae1 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt @@ -33,6 +33,7 @@ private const val ATTR_SHORTCUT_ID = "sid" private const val ATTR_KEY = "key" private const val ATTR_DESIRED_HEIGHT = "h" private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid" +private const val ATTR_TITLE = "t" /** * Writes the bubbles in xml format into given output stream. @@ -63,6 +64,7 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { serializer.attribute(null, ATTR_KEY, bubble.key) serializer.attribute(null, ATTR_DESIRED_HEIGHT, bubble.desiredHeight.toString()) serializer.attribute(null, ATTR_DESIRED_HEIGHT_RES_ID, bubble.desiredHeightResId.toString()) + bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) } serializer.endTag(null, TAG_BUBBLE) } catch (e: IOException) { throw RuntimeException(e) @@ -92,7 +94,8 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? { parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null, parser.getAttributeWithName(ATTR_KEY) ?: return null, parser.getAttributeWithName(ATTR_DESIRED_HEIGHT)?.toInt() ?: return null, - parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null + parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null, + parser.getAttributeWithName(ATTR_TITLE) ) } 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 ebdcdccead90..40c8c6bfa9f7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -200,9 +200,9 @@ class ControlsControllerImpl @Inject constructor ( GlobalActionsDialog.PREFS_CONTROLS_FILE, Context.MODE_PRIVATE) val completedSeedingPackageSet = prefs.getStringSet( GlobalActionsDialog.PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>()) - val favoritePackageSet = favoriteComponentSet.map { it.packageName } + val servicePackageSet = serviceInfoSet.map { it.packageName } prefs.edit().putStringSet(GlobalActionsDialog.PREFS_CONTROLS_SEEDING_COMPLETED, - completedSeedingPackageSet.intersect(favoritePackageSet)).apply() + completedSeedingPackageSet.intersect(servicePackageSet)).apply() var changed = false favoriteComponentSet.subtract(serviceInfoSet).forEach { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 26124f7f3285..05433197799e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -238,7 +238,7 @@ internal class ControlHolder( updateFavorite(!favorite.isChecked) favoriteCallback(wrapper.controlId, favorite.isChecked) } - applyRenderInfo(renderInfo) + applyRenderInfo(renderInfo, wrapper.deviceType) } override fun updateFavorite(favorite: Boolean) { @@ -254,12 +254,16 @@ internal class ControlHolder( return RenderInfo.lookup(itemView.context, component, deviceType) } - private fun applyRenderInfo(ri: RenderInfo) { + private fun applyRenderInfo(ri: RenderInfo, @DeviceTypes.DeviceType deviceType: Int) { val context = itemView.context val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) icon.setImageDrawable(ri.icon) - icon.setImageTintList(fg) + + // Do not color app icons + if (deviceType != DeviceTypes.TYPE_ROUTINE) { + icon.setImageTintList(fg) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 496b21b612fe..4884781c64de 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -79,6 +79,7 @@ class ControlsFavoritingActivity @Inject constructor( private lateinit var structurePager: ViewPager2 private lateinit var statusText: TextView private lateinit var titleView: TextView + private lateinit var subtitleView: TextView private lateinit var pageIndicator: ManagementPageIndicator private var mTooltipManager: TooltipManager? = null private lateinit var doneButton: View @@ -165,7 +166,12 @@ class ControlsFavoritingActivity @Inject constructor( structurePager.adapter = StructureAdapter(listOfStructures) structurePager.setCurrentItem(structureIndex) if (error) { - statusText.text = resources.getText(R.string.controls_favorite_load_error) + statusText.text = resources.getString(R.string.controls_favorite_load_error, + appName ?: "") + subtitleView.visibility = View.GONE + } else if (listOfStructures.isEmpty()) { + statusText.text = resources.getString(R.string.controls_favorite_load_none) + subtitleView.visibility = View.GONE } else { statusText.visibility = View.GONE } @@ -266,8 +272,9 @@ class ControlsFavoritingActivity @Inject constructor( titleView = requireViewById<TextView>(R.id.title).apply { text = title } - requireViewById<TextView>(R.id.subtitle).text = - resources.getText(R.string.controls_favorite_subtitle) + subtitleView = requireViewById<TextView>(R.id.subtitle).apply { + text = resources.getText(R.string.controls_favorite_subtitle) + } structurePager = requireViewById<ViewPager2>(R.id.structure_pager) structurePager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 353367ead7e6..e8530272a5c8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -89,6 +89,7 @@ class ControlViewHolder( return when { status != Control.STATUS_OK -> StatusBehavior::class deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class + template == ControlTemplate.NO_TEMPLATE -> TouchBehavior::class template is ToggleTemplate -> ToggleBehavior::class template is StatelessTemplate -> TouchBehavior::class template is ToggleRangeTemplate -> ToggleRangeBehavior::class @@ -235,7 +236,10 @@ class ControlViewHolder( controlsController.action(cws.componentName, cws.ci, action) } - fun usePanel(): Boolean = deviceType in ControlViewHolder.FORCE_PANEL_DEVICES + fun usePanel(): Boolean { + return deviceType in ControlViewHolder.FORCE_PANEL_DEVICES || + controlTemplate == ControlTemplate.NO_TEMPLATE + } fun bindBehavior( existingBehavior: Behavior?, @@ -270,7 +274,6 @@ class ControlViewHolder( val ri = RenderInfo.lookup(context, cws.componentName, deviceTypeOrError, offset) val fg = context.resources.getColorStateList(ri.foreground, context.theme) val newText = nextStatusText - nextStatusText = "" val control = cws.control var shouldAnimate = animated @@ -293,10 +296,8 @@ class ControlViewHolder( if (immediately) { status.alpha = STATUS_ALPHA_ENABLED status.text = text - nextStatusText = "" - } else { - nextStatusText = text } + nextStatusText = text } private fun animateBackgroundChange( diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 0f5aef7eeec1..1eb7e2168a6a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -103,6 +103,22 @@ class ControlsUiControllerImpl @Inject constructor ( private lateinit var dismissGlobalActions: Runnable private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow) + private val collator = Collator.getInstance(context.resources.configuration.locales[0]) + private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) { + it.getTitle() + } + + private val onSeedingComplete = Consumer<Boolean> { + accepted -> + if (accepted) { + selectedStructure = controlsController.get().getFavorites().maxBy { + it.controls.size + } ?: EMPTY_STRUCTURE + updatePreferences(selectedStructure) + } + reload(parent) + } + override val available: Boolean get() = controlsController.get().available @@ -113,22 +129,13 @@ class ControlsUiControllerImpl @Inject constructor ( ): ControlsListingController.ControlsListingCallback { return object : ControlsListingController.ControlsListingCallback { override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { - bgExecutor.execute { - val collator = Collator.getInstance(context.resources.configuration.locales[0]) - val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) { - it.loadLabel() - } - - val mList = serviceInfos.toMutableList() - mList.sortWith(localeComparator) - lastItems = mList.map { - SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName) - } - uiExecutor.execute { - parent.removeAllViews() - if (lastItems.size > 0) { - onResult(lastItems) - } + val lastItems = serviceInfos.map { + SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName) + } + uiExecutor.execute { + parent.removeAllViews() + if (lastItems.size > 0) { + onResult(lastItems) } } } @@ -144,8 +151,7 @@ class ControlsUiControllerImpl @Inject constructor ( allStructures = controlsController.get().getFavorites() selectedStructure = loadPreference(allStructures) - val cb = Consumer<Boolean> { _ -> reload(parent) } - if (controlsController.get().addSeedingFavoritesCallback(cb)) { + if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) { listingCallback = createCallback(::showSeedingView) } else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) { // only show initial view if there are really no favorites across any structure @@ -309,9 +315,12 @@ class ControlsUiControllerImpl @Inject constructor ( } val itemsByComponent = items.associateBy { it.componentName } - val itemsWithStructure = allStructures.mapNotNull { + val itemsWithStructure = mutableListOf<SelectionItem>() + allStructures.mapNotNullTo(itemsWithStructure) { itemsByComponent.get(it.componentName)?.copy(structure = it.structure) } + itemsWithStructure.sortWith(localeComparator) + val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0] var adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply { @@ -441,6 +450,7 @@ class ControlsUiControllerImpl @Inject constructor ( } private fun updatePreferences(si: StructureInfo) { + if (si == EMPTY_STRUCTURE) return sharedPreferences.edit() .putString(PREF_COMPONENT, si.componentName.flattenToString()) .putString(PREF_STRUCTURE, si.structure.toString()) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt index 48f945874135..9dd0f534e3a8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt @@ -21,6 +21,7 @@ import android.graphics.drawable.LayerDrawable import android.view.View import android.service.controls.Control import android.service.controls.templates.ControlTemplate +import android.service.controls.templates.StatelessTemplate import com.android.systemui.R import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL @@ -35,24 +36,44 @@ class TouchBehavior : Behavior { lateinit var template: ControlTemplate lateinit var control: Control lateinit var cvh: ControlViewHolder + private var statelessTouch = false + private var lastColorOffset = 0 + private val enabled: Boolean + get() = if (lastColorOffset > 0 || statelessTouch) true else false + + companion object { + const val STATELESS_ENABLE_TIMEOUT_IN_MILLIS = 3000L + } override fun initialize(cvh: ControlViewHolder) { this.cvh = cvh cvh.layout.setOnClickListener(View.OnClickListener() { cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control) + + // StatelessTemplates have no state, with no way to discern between enabled and + // disabled. Render an enabled state for a few moments to let the user know the + // action is in progress. + if (template is StatelessTemplate) { + statelessTouch = true + cvh.applyRenderInfo(enabled, lastColorOffset) + cvh.uiExecutor.executeDelayed({ + statelessTouch = false + cvh.applyRenderInfo(enabled, lastColorOffset) + }, STATELESS_ENABLE_TIMEOUT_IN_MILLIS) + } }) } override fun bind(cws: ControlWithState, colorOffset: Int) { this.control = cws.control!! + lastColorOffset = colorOffset cvh.setStatusText(control.getStatusText()) template = control.getControlTemplate() val ld = cvh.layout.getBackground() as LayerDrawable clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) - val enabled = if (colorOffset > 0) true else false clipLayer.setLevel(if (enabled) MAX_LEVEL else MIN_LEVEL) cvh.applyRenderInfo(enabled, colorOffset) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 8368b2c1ae86..1f30305ab169 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -25,6 +25,7 @@ import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.display.NightDisplayListener; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; import android.os.ServiceManager; import android.util.DisplayMetrics; import android.view.Choreographer; @@ -39,9 +40,12 @@ import com.android.internal.util.NotificationMessagingUtil; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.Prefs; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.AlwaysOnDisplayPolicy; +import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.PluginInitializerImpl; import com.android.systemui.shared.plugins.PluginManager; @@ -178,6 +182,21 @@ public class DependencyProvider { return ActivityManagerWrapper.getInstance(); } + /** Provides and initializes the {#link BroadcastDispatcher} for SystemUI */ + @Singleton + @Provides + public BroadcastDispatcher providesBroadcastDispatcher( + Context context, + @Background Looper backgroundLooper, + DumpManager dumpManager, + BroadcastDispatcherLogger logger + ) { + BroadcastDispatcher bD = + new BroadcastDispatcher(context, backgroundLooper, dumpManager, logger); + bD.initialize(); + return bD; + } + @Singleton @Provides public DevicePolicyManagerWrapper provideDevicePolicyManagerWrapper() { diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index bc95a2514c0b..b520717ee27f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; @@ -247,7 +248,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final Executor mBackgroundExecutor; private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>(); private Optional<ControlsController> mControlsControllerOptional; - private SharedPreferences mControlsPreferences; private final RingerModeTracker mRingerModeTracker; private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms private Handler mMainHandler; @@ -396,21 +396,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, controlsComponent.getControlsListingController().get() .addCallback(list -> { mControlsServiceInfos = list; - // This callback may occur after the dialog has been shown. - // If so, add controls into the already visible space - if (mDialog != null && !mDialog.isShowingControls() - && shouldShowControls()) { - mDialog.showControls(mControlsUiControllerOptional.get()); + // This callback may occur after the dialog has been shown. If so, add + // controls into the already visible space or show the lock msg if needed. + if (mDialog != null) { + if (!mDialog.isShowingControls() && shouldShowControls()) { + mDialog.showControls(mControlsUiControllerOptional.get()); + } else if (shouldShowLockMessage()) { + mDialog.showLockMessage(); + } } }); } - // 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( @@ -444,19 +441,22 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, Collections.emptySet()); List<ComponentName> componentsToSeed = new ArrayList<>(); - for (ControlsServiceInfo info : mControlsServiceInfos) { - String pkg = info.componentName.getPackageName(); - if (seededPackages.contains(pkg) - || mControlsControllerOptional.get().countFavoritesForComponent( - info.componentName) > 0) { - continue; - } - - for (int i = 0; i < Math.min(SEEDING_MAX, preferredControlsPackages.length); i++) { - if (pkg.equals(preferredControlsPackages[i])) { - componentsToSeed.add(info.componentName); + for (int i = 0; i < Math.min(SEEDING_MAX, preferredControlsPackages.length); i++) { + String pkg = preferredControlsPackages[i]; + for (ControlsServiceInfo info : mControlsServiceInfos) { + if (!pkg.equals(info.componentName.getPackageName())) continue; + if (seededPackages.contains(pkg)) { + break; + } else if (mControlsControllerOptional.get() + .countFavoritesForComponent(info.componentName) > 0) { + // When there are existing controls but no saved preference, assume it + // is out of sync, perhaps through a device restore, and update the + // preference + addPackageToSeededSet(prefs, pkg); break; } + componentsToSeed.add(info.componentName); + break; } } @@ -466,16 +466,20 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, componentsToSeed, (response) -> { Log.d(TAG, "Controls seeded: " + response); - Set<String> completedPkgs = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, - new HashSet<String>()); if (response.getAccepted()) { - completedPkgs.add(response.getPackageName()); - prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, - completedPkgs).apply(); + addPackageToSeededSet(prefs, response.getPackageName()); } }); } + private void addPackageToSeededSet(SharedPreferences prefs, String pkg) { + Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, + Collections.emptySet()); + Set<String> updatedPkgs = new HashSet<>(seededPackages); + updatedPkgs.add(pkg); + prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply(); + } + /** * Show the global actions dialog (creating if necessary) * @@ -700,9 +704,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mStatusBarService, mNotificationShadeWindowController, controlsAvailable(), uiController, mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter); - boolean walletViewAvailable = walletViewController != null - && walletViewController.getPanelContent() != null; - if (shouldShowLockMessage(walletViewAvailable)) { + + if (shouldShowLockMessage()) { dialog.showLockMessage(); } dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. @@ -2591,7 +2594,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private boolean shouldShowControls() { return (mKeyguardStateController.isUnlocked() || mShowLockScreenCardsAndControls) - && controlsAvailable(); + && controlsAvailable() + && mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id) + != STRONG_AUTH_REQUIRED_AFTER_BOOT; } private boolean controlsAvailable() { @@ -2601,10 +2606,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, && !mControlsServiceInfos.isEmpty(); } - private boolean shouldShowLockMessage(boolean walletViewAvailable) { + private boolean walletViewAvailable() { + GlobalActionsPanelPlugin.PanelViewController walletViewController = + getWalletViewController(); + return walletViewController != null && walletViewController.getPanelContent() != null; + } + + private boolean shouldShowLockMessage() { + boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id) + == STRONG_AUTH_REQUIRED_AFTER_BOOT; return !mKeyguardStateController.isUnlocked() - && !mShowLockScreenCardsAndControls - && (controlsAvailable() || walletViewAvailable); + && (!mShowLockScreenCardsAndControls || isLockedAfterBoot) + && (controlsAvailable() || walletViewAvailable()); } private void onPowerMenuLockScreenSettingsChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index b26dc5f91245..53251ed4362d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -654,7 +654,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { @Override public void onBouncerVisiblityChanged(boolean shown) { synchronized (KeyguardViewMediator.this) { - adjustStatusBarLocked(shown); + adjustStatusBarLocked(shown, false); } } @@ -2003,10 +2003,12 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { } private void adjustStatusBarLocked() { - adjustStatusBarLocked(false /* forceHideHomeRecentsButtons */); + adjustStatusBarLocked(false /* forceHideHomeRecentsButtons */, + false /* forceClearFlags */); } - private void adjustStatusBarLocked(boolean forceHideHomeRecentsButtons) { + private void adjustStatusBarLocked(boolean forceHideHomeRecentsButtons, + boolean forceClearFlags) { if (mStatusBarManager == null) { mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE); @@ -2018,6 +2020,13 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { // Disable aspects of the system/status/navigation bars that must not be re-enabled by // windows that appear on top, ever int flags = StatusBarManager.DISABLE_NONE; + + // TODO (b/155663717) After restart, status bar will not properly hide home button + // unless disable is called to show un-hide it once first + if (forceClearFlags) { + mStatusBarManager.disable(flags); + } + if (forceHideHomeRecentsButtons || isShowingAndNotOccluded()) { if (!mShowHomeOverLockscreen || !mInGestureNavigationMode) { flags |= StatusBarManager.DISABLE_HOME; @@ -2141,6 +2150,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { public void onBootCompleted() { synchronized (this) { mBootCompleted = true; + adjustStatusBarLocked(false, true); if (mBootSendUserPresent) { sendUserPresentBroadcast(); } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java new file mode 100644 index 000000000000..7d1f1c2709fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BroadcastDispatcherLog.java @@ -0,0 +1,33 @@ +/* + * 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.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for BroadcastDispatcher-related messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface BroadcastDispatcherLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 9c89fee5cba1..16f76deca6c6 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -68,7 +68,7 @@ public class LogModule { public static LogBuffer provideNotificationSectionLogBuffer( LogcatEchoTracker bufferFilter, DumpManager dumpManager) { - LogBuffer buffer = new LogBuffer("NotifSectionLog", 500, 10, bufferFilter); + LogBuffer buffer = new LogBuffer("NotifSectionLog", 1000, 10, bufferFilter); buffer.attach(dumpManager); return buffer; } @@ -97,6 +97,18 @@ public class LogModule { return buffer; } + /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */ + @Provides + @Singleton + @BroadcastDispatcherLog + public static LogBuffer provideBroadcastDispatcherLogBuffer( + LogcatEchoTracker bufferFilter, + DumpManager dumpManager) { + LogBuffer buffer = new LogBuffer("BroadcastDispatcherLog", 500, 10, bufferFilter); + buffer.attach(dumpManager); + return buffer; + } + /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ @Provides @Singleton diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index f039fc2d84ae..2df0507a8864 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -35,7 +35,6 @@ import android.util.Log; import android.view.View; import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.SeekBar; import android.widget.TextView; import androidx.annotation.NonNull; @@ -97,13 +96,13 @@ public class MediaControlPanel { */ @Inject public MediaControlPanel(Context context, @Background Executor backgroundExecutor, - ActivityStarter activityStarter, MediaHostStatesManager mediaHostStatesManager, + ActivityStarter activityStarter, MediaViewController mediaViewController, SeekBarViewModel seekBarViewModel) { mContext = context; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; mSeekBarViewModel = seekBarViewModel; - mMediaViewController = new MediaViewController(context, mediaHostStatesManager); + mMediaViewController = mediaViewController; loadDimens(); } @@ -165,9 +164,7 @@ public class MediaControlPanel { TransitionLayout player = vh.getPlayer(); mSeekBarObserver = new SeekBarObserver(vh); mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver); - SeekBar bar = vh.getSeekBar(); - bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener()); - bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener()); + mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar()); mMediaViewController.attach(player); } @@ -260,16 +257,18 @@ public class MediaControlPanel { rect.setColor(Color.TRANSPARENT); final MediaDeviceData device = data.getDevice(); + int seamlessId = mViewHolder.getSeamless().getId(); if (device != null && !device.getEnabled()) { mViewHolder.getSeamless().setEnabled(false); - // TODO(b/156875717): setEnabled should cause the alpha to change. - mViewHolder.getSeamless().setAlpha(0.38f); + expandedSet.setAlpha(seamlessId, 0.38f); + collapsedSet.setAlpha(seamlessId, 0.38f); iconView.setImageResource(R.drawable.ic_hardware_speaker); iconView.setVisibility(View.VISIBLE); deviceName.setText(R.string.media_seamless_remote_device); } else if (device != null) { mViewHolder.getSeamless().setEnabled(true); - mViewHolder.getSeamless().setAlpha(1f); + expandedSet.setAlpha(seamlessId, 1.0f); + collapsedSet.setAlpha(seamlessId, 1.0f); Drawable icon = device.getIcon(); iconView.setVisibility(View.VISIBLE); @@ -285,7 +284,8 @@ public class MediaControlPanel { // Reset to default Log.w(TAG, "device is null. Not binding output chip."); mViewHolder.getSeamless().setEnabled(true); - mViewHolder.getSeamless().setAlpha(1f); + expandedSet.setAlpha(seamlessId, 1.0f); + collapsedSet.setAlpha(seamlessId, 1.0f); iconView.setVisibility(View.GONE); deviceName.setText(com.android.internal.R.string.ext_media_seamless_action); } @@ -302,11 +302,14 @@ public class MediaControlPanel { button.setContentDescription(mediaAction.getContentDescription()); Runnable action = mediaAction.getAction(); - button.setOnClickListener(v -> { - if (action != null) { + if (action == null) { + button.setEnabled(false); + } else { + button.setEnabled(true); + button.setOnClickListener(v -> { action.run(); - } - }); + }); + } boolean visibleInCompat = actionsWhenCollapsed.contains(i); setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat); setVisibleAndAlpha(expandedSet, actionId, true /*visible */); @@ -363,14 +366,6 @@ public class MediaControlPanel { } /** - * Return the token for the current media session - * @return the token - */ - public MediaSession.Token getMediaSessionToken() { - return mToken; - } - - /** * Get the current media controller * @return the controller */ @@ -379,25 +374,6 @@ public class MediaControlPanel { } /** - * Get the name of the package associated with the current media controller - * @return the package name, or null if no controller - */ - public String getMediaPlayerPackage() { - if (mController == null) { - return null; - } - return mController.getPackageName(); - } - - /** - * Check whether this player has an attached media session. - * @return whether there is a controller with a current media session. - */ - public boolean hasMediaSession() { - return mController != null && mController.getPlaybackState() != null; - } - - /** * Check whether the media controlled by this player is currently playing * @return whether it is playing, or false if no controller information */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 5d28178a3b1b..0b0ffcede3af 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -25,19 +25,65 @@ import android.media.session.MediaSession data class MediaData( val initialized: Boolean = false, val backgroundColor: Int, + /** + * App name that will be displayed on the player. + */ val app: String?, + /** + * Icon shown on player, close to app name. + */ val appIcon: Drawable?, + /** + * Artist name. + */ val artist: CharSequence?, + /** + * Song name. + */ val song: CharSequence?, + /** + * Album artwork. + */ val artwork: Icon?, + /** + * List of actions that can be performed on the player: prev, next, play, pause, etc. + */ val actions: List<MediaAction>, + /** + * Same as above, but shown on smaller versions of the player, like in QQS or keyguard. + */ val actionsToShowInCompact: List<Int>, + /** + * Package name of the app that's posting the media. + */ val packageName: String, + /** + * Unique media session identifier. + */ val token: MediaSession.Token?, + /** + * Action to perform when the player is tapped. + * This is unrelated to {@link #actions}. + */ val clickIntent: PendingIntent?, + /** + * Where the media is playing: phone, headphones, ear buds, remote session. + */ val device: MediaDeviceData?, + /** + * When active, a player will be displayed on keyguard and quick-quick settings. + * This is unrelated to the stream being playing or not, a player will not be active if + * timed out, or in resumption mode. + */ + var active: Boolean, + /** + * Action that should be performed to restart a non active session. + */ var resumeAction: Runnable?, - val notificationKey: String = "INVALID", + /** + * Notification key for cancelling a media player after a timeout (when not using resumption.) + */ + val notificationKey: String? = null, var hasCheckedForResume: Boolean = false ) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 094c5bef3c18..5300795189de 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -18,8 +18,11 @@ package com.android.systemui.media import android.app.Notification import android.app.PendingIntent +import android.content.BroadcastReceiver import android.content.ContentResolver import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color @@ -30,11 +33,13 @@ import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaSession import android.net.Uri +import android.os.UserHandle import android.service.notification.StatusBarNotification import android.text.TextUtils import android.util.Log import com.android.internal.graphics.ColorUtils import com.android.systemui.R +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.NotificationMediaManager @@ -62,7 +67,7 @@ private const val LUMINOSITY_THRESHOLD = 0.05f private const val SATURATION_MULTIPLIER = 0.8f private val LOADING = MediaData(false, 0, null, null, null, null, null, - emptyList(), emptyList(), "INVALID", null, null, null, null) + emptyList(), emptyList(), "INVALID", null, null, null, true, null) fun isMediaNotification(sbn: StatusBarNotification): Boolean { if (!sbn.notification.hasMediaSession()) { @@ -83,22 +88,49 @@ fun isMediaNotification(sbn: StatusBarNotification): Boolean { class MediaDataManager @Inject constructor( private val context: Context, private val mediaControllerFactory: MediaControllerFactory, - private val mediaTimeoutListener: MediaTimeoutListener, private val notificationEntryManager: NotificationEntryManager, - private val mediaResumeListener: MediaResumeListener, @Background private val backgroundExecutor: Executor, - @Main private val foregroundExecutor: Executor + @Main private val foregroundExecutor: Executor, + broadcastDispatcher: BroadcastDispatcher, + mediaTimeoutListener: MediaTimeoutListener, + mediaResumeListener: MediaResumeListener ) { private val listeners: MutableSet<Listener> = mutableSetOf() private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() private val useMediaResumption: Boolean = Utils.useMediaResumption(context) + private val userChangeReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (Intent.ACTION_USER_SWITCHED == intent.action) { + // Remove all controls, regardless of state + clearData() + } + } + } + + private val appChangeReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + when (intent.action) { + Intent.ACTION_PACKAGES_SUSPENDED -> { + val packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST) + packages?.forEach { + removeAllForPackage(it) + } + } + Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_RESTARTED -> { + intent.data?.encodedSchemeSpecificPart?.let { + removeAllForPackage(it) + } + } + } + } + } + init { mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean -> setTimedOut(token, timedOut) } addListener(mediaTimeoutListener) - if (useMediaResumption) { mediaResumeListener.addTrackToResumeCallback = { desc: MediaDescription, resumeAction: Runnable, token: MediaSession.Token, appName: String, @@ -111,6 +143,20 @@ class MediaDataManager @Inject constructor( } addListener(mediaResumeListener) } + + val userFilter = IntentFilter(Intent.ACTION_USER_SWITCHED) + broadcastDispatcher.registerReceiver(userChangeReceiver, userFilter, null, UserHandle.ALL) + + val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED) + broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL) + + val uninstallFilter = IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_REMOVED) + addAction(Intent.ACTION_PACKAGE_RESTARTED) + addDataScheme("package") + } + // BroadcastDispatcher does not allow filters with data schemes + context.registerReceiver(appChangeReceiver, uninstallFilter) } fun onNotificationAdded(key: String, sbn: StatusBarNotification) { @@ -131,6 +177,29 @@ class MediaDataManager @Inject constructor( } } + private fun clearData() { + // Called on user change. Remove all current MediaData objects and inform listeners + val listenersCopy = listeners.toSet() + mediaEntries.forEach { + listenersCopy.forEach { listener -> + listener.onMediaDataRemoved(it.key) + } + } + mediaEntries.clear() + } + + private fun removeAllForPackage(packageName: String) { + Assert.isMainThread() + val listenersCopy = listeners.toSet() + val toRemove = mediaEntries.filter { it.value.packageName == packageName } + toRemove.forEach { + mediaEntries.remove(it.key) + listenersCopy.forEach { listener -> + listener.onMediaDataRemoved(it.key) + } + } + } + private fun addResumptionControls( desc: MediaDescription, action: Runnable, @@ -145,7 +214,7 @@ class MediaDataManager @Inject constructor( mediaEntries.put(packageName, resumeData) } backgroundExecutor.execute { - loadMediaDataInBg(desc, action, token, appName, appIntent, packageName) + loadMediaDataInBgForResumption(desc, action, token, appName, appIntent, packageName) } } @@ -185,16 +254,21 @@ class MediaDataManager @Inject constructor( fun removeListener(listener: Listener) = listeners.remove(listener) private fun setTimedOut(token: String, timedOut: Boolean) { - if (!timedOut) { - return - } mediaEntries[token]?.let { - notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */, - UNDEFINED_DISMISS_REASON) + if (Utils.useMediaResumption(context)) { + if (it.active == !timedOut) { + return + } + it.active = !timedOut + onMediaDataLoaded(token, token, it) + } else if (timedOut) { + notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */, + UNDEFINED_DISMISS_REASON) + } } } - private fun loadMediaDataInBg( + private fun loadMediaDataInBgForResumption( desc: MediaDescription, resumeAction: Runnable, token: MediaSession.Token, @@ -202,11 +276,6 @@ class MediaDataManager @Inject constructor( appIntent: PendingIntent, packageName: String ) { - if (resumeAction == null) { - Log.e(TAG, "Resume action cannot be null") - return - } - if (TextUtils.isEmpty(desc.title)) { Log.e(TAG, "Description incomplete") return @@ -228,8 +297,9 @@ class MediaDataManager @Inject constructor( val mediaAction = getResumeMediaAction(resumeAction) foregroundExecutor.execute { onMediaDataLoaded(packageName, null, MediaData(true, Color.DKGRAY, appName, - null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), - packageName, token, appIntent, null, resumeAction, packageName)) + null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), + packageName, token, appIntent, device = null, active = false, + resumeAction = resumeAction)) } } @@ -337,15 +407,20 @@ class MediaDataManager @Inject constructor( actionsToShowCollapsed.remove(index) continue } + val runnable = if (action.actionIntent != null) { + Runnable { + try { + action.actionIntent.send() + } catch (e: PendingIntent.CanceledException) { + Log.d(TAG, "Intent canceled", e) + } + } + } else { + null + } val mediaAction = MediaAction( action.getIcon().loadDrawable(packageContext), - Runnable { - try { - action.actionIntent.send() - } catch (e: PendingIntent.CanceledException) { - Log.d(TAG, "Intent canceled", e) - } - }, + runnable, action.title) actionIcons.add(mediaAction) } @@ -355,7 +430,8 @@ class MediaDataManager @Inject constructor( foregroundExecutor.execute { onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token, - notif.contentIntent, null, resumeAction, key)) + notif.contentIntent, null, active = true, resumeAction = resumeAction, + notificationKey = key)) } } @@ -453,13 +529,13 @@ class MediaDataManager @Inject constructor( /** * Are there any media notifications active? */ - fun hasActiveMedia() = mediaEntries.any({ isActive(it.value) }) + fun hasActiveMedia() = mediaEntries.any { it.value.active } - fun isActive(data: MediaData): Boolean { - if (data.token == null) { + fun isActive(token: MediaSession.Token?): Boolean { + if (token == null) { return false } - val controller = mediaControllerFactory.create(data.token) + val controller = mediaControllerFactory.create(token) val state = controller?.playbackState?.state return state != null && NotificationMediaManager.isActiveState(state) } @@ -467,7 +543,7 @@ class MediaDataManager @Inject constructor( /** * Are there any media entries, including resume controls? */ - fun hasAnyMedia() = mediaEntries.isNotEmpty() + fun hasAnyMedia() = if (useMediaResumption) mediaEntries.isNotEmpty() else hasActiveMedia() interface Listener { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index de0af16bc2fa..7ae2dc5c0941 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -22,6 +22,10 @@ import android.media.session.MediaController import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.Dumpable +import com.android.systemui.dump.DumpManager +import java.io.FileDescriptor +import java.io.PrintWriter import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton @@ -35,13 +39,15 @@ class MediaDeviceManager @Inject constructor( private val localMediaManagerFactory: LocalMediaManagerFactory, private val mr2manager: MediaRouter2Manager, @Main private val fgExecutor: Executor, - private val mediaDataManager: MediaDataManager -) : MediaDataManager.Listener { + private val mediaDataManager: MediaDataManager, + private val dumpManager: DumpManager +) : MediaDataManager.Listener, Dumpable { private val listeners: MutableSet<Listener> = mutableSetOf() private val entries: MutableMap<String, Token> = mutableMapOf() init { mediaDataManager.addListener(this) + dumpManager.registerDumpable(javaClass.name, this) } /** @@ -81,6 +87,17 @@ class MediaDeviceManager @Inject constructor( } } + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { + with(pw) { + println("MediaDeviceManager state:") + entries.forEach { + key, entry -> + println(" key=$key") + entry.dump(fd, pw, args) + } + } + } + private fun processDevice(key: String, device: MediaDevice?) { val enabled = device != null val data = MediaDeviceData(enabled, device?.iconWithoutBackground, device?.name) @@ -122,6 +139,17 @@ class MediaDeviceManager @Inject constructor( localMediaManager.stopScan() localMediaManager.unregisterCallback(this) } + fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { + val route = controller?.let { + mr2manager.getRoutingSessionForMediaController(it) + } + with(pw) { + println(" current device is ${current?.name}") + val type = controller?.playbackInfo?.playbackType + println(" PlaybackType=$type (1 for local, 2 for remote)") + println(" route=$route") + } + } override fun onDeviceListUpdate(devices: List<MediaDevice>?) = fgExecutor.execute { updateCurrent() } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 775a1649702a..b86e1d0503d4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -480,7 +480,17 @@ class MediaHierarchyManager @Inject constructor( if (inOverlay) { rootOverlay!!.add(mediaFrame) } else { + // When adding back to the host, let's make sure to reset the bounds. + // Usually adding the view will trigger a layout that does this automatically, + // but we sometimes suppress this. targetHost.addView(mediaFrame) + val left = targetHost.paddingLeft + val top = targetHost.paddingTop + mediaFrame.setLeftTopRightBottom( + left, + top, + left + currentBounds.width(), + top + currentBounds.height()) } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index 2bd8c0cbeab2..7c5f0d1c2a16 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -1,5 +1,6 @@ package com.android.systemui.media +import android.graphics.PointF import android.graphics.Rect import android.view.View import android.view.View.OnAttachStateChangeListener @@ -20,8 +21,6 @@ class MediaHost @Inject constructor( var location: Int = -1 private set var visibleChangedListener: ((Boolean) -> Unit)? = null - var visible: Boolean = false - private set private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0) @@ -109,16 +108,17 @@ class MediaHost @Inject constructor( } private fun updateViewVisibility() { - if (showsOnlyActiveMedia) { - visible = mediaDataManager.hasActiveMedia() + visible = if (showsOnlyActiveMedia) { + mediaDataManager.hasActiveMedia() } else { - visible = mediaDataManager.hasAnyMedia() + mediaDataManager.hasAnyMedia() } hostView.visibility = if (visible) View.VISIBLE else View.GONE visibleChangedListener?.invoke(visible) } class MediaHostStateHolder @Inject constructor() : MediaHostState { + private var gonePivot: PointF = PointF() override var measurementInput: MeasurementInput? = null set(value) { @@ -144,6 +144,25 @@ class MediaHost @Inject constructor( } } + override var visible: Boolean = true + set(value) { + if (field == value) { + return + } + field = value + changedListener?.invoke() + } + + override fun getPivotX(): Float = gonePivot.x + override fun getPivotY(): Float = gonePivot.y + override fun setGonePivot(x: Float, y: Float) { + if (gonePivot.equals(x, y)) { + return + } + gonePivot.set(x, y) + changedListener?.invoke() + } + /** * A listener for all changes. This won't be copied over when invoking [copy] */ @@ -157,6 +176,8 @@ class MediaHost @Inject constructor( mediaHostState.expansion = expansion mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia mediaHostState.measurementInput = measurementInput?.copy() + mediaHostState.visible = visible + mediaHostState.gonePivot.set(gonePivot) return mediaHostState } @@ -173,6 +194,12 @@ class MediaHost @Inject constructor( if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) { return false } + if (visible != other.visible) { + return false + } + if (!gonePivot.equals(other.getPivotX(), other.getPivotY())) { + return false + } return true } @@ -180,6 +207,8 @@ class MediaHost @Inject constructor( var result = measurementInput?.hashCode() ?: 0 result = 31 * result + expansion.hashCode() result = 31 * result + showsOnlyActiveMedia.hashCode() + result = 31 * result + if (visible) 1 else 2 + result = 31 * result + gonePivot.hashCode() return result } } @@ -194,7 +223,8 @@ interface MediaHostState { var measurementInput: MeasurementInput? /** - * The expansion of the player, 0 for fully collapsed, 1 for fully expanded + * The expansion of the player, 0 for fully collapsed (up to 3 actions), 1 for fully expanded + * (up to 5 actions.) */ var expansion: Float @@ -204,6 +234,30 @@ interface MediaHostState { var showsOnlyActiveMedia: Boolean /** + * If the view should be VISIBLE or GONE. + */ + var visible: Boolean + + /** + * Sets the pivot point when clipping the height or width. + * Clipping happens when animating visibility when we're visible in QS but not on QQS, + * for example. + */ + fun setGonePivot(x: Float, y: Float) + + /** + * x position of pivot, from 0 to 1 + * @see [setGonePivot] + */ + fun getPivotX(): Float + + /** + * y position of pivot, from 0 to 1 + * @see [setGonePivot] + */ + fun getPivotY(): Float + + /** * Get a copy of this view state, deepcopying all appropriate members */ fun copy(): MediaHostState diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index 6bbe0d1651dd..e8a4b1e46fec 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -40,7 +40,7 @@ import javax.inject.Singleton private const val TAG = "MediaResumeListener" private const val MEDIA_PREFERENCES = "media_control_prefs" -private const val MEDIA_PREFERENCE_KEY = "browser_components" +private const val MEDIA_PREFERENCE_KEY = "browser_components_" @Singleton class MediaResumeListener @Inject constructor( @@ -63,11 +63,16 @@ class MediaResumeListener @Inject constructor( lateinit var resumeComponentFoundCallback: (String, Runnable?) -> Unit private var mediaBrowser: ResumeMediaBrowser? = null + private var currentUserId: Int - private val unlockReceiver = object : BroadcastReceiver() { + private val userChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_USER_UNLOCKED == intent.action) { loadMediaResumptionControls() + } else if (Intent.ACTION_USER_SWITCHED == intent.action) { + currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) + loadSavedComponents() + loadMediaResumptionControls() } } } @@ -97,18 +102,22 @@ class MediaResumeListener @Inject constructor( } init { + currentUserId = context.userId if (useMediaResumption) { val unlockFilter = IntentFilter() unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED) - broadcastDispatcher.registerReceiver(unlockReceiver, unlockFilter, null, UserHandle.ALL) + unlockFilter.addAction(Intent.ACTION_USER_SWITCHED) + broadcastDispatcher.registerReceiver(userChangeReceiver, unlockFilter, null, + UserHandle.ALL) loadSavedComponents() } } private fun loadSavedComponents() { - val userContext = context.createContextAsUser(context.getUser(), 0) - val prefs = userContext.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE) - val listString = prefs.getString(MEDIA_PREFERENCE_KEY, null) + // Make sure list is empty (if we switched users) + resumeComponents.clear() + val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE) + val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null) val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex()) ?.dropLastWhile { it.isEmpty() } components?.forEach { @@ -133,7 +142,6 @@ class MediaResumeListener @Inject constructor( val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it) browser.findRecentMedia() } - broadcastDispatcher.unregisterReceiver(unlockReceiver) // only need to load once } override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { @@ -212,9 +220,8 @@ class MediaResumeListener @Inject constructor( sb.append(it.flattenToString()) sb.append(ResumeMediaBrowser.DELIMITER) } - val userContext = context.createContextAsUser(context.getUser(), 0) - val prefs = userContext.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE) - prefs.edit().putString(MEDIA_PREFERENCE_KEY, sb.toString()).apply() + val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE) + prefs.edit().putString(MEDIA_PREFERENCE_KEY + currentUserId, sb.toString()).apply() } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 3c3f4a977ee7..cf8a636a2b67 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -43,6 +43,11 @@ class MediaTimeoutListener @Inject constructor( private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf() + /** + * Callback representing that a media object is now expired: + * @param token Media session unique identifier + * @param pauseTimeuot True when expired for {@code PAUSED_MEDIA_TIMEOUT} + */ lateinit var timeoutCallback: (String, Boolean) -> Unit override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { @@ -77,6 +82,7 @@ class MediaTimeoutListener @Inject constructor( init { mediaController?.registerCallback(this) + onPlaybackStateChanged(mediaController?.playbackState) } fun destroy() { @@ -112,11 +118,11 @@ class MediaTimeoutListener @Inject constructor( } } - private fun expireMediaTimeout(mediaNotificationKey: String, reason: String) { + private fun expireMediaTimeout(mediaKey: String, reason: String) { cancellation?.apply { if (DEBUG) { Log.v(TAG, - "media timeout cancelled for $mediaNotificationKey, reason: $reason") + "media timeout cancelled for $mediaKey, reason: $reason") } run() } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index e82bb402407e..90ccfc6ca725 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -17,20 +17,22 @@ package com.android.systemui.media import android.content.Context +import android.graphics.PointF import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.R +import com.android.systemui.util.animation.MeasurementOutput import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionLayoutController import com.android.systemui.util.animation.TransitionViewState -import com.android.systemui.util.animation.MeasurementOutput +import javax.inject.Inject /** * A class responsible for controlling a single instance of a media player handling interactions * with the view instance and keeping the media view states up to date. */ -class MediaViewController( +class MediaViewController @Inject constructor( context: Context, - val mediaHostStatesManager: MediaHostStatesManager + private val mediaHostStatesManager: MediaHostStatesManager ) { private var firstRefresh: Boolean = true @@ -44,7 +46,7 @@ class MediaViewController( /** * A map containing all viewStates for all locations of this mediaState */ - private val mViewStates: MutableMap<MediaHostState, TransitionViewState?> = mutableMapOf() + private val viewStates: MutableMap<MediaHostState, TransitionViewState?> = mutableMapOf() /** * The ending location of the view where it ends when all animations and transitions have @@ -69,6 +71,11 @@ class MediaViewController( private val tmpState = TransitionViewState() /** + * Temporary variable to avoid unnecessary allocations. + */ + private val tmpPoint = PointF() + + /** * A callback for media state changes */ val stateCallback = object : MediaHostStatesManager.Callback { @@ -125,7 +132,7 @@ class MediaViewController( * it's not available, it will recreate one by measuring, which may be expensive. */ private fun obtainViewState(state: MediaHostState): TransitionViewState? { - val viewState = mViewStates[state] + val viewState = viewStates[state] if (viewState != null) { // we already have cached this measurement, let's continue return viewState @@ -143,7 +150,7 @@ class MediaViewController( // We don't want to cache interpolated or null states as this could quickly fill up // our cache. We only cache the start and the end states since the interpolation // is cheap - mViewStates[state.copy()] = result + viewStates[state.copy()] = result } else { // This is an interpolated state val startState = state.copy().also { it.expansion = 0.0f } @@ -153,11 +160,13 @@ class MediaViewController( val startViewState = obtainViewState(startState) as TransitionViewState val endState = state.copy().also { it.expansion = 1.0f } val endViewState = obtainViewState(endState) as TransitionViewState + tmpPoint.set(startState.getPivotX(), startState.getPivotY()) result = TransitionViewState() layoutController.getInterpolatedState( startViewState, endViewState, state.expansion, + tmpPoint, result) } } else { @@ -213,11 +222,35 @@ class MediaViewController( val shouldAnimate = animateNextStateChange && !applyImmediately + var startHostState = mediaHostStatesManager.mediaHostStates[startLocation] + var endHostState = mediaHostStatesManager.mediaHostStates[endLocation] + var swappedStartState = false + var swappedEndState = false + + // if we're going from or to a non visible state, let's grab the visible one and animate + // the view being clipped instead. + if (endHostState?.visible != true) { + endHostState = startHostState + swappedEndState = true + } + if (startHostState?.visible != true) { + startHostState = endHostState + swappedStartState = true + } + if (startHostState == null || endHostState == null) { + return + } + + var endViewState = obtainViewState(endHostState) ?: return + if (swappedEndState) { + endViewState = endViewState.copy() + endViewState.height = 0 + } + // Obtain the view state that we'd want to be at the end // The view might not be bound yet or has never been measured and in that case will be // reset once the state is fully available - val endState = obtainViewStateForLocation(endLocation) ?: return - layoutController.setMeasureState(endState) + layoutController.setMeasureState(endViewState) // If the view isn't bound, we can drop the animation, otherwise we'll executute it animateNextStateChange = false @@ -225,24 +258,43 @@ class MediaViewController( return } - val startState = obtainViewStateForLocation(startLocation) + var startViewState = obtainViewState(startHostState) + if (swappedStartState) { + startViewState = startViewState?.copy() + startViewState?.height = 0 + } + val result: TransitionViewState? - if (transitionProgress == 1.0f || startState == null) { - result = endState + result = if (transitionProgress == 1.0f || startViewState == null) { + endViewState } else if (transitionProgress == 0.0f) { - result = startState + startViewState } else { - layoutController.getInterpolatedState(startState, endState, transitionProgress, - tmpState) - result = tmpState + if (swappedEndState || swappedStartState) { + tmpPoint.set(startHostState.getPivotX(), startHostState.getPivotY()) + } else { + tmpPoint.set(0.0f, 0.0f) + } + layoutController.getInterpolatedState(startViewState, endViewState, transitionProgress, + tmpPoint, tmpState) + tmpState } layoutController.setState(result, applyImmediately, shouldAnimate, animationDuration, animationDelay) } - private fun obtainViewStateForLocation(location: Int): TransitionViewState? { - val mediaState = mediaHostStatesManager.mediaHostStates[location] ?: return null - return obtainViewState(mediaState) + /** + * Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation]. + * In the event of [location] not being visible, [locationWhenHidden] will be used instead. + * + * @param location Target + * @param locationWhenHidden Location that will be used when the target is not + * [MediaHost.visible] + * @return State require for executing a transition, and also the respective [MediaHost]. + */ + private fun obtainViewStateForLocation(@MediaLocation location: Int): TransitionViewState? { + val mediaHostState = mediaHostStatesManager.mediaHostStates[location] ?: return null + return obtainViewState(mediaHostState) } /** @@ -250,8 +302,7 @@ class MediaViewController( * This updates the width the view will me measured with. */ fun onLocationPreChange(@MediaLocation newLocation: Int) { - val viewState = obtainViewStateForLocation(newLocation) - viewState?.let { + obtainViewStateForLocation(newLocation)?.let { layoutController.setMeasureState(it) } } @@ -271,7 +322,7 @@ class MediaViewController( fun refreshState() { if (!firstRefresh) { // Let's clear all of our measurements and recreate them! - mViewStates.clear() + viewStates.clear() setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress, applyImmediately = false) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt index 9b9a6b4b13ab..bccc3abd8a27 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt @@ -13,6 +13,7 @@ import androidx.core.view.GestureDetectorCompat import com.android.systemui.R import com.android.systemui.qs.PageIndicator import com.android.systemui.statusbar.notification.VisualStabilityManager +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.animation.requiresRemeasuring import javax.inject.Inject @@ -31,7 +32,8 @@ class MediaViewManager @Inject constructor( private val mediaControlPanelFactory: Provider<MediaControlPanel>, private val visualStabilityManager: VisualStabilityManager, private val mediaHostStatesManager: MediaHostStatesManager, - mediaManager: MediaDataCombineLatest + mediaManager: MediaDataCombineLatest, + configurationController: ConfigurationController ) { /** @@ -74,6 +76,7 @@ class MediaViewManager @Inject constructor( private val mediaCarousel: HorizontalScrollView val mediaFrame: ViewGroup val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf() + private val mediaData: MutableMap<String, MediaData> = mutableMapOf() private val mediaContent: ViewGroup private val pageIndicator: PageIndicator private val gestureDetector: GestureDetectorCompat @@ -120,6 +123,11 @@ class MediaViewManager @Inject constructor( return this@MediaViewManager.onTouch(view, motionEvent) } } + private val configListener = object : ConfigurationController.ConfigurationListener { + override fun onDensityOrFontScaleChanged() { + recreatePlayers() + } + } init { gestureDetector = GestureDetectorCompat(context, gestureListener) @@ -130,6 +138,7 @@ class MediaViewManager @Inject constructor( mediaCarousel.setOnTouchListener(touchListener) mediaCarousel.setOverScrollMode(View.OVER_SCROLL_NEVER) mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) + configurationController.addCallback(configListener) visualStabilityCallback = VisualStabilityManager.Callback { if (needsReordering) { needsReordering = false @@ -142,29 +151,14 @@ class MediaViewManager @Inject constructor( true /* persistent */) mediaManager.addListener(object : MediaDataManager.Listener { override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { - updateView(key, oldKey, data) - updatePlayerVisibilities() - mediaCarousel.requiresRemeasuring = true + oldKey?.let { mediaData.remove(it) } + mediaData.put(key, data) + addOrUpdatePlayer(key, oldKey, data) } override fun onMediaDataRemoved(key: String) { - val removed = mediaPlayers.remove(key) - removed?.apply { - val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= - activeMediaIndex - mediaContent.removeView(removed.view?.player) - removed.onDestroy() - updateMediaPaddings() - if (beforeActive) { - // also update the index here since the scroll below might not always lead - // to a scrolling changed - activeMediaIndex = Math.max(0, activeMediaIndex - 1) - mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - - playerWidthPlusPadding, 0) - } - updatePlayerVisibilities() - updatePageIndicator() - } + mediaData.remove(key) + removePlayer(key) } }) mediaHostStatesManager.addCallback(object : MediaHostStatesManager.Callback { @@ -253,7 +247,7 @@ class MediaViewManager @Inject constructor( } } - private fun updateView(key: String, oldKey: String?, data: MediaData) { + private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) { // If the key was changed, update entry val oldData = mediaPlayers[oldKey] if (oldData != null) { @@ -288,6 +282,39 @@ class MediaViewManager @Inject constructor( existingPlayer?.bind(data) updateMediaPaddings() updatePageIndicator() + updatePlayerVisibilities() + mediaCarousel.requiresRemeasuring = true + } + + private fun removePlayer(key: String) { + val removed = mediaPlayers.remove(key) + removed?.apply { + val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= + activeMediaIndex + mediaContent.removeView(removed.view?.player) + removed.onDestroy() + updateMediaPaddings() + if (beforeActive) { + // also update the index here since the scroll below might not always lead + // to a scrolling changed + activeMediaIndex = Math.max(0, activeMediaIndex - 1) + mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - + playerWidthPlusPadding, 0) + } + updatePlayerVisibilities() + updatePageIndicator() + } + } + + private fun recreatePlayers() { + // Note that this will scramble the order of players. Actively playing sessions will, at + // least, still be put in the front. If we want to maintain order, then more work is + // needed. + mediaData.forEach { + key, data -> + removePlayer(key) + addOrUpdatePlayer(key = key, oldKey = null, data = data) + } } private fun updateMediaPaddings() { diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java index 1e9a30364607..6462f072bc74 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java @@ -135,12 +135,16 @@ public class ResumeMediaBrowser { */ @Override public void onConnected() { + Log.d(TAG, "Service connected for " + mComponentName); if (mMediaBrowser.isConnected()) { - mCallback.onConnected(); - Log.d(TAG, "Service connected for " + mComponentName); String root = mMediaBrowser.getRoot(); - mMediaBrowser.subscribe(root, mSubscriptionCallback); + if (!TextUtils.isEmpty(root)) { + mCallback.onConnected(); + mMediaBrowser.subscribe(root, mSubscriptionCallback); + return; + } } + mCallback.onError(); } /** @@ -193,6 +197,10 @@ public class ResumeMediaBrowser { @Override public void onConnected() { Log.d(TAG, "Connected for restart " + mMediaBrowser.isConnected()); + if (!mMediaBrowser.isConnected()) { + mCallback.onError(); + return; + } MediaSession.Token token = mMediaBrowser.getSessionToken(); MediaController controller = new MediaController(mContext, token); controller.getTransportControls(); @@ -251,7 +259,8 @@ public class ResumeMediaBrowser { @Override public void onConnected() { Log.d(TAG, "connected"); - if (TextUtils.isEmpty(mMediaBrowser.getRoot())) { + if (!mMediaBrowser.isConnected() + || TextUtils.isEmpty(mMediaBrowser.getRoot())) { mCallback.onError(); } else { mCallback.onConnected(); diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt index efc476d0c86f..1dca3f1297b1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt @@ -20,19 +20,22 @@ import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.PlaybackState import android.os.SystemClock +import android.view.GestureDetector import android.view.MotionEvent import android.view.View +import android.view.ViewConfiguration import android.widget.SeekBar import androidx.annotation.AnyThread import androidx.annotation.WorkerThread +import androidx.core.view.GestureDetectorCompat import androidx.lifecycle.MutableLiveData import androidx.lifecycle.LiveData import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.concurrency.RepeatableExecutor -import java.util.concurrent.Executor import javax.inject.Inject private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L +private const val MIN_FLING_VELOCITY_SCALE_FACTOR = 10 private fun PlaybackState.isInMotion(): Boolean { return this.state == PlaybackState.STATE_PLAYING || @@ -68,7 +71,6 @@ private fun PlaybackState.computePosition(duration: Long): Long { /** ViewModel for seek bar in QS media player. */ class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: RepeatableExecutor) { - private var _data = Progress(false, false, null, null) set(value) { field = value @@ -104,6 +106,9 @@ class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: R } private var cancel: Runnable? = null + /** Indicates if the seek interaction is considered a false guesture. */ + private var isFalseSeek = false + /** Listening state (QS open or closed) is used to control polling of progress. */ var listening = true set(value) = bgExecutor.execute { @@ -113,16 +118,61 @@ class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: R } } + /** Set to true when the user is touching the seek bar to change the position. */ + private var scrubbing = false + set(value) { + if (field != value) { + field = value + checkIfPollingNeeded() + } + } + + /** + * Event indicating that the user has started interacting with the seek bar. + */ + @AnyThread + fun onSeekStarting() = bgExecutor.execute { + scrubbing = true + isFalseSeek = false + } + + /** + * Event indicating that the user has moved the seek bar but hasn't yet finished the gesture. + * @param position Current location in the track. + */ + @AnyThread + fun onSeekProgress(position: Long) = bgExecutor.execute { + if (scrubbing) { + _data = _data.copy(elapsedTime = position.toInt()) + } + } + + /** + * Event indicating that the seek interaction is a false gesture and it should be ignored. + */ + @AnyThread + fun onSeekFalse() = bgExecutor.execute { + if (scrubbing) { + isFalseSeek = true + } + } + /** * Handle request to change the current position in the media track. * @param position Place to seek to in the track. */ - @WorkerThread - fun onSeek(position: Long) { - controller?.transportControls?.seekTo(position) - // Invalidate the cached playbackState to avoid the thumb jumping back to the previous - // position. - playbackState = null + @AnyThread + fun onSeek(position: Long) = bgExecutor.execute { + if (isFalseSeek) { + scrubbing = false + checkPlaybackPosition() + } else { + controller?.transportControls?.seekTo(position) + // Invalidate the cached playbackState to avoid the thumb jumping back to the previous + // position. + playbackState = null + scrubbing = false + } } /** @@ -180,7 +230,7 @@ class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: R @WorkerThread private fun checkIfPollingNeeded() { - val needed = listening && playbackState?.isInMotion() ?: false + val needed = listening && !scrubbing && playbackState?.isInMotion() ?: false if (needed) { if (cancel == null) { cancel = bgExecutor.executeRepeatedly(this::checkPlaybackPosition, 0L, @@ -195,41 +245,162 @@ class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: R /** Gets a listener to attach to the seek bar to handle seeking. */ val seekBarListener: SeekBar.OnSeekBarChangeListener get() { - return SeekBarChangeListener(this, bgExecutor) + return SeekBarChangeListener(this) } - /** Gets a listener to attach to the seek bar to disable touch intercepting. */ - val seekBarTouchListener: View.OnTouchListener - get() { - return SeekBarTouchListener() - } + /** Attach touch handlers to the seek bar view. */ + fun attachTouchHandlers(bar: SeekBar) { + bar.setOnSeekBarChangeListener(seekBarListener) + bar.setOnTouchListener(SeekBarTouchListener(this, bar)) + } private class SeekBarChangeListener( - val viewModel: SeekBarViewModel, - val bgExecutor: Executor + val viewModel: SeekBarViewModel ) : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { - bgExecutor.execute { - viewModel.onSeek(progress.toLong()) - } + viewModel.onSeekProgress(progress.toLong()) } } + override fun onStartTrackingTouch(bar: SeekBar) { + viewModel.onSeekStarting() } + override fun onStopTrackingTouch(bar: SeekBar) { - val pos = bar.progress.toLong() - bgExecutor.execute { - viewModel.onSeek(pos) - } + viewModel.onSeek(bar.progress.toLong()) } } - private class SeekBarTouchListener : View.OnTouchListener { + /** + * Responsible for intercepting touch events before they reach the seek bar. + * + * This reduces the gestures seen by the seek bar so that users don't accidentially seek when + * they intend to scroll the carousel. + */ + private class SeekBarTouchListener( + private val viewModel: SeekBarViewModel, + private val bar: SeekBar + ) : View.OnTouchListener, GestureDetector.OnGestureListener { + + // Gesture detector helps decide which touch events to intercept. + private val detector = GestureDetectorCompat(bar.context, this) + // Velocity threshold used to decide when a fling is considered a false gesture. + private val flingVelocity: Int = ViewConfiguration.get(bar.context).run { + getScaledMinimumFlingVelocity() * MIN_FLING_VELOCITY_SCALE_FACTOR + } + // Indicates if the gesture should go to the seek bar or if it should be intercepted. + private var shouldGoToSeekBar = false + + /** + * Decide which touch events to intercept before they reach the seek bar. + * + * Based on the gesture detected, we decide whether we want the event to reach the seek bar. + * If we want the seek bar to see the event, then we return false so that the event isn't + * handled here and it will be passed along. If, however, we don't want the seek bar to see + * the event, then return true so that the event is handled here. + * + * When the seek bar is contained in the carousel, the carousel still has the ability to + * intercept the touch event. So, even though we may handle the event here, the carousel can + * still intercept the event. This way, gestures that we consider falses on the seek bar can + * still be used by the carousel for paging. + * + * Returns true for events that we don't want dispatched to the seek bar. + */ override fun onTouch(view: View, event: MotionEvent): Boolean { - view.parent.requestDisallowInterceptTouchEvent(true) - return view.onTouchEvent(event) + if (view != bar) { + return false + } + detector.onTouchEvent(event) + return !shouldGoToSeekBar + } + + /** + * Handle down events that press down on the thumb. + * + * On the down action, determine a target box around the thumb to know when a scroll + * gesture starts by clicking on the thumb. The target box will be used by subsequent + * onScroll events. + * + * Returns true when the down event hits within the target box of the thumb. + */ + override fun onDown(event: MotionEvent): Boolean { + val padL = bar.paddingLeft + val padR = bar.paddingRight + // Compute the X location of the thumb as a function of the seek bar progress. + // TODO: account for thumb offset + val progress = bar.getProgress() + val range = bar.max - bar.min + val widthFraction = if (range > 0) { + (progress - bar.min).toDouble() / range + } else { + 0.0 + } + val availableWidth = bar.width - padL - padR + val thumbX = if (bar.isLayoutRtl()) { + padL + availableWidth * (1 - widthFraction) + } else { + padL + availableWidth * widthFraction + } + // Set the min, max boundaries of the thumb box. + // I'm cheating by using the height of the seek bar as the width of the box. + val halfHeight: Int = bar.height / 2 + val targetBoxMinX = (Math.round(thumbX) - halfHeight).toInt() + val targetBoxMaxX = (Math.round(thumbX) + halfHeight).toInt() + // If the x position of the down event is within the box, then request that the parent + // not intercept the event. + val x = Math.round(event.x) + shouldGoToSeekBar = x >= targetBoxMinX && x <= targetBoxMaxX + if (shouldGoToSeekBar) { + bar.parent?.requestDisallowInterceptTouchEvent(true) + } + return shouldGoToSeekBar + } + + /** + * Always handle single tap up. + * + * This enables the user to single tap anywhere on the seek bar to seek to that position. + */ + override fun onSingleTapUp(event: MotionEvent): Boolean { + shouldGoToSeekBar = true + return true } + + /** + * Handle scroll events when the down event is on the thumb. + * + * Returns true when the down event of the scroll hits within the target box of the thumb. + */ + override fun onScroll( + eventStart: MotionEvent, + event: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + return shouldGoToSeekBar + } + + /** + * Handle fling events when the down event is on the thumb. + * + * Gestures that include a fling are considered a false gesture on the seek bar. + */ + override fun onFling( + eventStart: MotionEvent, + event: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { + if (Math.abs(velocityX) > flingVelocity || Math.abs(velocityY) > flingVelocity) { + viewModel.onSeekFalse() + } + return shouldGoToSeekBar + } + + override fun onShowPress(event: MotionEvent) {} + + override fun onLongPress(event: MotionEvent) {} } /** State seen by seek bar UI. */ diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index b93e07e65c73..9daa876038d5 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -708,6 +708,12 @@ public class PipTaskOrganizer extends TaskOrganizer implements Log.w(TAG, "Abort animation, invalid leash"); return; } + + if (startBounds.isEmpty() || destinationBounds.isEmpty()) { + Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting."); + return; + } + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds); tx.apply(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index d077666f8184..06c98d00cca7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; import android.util.Log; @@ -38,6 +39,9 @@ import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.PrintWriter; import java.util.function.Consumer; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; + /** * A helper to animate and manipulate the PiP. */ @@ -74,9 +78,15 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, new SfVsyncFrameCallbackProvider(); /** - * Bounds that are animated using the physics animator. + * Temporary bounds used when PIP is being dragged or animated. These bounds are applied to PIP + * using {@link PipTaskOrganizer#scheduleUserResizePip}, so that we can animate shrinking into + * and expanding out of the magnetic dismiss target. + * + * Once PIP is done being dragged or animated, we set {@link #mBounds} equal to these temporary + * bounds, and call {@link PipTaskOrganizer#scheduleFinishResizePip} to 'officially' move PIP to + * its new bounds. */ - private final Rect mAnimatedBounds = new Rect(); + private final Rect mTemporaryBounds = new Rect(); /** The destination bounds to which PIP is animating. */ private final Rect mAnimatingToBounds = new Rect(); @@ -85,20 +95,20 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private FloatingContentCoordinator mFloatingContentCoordinator; /** Callback that re-sizes PIP to the animated bounds. */ - private final Choreographer.FrameCallback mResizePipVsyncCallback = - l -> resizePipUnchecked(mAnimatedBounds); + private final Choreographer.FrameCallback mResizePipVsyncCallback; /** - * PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations. + * PhysicsAnimator instance for animating {@link #mTemporaryBounds} using physics animations. */ - private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance( - mAnimatedBounds); + private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( + mTemporaryBounds); + + private MagnetizedObject<Rect> mMagnetizedPip; /** - * Update listener that resizes the PIP to {@link #mAnimatedBounds}. + * Update listener that resizes the PIP to {@link #mTemporaryBounds}. */ - final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener = - (target, values) -> mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback); + private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener; /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ private PhysicsAnimator.FlingConfig mFlingConfigX; @@ -124,6 +134,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private boolean mSpringingToTouch = false; /** + * Whether PIP was released in the dismiss target, and will be animated out and dismissed + * shortly. + */ + private boolean mDismissalPending = false; + + /** * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is * used to show menu activity when the expand animation is completed. */ @@ -155,6 +171,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mSnapAlgorithm = snapAlgorithm; mFloatingContentCoordinator = floatingContentCoordinator; mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback); + + mResizePipVsyncCallback = l -> { + if (!mTemporaryBounds.isEmpty()) { + mPipTaskOrganizer.scheduleUserResizePip( + mBounds, mTemporaryBounds, null); + } + }; + + mResizePipUpdateListener = (target, values) -> + mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback); } @NonNull @@ -186,19 +212,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } } - /** - * Synchronizes the current bounds with either the pinned stack, or the ongoing animation. This - * is done to prepare for a touch gesture. - */ - void synchronizePinnedStackBoundsForTouchGesture() { - if (mAnimatingToBounds.isEmpty()) { - // If we're not animating anywhere, sync normally. - synchronizePinnedStackBounds(); - } else { - // If we're animating, set the current bounds to the animated bounds. That way, the - // touch gesture will begin at the most recent animated location of the bounds. - mBounds.set(mAnimatedBounds); - } + boolean isAnimating() { + return mTemporaryBoundsPhysicsAnimator.isRunning(); } /** @@ -224,32 +239,54 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, // If we are moving PIP directly to the touch event locations, cancel any animations and // move PIP to the given bounds. cancelAnimations(); - resizePipUnchecked(toBounds); - mBounds.set(toBounds); + + if (!isDragging) { + resizePipUnchecked(toBounds); + mBounds.set(toBounds); + } else { + mTemporaryBounds.set(toBounds); + mPipTaskOrganizer.scheduleUserResizePip(mBounds, mTemporaryBounds, null); + } } else { // If PIP is 'catching up' after being stuck in the dismiss target, update the animation // to spring towards the new touch location. - mAnimatedBoundsPhysicsAnimator + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig) .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig) - .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig) - .withEndActions(() -> mSpringingToTouch = false); + .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig); startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */, false /* dismiss */); } } - /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */ - void setSpringingToTouch(boolean springingToTouch) { - if (springingToTouch) { - mAnimatedBounds.set(mBounds); - } + /** Animates the PIP into the dismiss target, scaling it down. */ + void animateIntoDismissTarget( + MagnetizedObject.MagneticTarget target, + float velX, float velY, + boolean flung, Function0<Unit> after) { + final PointF targetCenter = target.getCenterOnScreen(); - mSpringingToTouch = springingToTouch; + final float desiredWidth = mBounds.width() / 2; + final float desiredHeight = mBounds.height() / 2; + + final float destinationX = targetCenter.x - (desiredWidth / 2f); + final float destinationY = targetCenter.y - (desiredHeight / 2f); + + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig) + .spring(FloatProperties.RECT_Y, destinationY, velY, mSpringConfig) + .spring(FloatProperties.RECT_WIDTH, desiredWidth, mSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, desiredHeight, mSpringConfig) + .withEndActions(after); + + startBoundsAnimator(destinationX, destinationY, false); } - void prepareForAnimation() { - mAnimatedBounds.set(mBounds); + /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */ + void setSpringingToTouch(boolean springingToTouch) { + mSpringingToTouch = springingToTouch; } /** @@ -309,13 +346,22 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** + * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds + * otherwise. + */ + Rect getPossiblyAnimatingBounds() { + return mTemporaryBounds.isEmpty() ? mBounds : mTemporaryBounds; + } + + /** * Flings the PiP to the closest snap target. */ void flingToSnapTarget( float velocityX, float velocityY, @Nullable Runnable updateAction, @Nullable Runnable endAction) { - mAnimatedBounds.set(mBounds); - mAnimatedBoundsPhysicsAnimator + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig) .flingThenSpring( FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig, true /* flingMustReachMinOrMax */) @@ -324,13 +370,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, .withEndActions(endAction); if (updateAction != null) { - mAnimatedBoundsPhysicsAnimator.addUpdateListener( + mTemporaryBoundsPhysicsAnimator.addUpdateListener( (target, values) -> updateAction.run()); } final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right; final float estimatedFlingYEndValue = - PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY); + PhysicsAnimator.estimateFlingEndValue( + mTemporaryBounds.top, velocityY, mFlingConfigY); startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */, false /* dismiss */); @@ -341,8 +388,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * configuration */ void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) { - mAnimatedBounds.set(mBounds); - mAnimatedBoundsPhysicsAnimator + if (!mTemporaryBoundsPhysicsAnimator.isRunning()) { + // Animate from the current bounds if we're not already animating. + mTemporaryBounds.set(mBounds); + } + + mTemporaryBoundsPhysicsAnimator .spring(FloatProperties.RECT_X, bounds.left, springConfig) .spring(FloatProperties.RECT_Y, bounds.top, springConfig); startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */, @@ -353,18 +404,19 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Animates the dismissal of the PiP off the edge of the screen. */ void animateDismiss() { - mAnimatedBounds.set(mBounds); - // Animate off the bottom of the screen, then dismiss PIP. - mAnimatedBoundsPhysicsAnimator + mTemporaryBoundsPhysicsAnimator .spring(FloatProperties.RECT_Y, - mBounds.bottom + mBounds.height(), + mMovementBounds.bottom + mBounds.height() * 2, 0, mSpringConfig) .withEndActions(this::dismissPip); - startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */, + startBoundsAnimator( + mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */, true /* dismiss */); + + mDismissalPending = false; } /** @@ -415,7 +467,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Cancels all existing animations. */ private void cancelAnimations() { - mAnimatedBoundsPhysicsAnimator.cancel(); + mTemporaryBoundsPhysicsAnimator.cancel(); mAnimatingToBounds.setEmpty(); mSpringingToTouch = false; } @@ -449,15 +501,36 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, (int) toY + mBounds.height()); setAnimatingToBounds(mAnimatingToBounds); - mAnimatedBoundsPhysicsAnimator - .withEndActions(() -> { - if (!dismiss) { - mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds); - } - mAnimatingToBounds.setEmpty(); - }) - .addUpdateListener(mResizePipUpdateListener) - .start(); + if (!mTemporaryBoundsPhysicsAnimator.isRunning()) { + mTemporaryBoundsPhysicsAnimator + .addUpdateListener(mResizePipUpdateListener) + .withEndActions(this::onBoundsAnimationEnd); + } + + mTemporaryBoundsPhysicsAnimator.start(); + } + + /** + * Notify that PIP was released in the dismiss target and will be animated out and dismissed + * shortly. + */ + void notifyDismissalPending() { + mDismissalPending = true; + } + + private void onBoundsAnimationEnd() { + if (!mDismissalPending + && !mSpringingToTouch + && !mMagnetizedPip.getObjectStuckToTarget()) { + mBounds.set(mTemporaryBounds); + mPipTaskOrganizer.scheduleFinishResizePip(mBounds); + + mTemporaryBounds.setEmpty(); + } + + mAnimatingToBounds.setEmpty(); + mSpringingToTouch = false; + mDismissalPending = false; } /** @@ -491,10 +564,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, Log.d(TAG, "resizeAndAnimatePipUnchecked: toBounds=" + toBounds + " duration=" + duration + " callers=\n" + Debug.getCallers(5, " ")); } - if (!toBounds.equals(mBounds)) { - mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration, mUpdateBoundsCallback); - setAnimatingToBounds(toBounds); - } + + // Intentionally resize here even if the current bounds match the destination bounds. + // This is so all the proper callbacks are performed. + mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration, mUpdateBoundsCallback); + setAnimatingToBounds(toBounds); } /** @@ -502,25 +576,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * magnetic dismiss target so it can calculate PIP's size and position. */ MagnetizedObject<Rect> getMagnetizedPip() { - return new MagnetizedObject<Rect>( - mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) { - @Override - public float getWidth(@NonNull Rect animatedPipBounds) { - return animatedPipBounds.width(); - } - - @Override - public float getHeight(@NonNull Rect animatedPipBounds) { - return animatedPipBounds.height(); - } + if (mMagnetizedPip == null) { + mMagnetizedPip = new MagnetizedObject<Rect>( + mContext, mTemporaryBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) { + @Override + public float getWidth(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.width(); + } + + @Override + public float getHeight(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.height(); + } + + @Override + public void getLocationOnScreen( + @NonNull Rect animatedPipBounds, @NonNull int[] loc) { + loc[0] = animatedPipBounds.left; + loc[1] = animatedPipBounds.top; + } + }; + } - @Override - public void getLocationOnScreen( - @NonNull Rect animatedPipBounds, @NonNull int[] loc) { - loc[0] = animatedPipBounds.left; - loc[1] = animatedPipBounds.top; - } - }; + return mMagnetizedPip; } public void dump(PrintWriter pw, String prefix) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java index f6b212c6f19f..c151715cd4ef 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -260,12 +260,14 @@ public class PipResizeGestureHandler { private void onMotionEvent(MotionEvent ev) { int action = ev.getActionMasked(); + float x = ev.getX(); + float y = ev.getY(); if (action == MotionEvent.ACTION_DOWN) { mLastResizeBounds.setEmpty(); - mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); + mAllowGesture = isWithinTouchRegion((int) x, (int) y); if (mAllowGesture) { - setCtrlType((int) ev.getX(), (int) ev.getY()); - mDownPoint.set(ev.getX(), ev.getY()); + setCtrlType((int) x, (int) y); + mDownPoint.set(x, y); mLastDownBounds.set(mMotionHelper.getBounds()); } @@ -277,20 +279,23 @@ public class PipResizeGestureHandler { break; case MotionEvent.ACTION_MOVE: // Capture inputs - float dx = Math.abs(ev.getX() - mDownPoint.x); - float dy = Math.abs(ev.getY() - mDownPoint.y); - if (!mThresholdCrossed && dx > mTouchSlop && dy > mTouchSlop) { + if (!mThresholdCrossed + && Math.hypot(x - mDownPoint.x, y - mDownPoint.y) > mTouchSlop) { mThresholdCrossed = true; + // Reset the down to begin resizing from this point + mDownPoint.set(x, y); mInputMonitor.pilferPointers(); } - final Rect currentPipBounds = mMotionHelper.getBounds(); - mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(), - mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, - mMinSize.y, mMaxSize, true, - mLastDownBounds.width() > mLastDownBounds.height())); - mPipBoundsHandler.transformBoundsToAspectRatio(mLastResizeBounds); - mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, - null); + if (mThresholdCrossed) { + final Rect currentPipBounds = mMotionHelper.getBounds(); + mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y, + mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, + mMinSize.y, mMaxSize, true, + mLastDownBounds.width() > mLastDownBounds.height())); + mPipBoundsHandler.transformBoundsToAspectRatio(mLastResizeBounds); + mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, + null); + } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: 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 3cc9127068bf..2f9b29d13744 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -70,6 +70,8 @@ import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.PrintWriter; +import kotlin.Unit; + /** * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding * the PIP. @@ -262,12 +264,14 @@ public class PipTouchHandler { mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); updateMagneticTargetSize(); - mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener); + mMagnetizedPip.setAnimateStuckToTarget( + (target, velX, velY, flung, after) -> { + mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after); + return Unit.INSTANCE; + }); mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { @Override public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { - mMotionHelper.prepareForAnimation(); - // Show the dismiss target, in case the initial touch event occurred within the // magnetic field radius. showDismissTargetMaybe(); @@ -286,12 +290,13 @@ public class PipTouchHandler { @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mMotionHelper.notifyDismissalPending(); + mHandler.post(() -> { mMotionHelper.animateDismiss(); hideDismissTarget(); }); - MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, PipUtils.getTopPipActivity(mContext, mActivityManager)); } @@ -617,11 +622,16 @@ public class PipTouchHandler { } MotionEvent ev = (MotionEvent) inputEvent; - - if (mPipResizeGestureHandler.isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY())) { + if (!mTouchState.isDragging() + && !mMagnetizedPip.getObjectStuckToTarget() + && !mMotionHelper.isAnimating() + && mPipResizeGestureHandler.isWithinTouchRegion( + (int) ev.getRawX(), (int) ev.getRawY())) { return true; } - if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) { + + if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting()) + && mMagnetizedPip.maybeConsumeMotionEvent(ev)) { // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event // to the touch state. Touch state needs a DOWN event in order to later process MOVE // events it'll receive if the object is dragged out of the magnetic field. @@ -643,7 +653,6 @@ public class PipTouchHandler { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { - mMotionHelper.synchronizePinnedStackBoundsForTouchGesture(); mGesture.onDown(mTouchState); break; } @@ -688,11 +697,11 @@ public class PipTouchHandler { break; } case MotionEvent.ACTION_HOVER_EXIT: { - mHideMenuAfterShown = true; // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably // on and changing MotionEvents into HoverEvents. // Let's not enable menu show/hide for a11y services. if (!mAccessibilityManager.isTouchExplorationEnabled()) { + mHideMenuAfterShown = true; mMenuController.hideMenu(); } if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) { @@ -872,7 +881,7 @@ public class PipTouchHandler { return; } - Rect bounds = mMotionHelper.getBounds(); + Rect bounds = mMotionHelper.getPossiblyAnimatingBounds(); mDelta.set(0f, 0f); mStartPosition.set(bounds.left, bounds.top); mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom; @@ -914,7 +923,7 @@ public class PipTouchHandler { mDelta.x += left - lastX; mDelta.y += top - lastY; - mTmpBounds.set(mMotionHelper.getBounds()); + mTmpBounds.set(mMotionHelper.getPossiblyAnimatingBounds()); mTmpBounds.offsetTo((int) left, (int) top); mMotionHelper.movePip(mTmpBounds, true /* isDragging */); diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt index 40d317c7bb22..dc157a8dd257 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt @@ -139,12 +139,7 @@ class DoubleLineTileLayout( } tilesToShow = actualColumns * NUM_LINES - val interTileSpace = if (actualColumns <= 2) { - // Extra "column" of padding to be distributed on each end - (availableWidth - actualColumns * smallTileSize) / actualColumns - } else { - (availableWidth - actualColumns * smallTileSize) / (actualColumns - 1) - } + val spacePerTile = availableWidth / actualColumns for (index in 0 until mRecords.size) { val tileView = mRecords[index].tileView @@ -154,15 +149,16 @@ class DoubleLineTileLayout( tileView.visibility = View.VISIBLE if (index > 0) tileView.updateAccessibilityOrder(mRecords[index - 1].tileView) val column = index % actualColumns - val left = getLeftForColumn(column, interTileSpace, actualColumns <= 2) + val left = getLeftForColumn(column, spacePerTile) val top = if (index < actualColumns) 0 else getTopBottomRow() tileView.layout(left, top, left + smallTileSize, top + smallTileSize) } } } - private fun getLeftForColumn(column: Int, interSpace: Int, sideMargin: Boolean): Int { - return (if (sideMargin) interSpace / 2 else 0) + column * (smallTileSize + interSpace) + private fun getLeftForColumn(column: Int, spacePerTile: Int): Int { + // Distribute the space evenly among all tiles. + return (column * spacePerTile + spacePerTile / 2.0f - smallTileSize / 2.0f).toInt() } private fun getTopBottomRow() = smallTileSize + cellMarginVertical diff --git a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java index c204d94916a4..aa17c4aa79b1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java @@ -17,6 +17,9 @@ package com.android.systemui.qs; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewParent; import android.widget.ScrollView; /** @@ -24,8 +27,12 @@ import android.widget.ScrollView; */ public class NonInterceptingScrollView extends ScrollView { + private final int mTouchSlop; + private float mDownY; + public NonInterceptingScrollView(Context context, AttributeSet attrs) { super(context, attrs); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } @Override @@ -34,10 +41,52 @@ public class NonInterceptingScrollView extends ScrollView { switch (action) { case MotionEvent.ACTION_DOWN: if (canScrollVertically(1)) { - requestDisallowInterceptTouchEvent(true); + // If we can scroll down, make sure we're not intercepted by the parent + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } } break; } return super.onTouchEvent(ev); } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + // If there's a touch on this view and we can scroll down, we don't want to be intercepted + int action = ev.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: + // If we can scroll down, make sure non of our parents intercepts us. + if (canScrollVertically(1)) { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + mDownY = ev.getY(); + break; + case MotionEvent.ACTION_MOVE: { + final int y = (int) ev.getY(); + final float yDiff = y - mDownY; + if (yDiff < -mTouchSlop && !canScrollVertically(1)) { + // Don't intercept touches that are overscrolling. + return false; + } + break; + } + } + return super.onInterceptTouchEvent(ev); + } + + public int getScrollRange() { + int scrollRange = 0; + if (getChildCount() > 0) { + View child = getChildAt(0); + scrollRange = Math.max(0, + child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop)); + } + return scrollRange; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index aaff9ac47ebf..3eed8ad89075 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -66,6 +66,10 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private int mHorizontalClipBound; private final Rect mClippingRect; private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger(); + private int mExcessHeight; + private int mLastExcessHeight; + private int mMinRows = 1; + private int mMaxColumns = TileLayout.NO_MAX_COLUMNS; public PagedTileLayout(Context context, AttributeSet attrs) { super(context, attrs); @@ -195,11 +199,18 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - mPages.add((TilePage) LayoutInflater.from(getContext()) - .inflate(R.layout.qs_paged_page, this, false)); + mPages.add(createTilePage()); mAdapter.notifyDataSetChanged(); } + private TilePage createTilePage() { + TilePage page = (TilePage) LayoutInflater.from(getContext()) + .inflate(R.layout.qs_paged_page, this, false); + page.setMinRows(mMinRows); + page.setMaxColumns(mMaxColumns); + return page; + } + public void setPageIndicator(PageIndicator indicator) { mPageIndicator = indicator; mPageIndicator.setNumPages(mPages.size()); @@ -280,15 +291,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } private void emptyAndInflateOrRemovePages() { - final int nTiles = mTiles.size(); - // We should always have at least one page, even if it's empty. - int numPages = Math.max(nTiles / mPages.get(0).maxTiles(), 1); - - // Add one more not full page if needed - if (nTiles > numPages * mPages.get(0).maxTiles()) { - numPages++; - } - + final int numPages = getNumPages(); final int NP = mPages.size(); for (int i = 0; i < NP; i++) { mPages.get(i).removeAllViews(); @@ -298,8 +301,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } while (mPages.size() < numPages) { if (DEBUG) Log.d(TAG, "Adding page"); - mPages.add((TilePage) LayoutInflater.from(getContext()) - .inflate(R.layout.qs_paged_page, this, false)); + mPages.add(createTilePage()); } while (mPages.size() > numPages) { if (DEBUG) Log.d(TAG, "Removing page"); @@ -342,17 +344,54 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } @Override + public boolean setMinRows(int minRows) { + mMinRows = minRows; + boolean changed = false; + for (int i = 0; i < mPages.size(); i++) { + if (mPages.get(i).setMinRows(minRows)) { + changed = true; + mDistributeTiles = true; + } + } + return changed; + } + + @Override + public boolean setMaxColumns(int maxColumns) { + mMaxColumns = maxColumns; + boolean changed = false; + for (int i = 0; i < mPages.size(); i++) { + if (mPages.get(i).setMaxColumns(maxColumns)) { + changed = true; + mDistributeTiles = true; + } + } + return changed; + } + + /** + * Set the amount of excess space that we gave this view compared to the actual available + * height. This is because this view is in a scrollview. + */ + public void setExcessHeight(int excessHeight) { + mExcessHeight = excessHeight; + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int nTiles = mTiles.size(); // If we have no reason to recalculate the number of rows, skip this step. In particular, // if the height passed by its parent is the same as the last time, we try not to remeasure. - if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec)) { + if (mDistributeTiles || mLastMaxHeight != MeasureSpec.getSize(heightMeasureSpec) + || mLastExcessHeight != mExcessHeight) { mLastMaxHeight = MeasureSpec.getSize(heightMeasureSpec); + mLastExcessHeight = mExcessHeight; // Only change the pages if the number of rows or columns (from updateResources) has // changed or the tiles have changed - if (mPages.get(0).updateMaxRows(heightMeasureSpec, nTiles) || mDistributeTiles) { + int availableHeight = mLastMaxHeight - mExcessHeight; + if (mPages.get(0).updateMaxRows(availableHeight, nTiles) || mDistributeTiles) { mDistributeTiles = false; distributeTiles(); } @@ -384,6 +423,22 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { return mPages.get(0).mColumns; } + /** + * Gets the number of pages in this paged tile layout + */ + public int getNumPages() { + final int nTiles = mTiles.size(); + // We should always have at least one page, even if it's empty. + int numPages = Math.max(nTiles / mPages.get(0).maxTiles(), 1); + + // Add one more not full page if needed + if (nTiles > numPages * mPages.get(0).maxTiles()) { + numPages++; + } + + return numPages; + } + public int getNumVisibleTiles() { if (mPages.size() == 0) return 0; TilePage currentPage = mPages.get(getCurrentPageNumber()); @@ -485,14 +540,6 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { // up. return Math.max(mColumns * mRows, 1); } - - @Override - public boolean updateResources() { - final int sidePadding = getContext().getResources().getDimensionPixelSize( - R.dimen.notification_side_paddings); - setPadding(sidePadding, 0, sidePadding, 0); - return super.updateResources(); - } } private final PagerAdapter mAdapter = new PagerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index ce002297e1a1..bc8f5a8fb652 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -18,6 +18,7 @@ import android.util.Log; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnLayoutChangeListener; +import android.widget.ScrollView; import com.android.systemui.Dependency; import com.android.systemui.plugins.qs.QS; @@ -30,7 +31,6 @@ import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.TouchAnimator.Listener; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; -import com.android.systemui.util.Utils; import java.util.ArrayList; import java.util.Collection; @@ -66,6 +66,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private TouchAnimator mNonfirstPageAnimator; private TouchAnimator mNonfirstPageDelayedAnimator; private TouchAnimator mBrightnessAnimator; + private boolean mNeedsAnimatorUpdate = false; private boolean mOnKeyguard; @@ -98,6 +99,12 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha updateAnimators(); } + + public void onQsScrollingChanged() { + // Lazily update animators whenever the scrolling changes + mNeedsAnimatorUpdate = true; + } + public void setOnKeyguard(boolean onKeyguard) { mOnKeyguard = onKeyguard; updateQQSVisibility(); @@ -172,6 +179,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } private void updateAnimators() { + mNeedsAnimatorUpdate = false; TouchAnimator.Builder firstPageBuilder = new Builder(); TouchAnimator.Builder translationXBuilder = new Builder(); TouchAnimator.Builder translationYBuilder = new Builder(); @@ -286,13 +294,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha .setListener(this) .build(); // Fade in the tiles/labels as we reach the final position. - mFirstPageDelayedAnimator = new TouchAnimator.Builder() + Builder builder = new Builder() .setStartDelay(EXPANDED_TILE_DELAY) - .addFloat(tileLayout, "alpha", 0, 1) - .addFloat(mQsPanel.getDivider(), "alpha", 0, 1) - .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build(); - mAllViews.add(mQsPanel.getDivider()); - mAllViews.add(mQsPanel.getFooter().getView()); + .addFloat(tileLayout, "alpha", 0, 1); + if (mQsPanel.getSecurityFooter() != null) { + builder.addFloat(mQsPanel.getSecurityFooter().getView(), "alpha", 0, 1); + } + mFirstPageDelayedAnimator = builder.build(); + if (mQsPanel.getSecurityFooter() != null) { + mAllViews.add(mQsPanel.getSecurityFooter().getView()); + } float px = 0; float py = 1; if (tiles.size() <= 3) { @@ -308,7 +319,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } mNonfirstPageAnimator = new TouchAnimator.Builder() .addFloat(mQuickQsPanel, "alpha", 1, 0) - .addFloat(mQsPanel.getDivider(), "alpha", 0, 1) .setListener(mNonFirstPageListener) .setEndDelay(.5f) .build(); @@ -339,10 +349,18 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha loc1[0] += view.getLeft(); loc1[1] += view.getTop(); } + if (!(view instanceof PagedTileLayout)) { + // Remove the scrolling position of all scroll views other than the viewpager + loc1[0] -= view.getScrollX(); + loc1[1] -= view.getScrollY(); + } getRelativePositionInt(loc1, (View) view.getParent(), parent); } public void setPosition(float position) { + if (mNeedsAnimatorUpdate) { + updateAnimators(); + } if (mFirstPageAnimator == null) return; if (mOnKeyguard) { if (mShowCollapsedOnKeyguard) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 1c3b6850afc1..0332bc3e0618 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -43,6 +43,7 @@ public class QSContainerImpl extends FrameLayout { private float mQsExpansion; private QSCustomizer mQSCustomizer; private View mDragHandle; + private View mQSPanelContainer; private View mBackground; private View mBackgroundGradient; @@ -61,6 +62,7 @@ public class QSContainerImpl extends FrameLayout { protected void onFinishInflate() { super.onFinishInflate(); mQSPanel = findViewById(R.id.quick_settings_panel); + mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view); mQSDetail = findViewById(R.id.qs_detail); mHeader = findViewById(R.id.header); mQSCustomizer = findViewById(R.id.qs_customize); @@ -95,7 +97,7 @@ public class QSContainerImpl extends FrameLayout { Configuration config = getResources().getConfiguration(); boolean navBelow = config.smallestScreenWidthDp >= 600 || config.orientation != Configuration.ORIENTATION_LANDSCAPE; - MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanel.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams(); // The footer is pinned to the bottom of QSPanel (same bottoms), therefore we don't need to // subtract its height. We do not care if the collapsed notifications fit in the screen. @@ -109,12 +111,11 @@ public class QSContainerImpl extends FrameLayout { + layoutParams.rightMargin; final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding, layoutParams.width); - // Measure with EXACTLY. That way, PagedTileLayout will only use excess height and will be - // measured last, after other views and padding is accounted for. - mQSPanel.measure(qsPanelWidthSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.EXACTLY)); - int width = mQSPanel.getMeasuredWidth() + padding; + mQSPanelContainer.measure(qsPanelWidthSpec, + MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST)); + int width = mQSPanelContainer.getMeasuredWidth() + padding; int height = layoutParams.topMargin + layoutParams.bottomMargin - + mQSPanel.getMeasuredHeight() + getPaddingBottom(); + + mQSPanelContainer.getMeasuredHeight() + getPaddingBottom(); super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); // QSCustomizer will always be the height of the screen, but do this after @@ -130,7 +131,7 @@ public class QSContainerImpl extends FrameLayout { // Do not measure QSPanel again when doing super.onMeasure. // This prevents the pages in PagedTileLayout to be remeasured with a different (incorrect) // size to the one used for determining the number of rows and then the number of pages. - if (child != mQSPanel) { + if (child != mQSPanelContainer) { super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); } @@ -151,10 +152,10 @@ public class QSContainerImpl extends FrameLayout { } private void updateResources() { - LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams(); + LayoutParams layoutParams = (LayoutParams) mQSPanelContainer.getLayoutParams(); layoutParams.topMargin = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.quick_qs_offset_height); - mQSPanel.setLayoutParams(layoutParams); + mQSPanelContainer.setLayoutParams(layoutParams); mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); mContentPaddingStart = getResources().getDimensionPixelSize( @@ -185,7 +186,7 @@ public class QSContainerImpl extends FrameLayout { mQSDetail.setBottom(getTop() + height); // Pin the drag handle to the bottom of the panel. mDragHandle.setTranslationY(height - mDragHandle.getHeight()); - mBackground.setTop(mQSPanel.getTop()); + mBackground.setTop(mQSPanelContainer.getTop()); mBackground.setBottom(height); } @@ -223,7 +224,7 @@ public class QSContainerImpl extends FrameLayout { LayoutParams lp = (LayoutParams) view.getLayoutParams(); lp.rightMargin = mSideMargins; lp.leftMargin = mSideMargins; - if (view == mQSPanel) { + if (view == mQSPanelContainer) { // QS panel lays out some of its content full width mQSPanel.setContentMargins(mContentPaddingStart, mContentPaddingEnd); } else if (view == mHeader) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index fc8e36ff22cf..c4bb4e86e41e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -218,7 +218,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, .addFloat(mActionsContainer, "alpha", 0, 1) .addFloat(mEditContainer, "alpha", 0, 1) .addFloat(mPageIndicator, "alpha", 0, 1) - .setStartDelay(0.15f) + .setStartDelay(0.9f) .build(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 6af9e1ef4571..f1bb8996e181 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -24,7 +24,6 @@ import android.os.Bundle; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -72,6 +71,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca protected QuickStatusBarHeader mHeader; private QSCustomizer mQSCustomizer; protected QSPanel mQSPanel; + protected NonInterceptingScrollView mQSPanelScrollView; private QSDetail mQSDetail; private boolean mListening; private QSContainerImpl mContainer; @@ -122,8 +122,20 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mQSPanel = view.findViewById(R.id.quick_settings_panel); + mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view); + mQSPanelScrollView.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + updateQsBounds(); + }); + mQSPanelScrollView.setOnScrollChangeListener( + (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + // Lazily update animators whenever the scrolling changes + mQSAnimator.onQsScrollingChanged(); + mHeader.setExpandedScrollAmount(scrollY); + }); mQSDetail = view.findViewById(R.id.qs_detail); mHeader = view.findViewById(R.id.header); + mQSPanel.setHeaderContainer(view.findViewById(R.id.header_text_container)); mFooter = view.findViewById(R.id.qs_footer); mContainer = view.findViewById(id.quick_settings_container); @@ -133,8 +145,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter); - mQSAnimator = new QSAnimator(this, - mHeader.findViewById(R.id.quick_qs_panel), mQSPanel); + mQSAnimator = new QSAnimator(this, mHeader.findViewById(R.id.quick_qs_panel), mQSPanel); + mQSCustomizer = view.findViewById(R.id.qs_customize); mQSCustomizer.setQs(this); @@ -319,11 +331,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - return isCustomizing(); - } - - @Override public void setHeaderClickable(boolean clickable) { if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable); } @@ -394,7 +401,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mLastViewHeight = currentHeight; boolean fullyExpanded = expansion == 1; - int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom(); + boolean fullyCollapsed = expansion == 0.0f; + int heightDiff = mQSPanelScrollView.getBottom() - mHeader.getBottom() + + mHeader.getPaddingBottom(); float panelTranslationY = translationScaleY * heightDiff; // Let the views animate their contents correctly by giving them the necessary context. @@ -403,19 +412,19 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); mQSPanel.getQsTileRevealController().setExpansion(expansion); mQSPanel.getTileLayout().setExpansion(expansion); - mQSPanel.setTranslationY(translationScaleY * heightDiff); + mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff); + if (fullyCollapsed) { + mQSPanelScrollView.setScrollY(0); + } mQSDetail.setFullyExpanded(fullyExpanded); - if (fullyExpanded) { - // Always draw within the bounds of the view when fully expanded. - mQSPanel.setClipBounds(null); - } else { + if (!fullyExpanded) { // Set bounds on the QS panel so it doesn't run over the header when animating. - mQsBounds.top = (int) -mQSPanel.getTranslationY(); - mQsBounds.right = mQSPanel.getWidth(); - mQsBounds.bottom = mQSPanel.getHeight(); - mQSPanel.setClipBounds(mQsBounds); + mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY(); + mQsBounds.right = mQSPanelScrollView.getWidth(); + mQsBounds.bottom = mQSPanelScrollView.getHeight(); } + updateQsBounds(); if (mQSAnimator != null) { mQSAnimator.setPosition(expansion); @@ -423,31 +432,63 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca updateMediaPositions(); } + private void updateQsBounds() { + if (mLastQSExpansion == 1.0f) { + // Fully expanded, let's set the layout bounds as clip bounds. This is necessary because + // it's a scrollview and otherwise wouldn't be clipped. + mQsBounds.set(0, 0, mQSPanelScrollView.getWidth(), mQSPanelScrollView.getHeight()); + } + mQSPanelScrollView.setClipBounds(mQsBounds); + } + private void updateMediaPositions() { if (Utils.useQsMediaPlayer(getContext())) { mContainer.getLocationOnScreen(mTmpLocation); float absoluteBottomPosition = mTmpLocation[1] + mContainer.getHeight(); - pinToBottom(absoluteBottomPosition, mQSPanel.getMediaHost()); - pinToBottom(absoluteBottomPosition - mHeader.getPaddingBottom(), - mHeader.getHeaderQsPanel().getMediaHost()); + // The Media can be scrolled off screen by default, let's offset it + float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY() + + mQSPanelScrollView.getScrollRange(); + // The expanded media host should never move below the laid out position + pinToBottom(expandedMediaPosition, mQSPanel.getMediaHost(), true /* expanded */); + // The expanded media host should never move above the laid out position + pinToBottom(absoluteBottomPosition, mHeader.getHeaderQsPanel().getMediaHost(), + false /* expanded */); } } - private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost) { + private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) { View hostView = mediaHost.getHostView(); if (mLastQSExpansion > 0) { - ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) hostView.getLayoutParams(); - float targetPosition = absoluteBottomPosition - params.bottomMargin + float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView) - hostView.getHeight(); float currentPosition = mediaHost.getCurrentBounds().top - hostView.getTranslationY(); - hostView.setTranslationY(targetPosition - currentPosition); + float translationY = targetPosition - currentPosition; + if (expanded) { + // Never go below the laid out position. This is necessary since the qs panel can + // change in height and we don't want to ever go below it's position + translationY = Math.min(translationY, 0); + } else { + translationY = Math.max(translationY, 0); + } + hostView.setTranslationY(translationY); } else { hostView.setTranslationY(0); } } + private float getTotalBottomMargin(View startView) { + int result = 0; + View child = startView; + View parent = (View) startView.getParent(); + while (!(parent instanceof QSContainerImpl) && parent != null) { + result += parent.getHeight() - child.getBottom(); + child = parent; + parent = (View) parent.getParent(); + } + return result; + } + private boolean headerWillBeAnimating() { return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard && !isKeyguardShowing(); @@ -504,7 +545,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void notifyCustomizeChanged() { // The customize state changed, so our height changed. mContainer.updateExpansion(); - mQSPanel.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); + mQSPanelScrollView.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE + : View.INVISIBLE); mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE); // Let the panel know the position changed and it needs to update where notifications // and whatnot are. @@ -521,9 +563,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca return getView().getHeight(); } if (mQSDetail.isClosingDetail()) { - LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams(); + LayoutParams layoutParams = (LayoutParams) mQSPanelScrollView.getLayoutParams(); int panelHeight = layoutParams.topMargin + layoutParams.bottomMargin + - + mQSPanel.getMeasuredHeight(); + + mQSPanelScrollView.getMeasuredHeight(); return panelHeight + getView().getPaddingBottom(); } else { return getView().getMeasuredHeight(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 78448785fe2f..5021e0019d57 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -16,10 +16,10 @@ package com.android.systemui.qs; -import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; import static com.android.systemui.util.Utils.useQsMediaPlayer; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; @@ -29,8 +29,8 @@ import android.metrics.LogMaker; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.service.quicksettings.Tile; import android.util.AttributeSet; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -39,7 +39,7 @@ import android.widget.LinearLayout; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settingslib.Utils; +import com.android.internal.widget.RemeasuringLinearLayout; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -83,38 +83,65 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); private final BroadcastDispatcher mBroadcastDispatcher; protected final MediaHost mMediaHost; + + /** + * The index where the content starts that needs to be moved between parents + */ + private final int mMovableContentStartIndex; private String mCachedSpecs = ""; - protected final View mBrightnessView; + + @Nullable + protected View mBrightnessView; + @Nullable + private BrightnessController mBrightnessController; + private final H mHandler = new H(); private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); - private final QSTileRevealController mQsTileRevealController; + private QSTileRevealController mQsTileRevealController; /** Whether or not the QS media player feature is enabled. */ protected boolean mUsingMediaPlayer; + private int mVisualMarginStart; + private int mVisualMarginEnd; protected boolean mExpanded; protected boolean mListening; private QSDetail.Callback mCallback; - private BrightnessController mBrightnessController; private final DumpManager mDumpManager; private final QSLogger mQSLogger; protected final UiEventLogger mUiEventLogger; protected QSTileHost mHost; - protected QSSecurityFooter mFooter; + @Nullable + protected QSSecurityFooter mSecurityFooter; + + @Nullable + protected View mFooter; + + @Nullable + private ViewGroup mHeaderContainer; private PageIndicator mFooterPageIndicator; private boolean mGridContentVisible = true; private int mContentMarginStart; private int mContentMarginEnd; private int mVisualTilePadding; - - protected QSTileLayout mTileLayout; + private boolean mUsingHorizontalLayout; private QSCustomizer mCustomizePanel; private Record mDetailRecord; private BrightnessMirrorController mBrightnessMirrorController; - private View mDivider; + private LinearLayout mHorizontalLinearLayout; + private LinearLayout mHorizontalContentContainer; + + // Only used with media + private QSTileLayout mHorizontalTileLayout; + protected QSTileLayout mRegularTileLayout; + protected QSTileLayout mTileLayout; + private int mLastOrientation = -1; + private int mMediaTotalBottomMargin; + private int mFooterMarginStartHorizontal; + @Inject public QSPanel( @@ -128,7 +155,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne ) { super(context, attrs); mUsingMediaPlayer = useQsMediaPlayer(context); + mMediaTotalBottomMargin = getResources().getDimensionPixelSize( + R.dimen.quick_settings_bottom_margin_media); mMediaHost = mediaHost; + mMediaHost.setVisibleChangedListener((visible) -> { + switchTileLayout(); + return null; + }); mContext = context; mQSLogger = qsLogger; mDumpManager = dumpManager; @@ -137,71 +170,104 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne setOrientation(VERTICAL); - mBrightnessView = LayoutInflater.from(mContext).inflate( - R.layout.quick_settings_brightness_dialog, this, false); - addView(mBrightnessView); - - mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( - R.layout.qs_paged_tile_layout, this, false); + addViewsAboveTiles(); + mMovableContentStartIndex = getChildCount(); + mRegularTileLayout = createRegularTileLayout(); + + if (mUsingMediaPlayer) { + mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext); + mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL); + mHorizontalLinearLayout.setClipChildren(false); + mHorizontalLinearLayout.setClipToPadding(false); + + mHorizontalContentContainer = new RemeasuringLinearLayout(mContext); + mHorizontalContentContainer.setOrientation(LinearLayout.VERTICAL); + mHorizontalContentContainer.setClipChildren(false); + mHorizontalContentContainer.setClipToPadding(false); + + mHorizontalTileLayout = createHorizontalTileLayout(); + LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1); + int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing); + lp.setMarginStart(0); + lp.setMarginEnd(marginSize); + lp.gravity = Gravity.CENTER_VERTICAL; + mHorizontalLinearLayout.addView(mHorizontalContentContainer, lp); + + lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0, 1); + addView(mHorizontalLinearLayout, lp); + + initMediaHostState(); + } + addSecurityFooter(); + if (mRegularTileLayout instanceof PagedTileLayout) { + mQsTileRevealController = new QSTileRevealController(mContext, this, + (PagedTileLayout) mRegularTileLayout); + } mQSLogger.logAllTilesChangeListening(mListening, getDumpableTag(), mCachedSpecs); - mTileLayout.setListening(mListening); - addView((View) mTileLayout); - - mQsTileRevealController = new QSTileRevealController(mContext, this, - (PagedTileLayout) mTileLayout); - - addDivider(); - - mFooter = new QSSecurityFooter(this, context); - addView(mFooter.getView()); - updateResources(); + } + protected void addSecurityFooter() { + mSecurityFooter = new QSSecurityFooter(this, mContext); + } + + protected void addViewsAboveTiles() { + mBrightnessView = LayoutInflater.from(mContext).inflate( + R.layout.quick_settings_brightness_dialog, this, false); + addView(mBrightnessView); mBrightnessController = new BrightnessController(getContext(), findViewById(R.id.brightness_slider), mBroadcastDispatcher); } - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - // Add media carousel at the end - if (useQsMediaPlayer(getContext())) { - addMediaHostView(); + protected QSTileLayout createRegularTileLayout() { + if (mRegularTileLayout == null) { + mRegularTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate( + R.layout.qs_paged_tile_layout, this, false); } + return mRegularTileLayout; + } + + + protected QSTileLayout createHorizontalTileLayout() { + return createRegularTileLayout(); } - protected void addMediaHostView() { + protected void initMediaHostState() { mMediaHost.setExpansion(1.0f); mMediaHost.setShowsOnlyActiveMedia(false); + // Reveal player with some parallax (1.0f would also work) + mMediaHost.setGonePivot(0.0f, 0.8f); mMediaHost.init(MediaHierarchyManager.LOCATION_QS); - ViewGroup hostView = mMediaHost.getHostView(); - addView(hostView); - int bottomPadding = getResources().getDimensionPixelSize( - R.dimen.quick_settings_expanded_bottom_margin); - MarginLayoutParams layoutParams = (MarginLayoutParams) hostView.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; - layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.bottomMargin = bottomPadding; - hostView.setLayoutParams(layoutParams); - updateMediaHostContentMargins(); - } - - protected void addDivider() { - mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false); - mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(), - getColorForState(mContext, Tile.STATE_ACTIVE))); - addView(mDivider); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mTileLayout instanceof PagedTileLayout) { + // Since PageIndicator gets measured before PagedTileLayout, we preemptively set the + // # of pages before the measurement pass so PageIndicator is measured appropriately + if (mFooterPageIndicator != null) { + mFooterPageIndicator.setNumPages(((PagedTileLayout) mTileLayout).getNumPages()); + } + + // Allow the UI to be as big as it want's to, we're in a scroll view + int newHeight = 10000; + int availableHeight = MeasureSpec.getSize(heightMeasureSpec); + int excessHeight = newHeight - availableHeight; + // Measure with EXACTLY. That way, The content will only use excess height and will + // be measured last, after other views and padding is accounted for. This only + // works because our Layouts in here remeasure themselves with the exact content + // height. + heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); + ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + // We want all the logic of LinearLayout#onMeasure, and for it to assign the excess space // not used by the other children to PagedTileLayout. However, in this case, LinearLayout // assumes that PagedTileLayout would use all the excess space. This is not the case as // PagedTileLayout height is quantized (because it shows a certain number of rows). // Therefore, after everything is measured, we need to make sure that we add up the correct // total height - super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = getPaddingBottom() + getPaddingTop(); int numChildren = getChildCount(); for (int i = 0; i < numChildren; i++) { @@ -215,10 +281,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne setMeasuredDimension(getMeasuredWidth(), height); } - public View getDivider() { - return mDivider; - } - public QSTileRevealController getQsTileRevealController() { return mQsTileRevealController; } @@ -273,7 +335,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne @Override public void onTuningChanged(String key, String newValue) { - if (QS_SHOW_BRIGHTNESS.equals(key)) { + if (QS_SHOW_BRIGHTNESS.equals(key) && mBrightnessView != null) { updateViewVisibilityForTuningValue(mBrightnessView, newValue); } } @@ -316,6 +378,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne updateBrightnessMirror(); } + @Nullable View getBrightnessView() { return mBrightnessView; } @@ -328,7 +391,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mHost = host; mHost.addCallback(this); setTiles(mHost.getTiles()); - mFooter.setHostEnvironment(host); + if (mSecurityFooter != null) { + mSecurityFooter.setHostEnvironment(host); + } mCustomizePanel = customizer; if (mCustomizePanel != null) { mCustomizePanel.setHost(mHost); @@ -341,18 +406,18 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne * @param pageIndicator indicator to use for page scrolling */ public void setFooterPageIndicator(PageIndicator pageIndicator) { - if (mTileLayout instanceof PagedTileLayout) { + if (mRegularTileLayout instanceof PagedTileLayout) { mFooterPageIndicator = pageIndicator; updatePageIndicator(); } } private void updatePageIndicator() { - if (mTileLayout instanceof PagedTileLayout) { + if (mRegularTileLayout instanceof PagedTileLayout) { if (mFooterPageIndicator != null) { mFooterPageIndicator.setVisibility(View.GONE); - ((PagedTileLayout) mTileLayout).setPageIndicator(mFooterPageIndicator); + ((PagedTileLayout) mRegularTileLayout).setPageIndicator(mFooterPageIndicator); } } } @@ -364,6 +429,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void updateResources() { int tileSize = getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); int tileBg = getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size); + mFooterMarginStartHorizontal = getResources().getDimensionPixelSize( + R.dimen.qs_footer_horizontal_margin); mVisualTilePadding = (int) ((tileSize - tileBg) / 2.0f); updatePadding(); @@ -379,8 +446,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected void updatePadding() { final Resources res = mContext.getResources(); + int padding = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top); + if (mUsingHorizontalLayout) { + // When using the horizontal layout, our space is quite constrained. We therefore + // reduce some of the padding on the top, which makes the brightness bar overlapp, + // but since that has naturally quite a bit of built in padding, that's fine. + padding = (int) (padding * 0.6f); + } setPaddingRelative(getPaddingStart(), - res.getDimensionPixelSize(R.dimen.qs_panel_padding_top), + padding, getPaddingEnd(), res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom)); } @@ -388,10 +462,165 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mFooter.onConfigurationChanged(); + if (mSecurityFooter != null) { + mSecurityFooter.onConfigurationChanged(); + } updateResources(); updateBrightnessMirror(); + + if (newConfig.orientation != mLastOrientation) { + mLastOrientation = newConfig.orientation; + switchTileLayout(); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mFooter = findViewById(R.id.qs_footer); + switchTileLayout(true /* force */); + } + + boolean switchTileLayout() { + return switchTileLayout(false /* force */); + } + + private boolean switchTileLayout(boolean force) { + /** Whether or not the QuickQSPanel currently contains a media player. */ + boolean horizontal = shouldUseHorizontalLayout(); + if (horizontal != mUsingHorizontalLayout || force) { + mUsingHorizontalLayout = horizontal; + View visibleView = horizontal ? mHorizontalLinearLayout : (View) mRegularTileLayout; + View hiddenView = horizontal ? (View) mRegularTileLayout : mHorizontalLinearLayout; + ViewGroup newParent = horizontal ? mHorizontalContentContainer : this; + QSTileLayout newLayout = horizontal ? mHorizontalTileLayout : mRegularTileLayout; + if (hiddenView != null && + (mRegularTileLayout != mHorizontalTileLayout || + hiddenView != mRegularTileLayout)) { + // Only hide the view if the horizontal and the regular view are different, + // otherwise its reattached. + hiddenView.setVisibility(View.GONE); + } + visibleView.setVisibility(View.VISIBLE); + switchAllContentToParent(newParent, newLayout); + reAttachMediaHost(); + if (mTileLayout != null) { + mTileLayout.setListening(false); + for (TileRecord record : mRecords) { + mTileLayout.removeTile(record); + record.tile.removeCallback(record.callback); + } + } + mTileLayout = newLayout; + if (mHost != null) setTiles(mHost.getTiles()); + newLayout.setListening(mListening); + if (needsDynamicRowsAndColumns()) { + newLayout.setMinRows(horizontal ? 2 : 1); + // Let's use 3 columns to match the current layout + newLayout.setMaxColumns(horizontal ? 3 : TileLayout.NO_MAX_COLUMNS); + } + updateTileLayoutMargins(); + updateFooterMargin(); + updateMediaHostContentMargins(); + updateHorizontalLinearLayoutMargins(); + updatePadding(); + return true; + } + return false; + } + + private void updateHorizontalLinearLayoutMargins() { + if (mHorizontalLinearLayout != null && !displayMediaMarginsOnMedia()) { + LayoutParams lp = (LayoutParams) mHorizontalLinearLayout.getLayoutParams(); + lp.bottomMargin = mMediaTotalBottomMargin - getPaddingBottom(); + mHorizontalLinearLayout.setLayoutParams(lp); + } + } + + /** + * @return true if the margin bottom of the media view should be on the media host or false + * if they should be on the HorizontalLinearLayout. Returning {@code false} is useful + * to visually center the tiles in the Media view, which doesn't work when the + * expanded panel actually scrolls. + */ + protected boolean displayMediaMarginsOnMedia() { + return true; + } + + protected boolean needsDynamicRowsAndColumns() { + return true; + } + + private void switchAllContentToParent(ViewGroup parent, QSTileLayout newLayout) { + int index = parent == this ? mMovableContentStartIndex : 0; + + // Let's first move the tileLayout to the new parent, since that should come first. + switchToParent((View) newLayout, parent, index); + index++; + + if (mSecurityFooter != null) { + View view = mSecurityFooter.getView(); + LinearLayout.LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); + if (mUsingHorizontalLayout && mHeaderContainer != null) { + // Adding the security view to the header, that enables us to avoid scrolling + layoutParams.width = 0; + layoutParams.weight = 1.6f; + switchToParent(view, mHeaderContainer, 1 /* always in second place */); + } else { + layoutParams.width = LayoutParams.WRAP_CONTENT; + layoutParams.weight = 0; + switchToParent(view, parent, index); + index++; + } + view.setLayoutParams(layoutParams); + } + + if (mFooter != null) { + // Then the footer with the settings + switchToParent(mFooter, parent, index); + } + } + + private void switchToParent(View child, ViewGroup parent, int index) { + ViewGroup currentParent = (ViewGroup) child.getParent(); + if (currentParent != parent || currentParent.indexOfChild(child) != index) { + if (currentParent != null) { + currentParent.removeView(child); + } + parent.addView(child, index); + } + } + + private boolean shouldUseHorizontalLayout() { + return mUsingMediaPlayer && mMediaHost.getVisible() + && getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; + } + + protected void reAttachMediaHost() { + if (!mUsingMediaPlayer) { + return; + } + boolean horizontal = shouldUseHorizontalLayout(); + ViewGroup host = mMediaHost.getHostView(); + ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this; + ViewGroup currentParent = (ViewGroup) host.getParent(); + if (currentParent != newParent) { + if (currentParent != null) { + currentParent.removeView(host); + } + newParent.addView(host); + LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams(); + layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; + layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.weight = horizontal ? 1.2f : 0; + // Add any bottom margin, such that the total spacing is correct. This is only + // necessary if the view isn't horizontal, since otherwise the padding is + // carried in the parent of this view (to ensure correct vertical alignment) + layoutParams.bottomMargin = !horizontal || displayMediaMarginsOnMedia() + ? mMediaTotalBottomMargin - getPaddingBottom() : 0; + } } public void updateBrightnessMirror() { @@ -457,13 +686,18 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne public void setListening(boolean listening, boolean expanded) { setListening(listening && expanded); - getFooter().setListening(listening); + if (mSecurityFooter != null) { + mSecurityFooter.setListening(listening); + } // Set the listening as soon as the QS fragment starts listening regardless of the expansion, // so it will update the current brightness before the slider is visible. setBrightnessListening(listening); } public void setBrightnessListening(boolean listening) { + if (mBrightnessController == null) { + return; + } if (listening) { mBrightnessController.registerCallbacks(); } else { @@ -472,11 +706,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } public void refreshAllTiles() { - mBrightnessController.checkRestrictionAndSetEnabled(); + if (mBrightnessController != null) { + mBrightnessController.checkRestrictionAndSetEnabled(); + } for (TileRecord r : mRecords) { r.tile.refreshState(); } - mFooter.refreshState(); + if (mSecurityFooter != null) { + mSecurityFooter.refreshState(); + } } public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) { @@ -728,12 +966,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne return null; } - public QSSecurityFooter getFooter() { - return mFooter; + @Nullable + public QSSecurityFooter getSecurityFooter() { + return mSecurityFooter; } public void showDeviceMonitoringDialog() { - mFooter.showDeviceMonitoringDialog(); + if (mSecurityFooter != null) { + mSecurityFooter.showDeviceMonitoringDialog(); + } } public void setContentMargins(int startMargin, int endMargin) { @@ -744,6 +985,24 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne updateTileLayoutMargins(mContentMarginStart - mVisualTilePadding, mContentMarginEnd - mVisualTilePadding); updateMediaHostContentMargins(); + updateFooterMargin(); + } + + private void updateFooterMargin() { + if (mFooter != null) { + int footerMargin = 0; + int indicatorMargin = 0; + if (mUsingHorizontalLayout) { + footerMargin = mFooterMarginStartHorizontal; + indicatorMargin = footerMargin - mVisualMarginEnd; + } + updateMargins(mFooter, footerMargin, 0); + // The page indicator isn't centered anymore because of the visual positioning. + // Let's fix it by adding some margin + if (mFooterPageIndicator != null) { + updateMargins(mFooterPageIndicator, 0, indicatorMargin); + } + } } /** @@ -754,16 +1013,30 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne * @param visualMarginEnd the visual end margin of the tile, adjusted for local insets * to the tile. This can be set on a tileLayout */ - protected void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { - updateMargins((View) mTileLayout, visualMarginStart, visualMarginEnd); + private void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { + mVisualMarginStart = visualMarginStart; + mVisualMarginEnd = visualMarginEnd; + updateTileLayoutMargins(); + } + + private void updateTileLayoutMargins() { + int marginEnd = mVisualMarginEnd; + if (mUsingHorizontalLayout) { + marginEnd = 0; + } + updateMargins((View) mTileLayout, mVisualMarginStart, marginEnd); } /** * Update the margins of the media hosts */ protected void updateMediaHostContentMargins() { - if (mUsingMediaPlayer && mMediaHost != null) { - updateMargins(mMediaHost.getHostView(), mContentMarginStart, mContentMarginEnd); + if (mUsingMediaPlayer) { + int marginStart = mContentMarginStart; + if (mUsingHorizontalLayout) { + marginStart = 0; + } + updateMargins(mMediaHost.getHostView(), marginStart, mContentMarginEnd); } } @@ -785,6 +1058,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne return mMediaHost; } + /** + * Set the header container of quick settings. + */ + public void setHeaderContainer(@NonNull ViewGroup headerContainer) { + mHeaderContainer = headerContainer; + } + private class H extends Handler { private static final int SHOW_DETAIL = 1; private static final int SET_TILE_VISIBILITY = 2; @@ -812,6 +1092,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } } + protected static class Record { DetailAdapter detailAdapter; int x; @@ -841,6 +1122,26 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne void setListening(boolean listening); + /** + * Set the minimum number of rows to show + * + * @param minRows the minimum. + */ + default boolean setMinRows(int minRows) { + return false; + } + + /** + * Set the max number of collums to show + * + * @param maxColumns the maximum + * + * @return true if the number of visible columns has changed. + */ + default boolean setMaxColumns(int maxColumns) { + return false; + } + default void setExpansion(float expansion) {} int getNumVisibleTiles(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index 476af20b78f4..7bcaa7263cc4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.policy.SecurityController; public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener { protected static final String TAG = "QSSecurityFooter"; protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG_FORCE_VISIBLE = false; private final View mRootView; private final TextView mFooterText; @@ -60,7 +61,6 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic private final SecurityController mSecurityController; private final ActivityStarter mActivityStarter; private final Handler mMainHandler; - private final View mDivider; private final UserManager mUm; @@ -85,7 +85,6 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic mActivityStarter = Dependency.get(ActivityStarter.class); mSecurityController = Dependency.get(SecurityController.class); mHandler = new H(Dependency.get(Dependency.BG_LOOPER)); - mDivider = qsPanel == null ? null : qsPanel.getDivider(); mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); } @@ -177,7 +176,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled, String vpnName, String vpnNameWorkProfile, CharSequence organizationName, CharSequence workProfileName) { - if (isDeviceManaged) { + if (isDeviceManaged || DEBUG_FORCE_VISIBLE) { if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) { if (organizationName == null) { return mContext.getString( @@ -451,8 +450,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic if (mFooterTextContent != null) { mFooterText.setText(mFooterTextContent); } - mRootView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE); - if (mDivider != null) mDivider.setVisibility(mIsVisible ? View.GONE : View.VISIBLE); + mRootView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 94b4cee92965..affb7b91b6a5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -24,7 +24,6 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; -import android.view.ViewGroup; import android.widget.LinearLayout; import com.android.internal.logging.UiEventLogger; @@ -61,15 +60,7 @@ public class QuickQSPanel extends QSPanel { private boolean mDisabledByPolicy; private int mMaxTiles; protected QSPanel mFullPanel; - /** Whether or not the QuickQSPanel currently contains a media player. */ - private boolean mShowHorizontalTileLayout; - private LinearLayout mHorizontalLinearLayout; - // Only used with media - private QSTileLayout mHorizontalTileLayout; - private QSTileLayout mRegularTileLayout; - private int mLastOrientation = -1; - private int mMediaBottomMargin; @Inject public QuickQSPanel( @@ -82,59 +73,8 @@ public class QuickQSPanel extends QSPanel { UiEventLogger uiEventLogger ) { super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger); - if (mFooter != null) { - removeView(mFooter.getView()); - } - if (mTileLayout != null) { - for (int i = 0; i < mRecords.size(); i++) { - mTileLayout.removeTile(mRecords.get(i)); - } - removeView((View) mTileLayout); - } - mMediaBottomMargin = getResources().getDimensionPixelSize( - R.dimen.quick_settings_media_extra_bottom_margin); - if (mUsingMediaPlayer) { - mHorizontalLinearLayout = new LinearLayout(mContext); - mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL); - mHorizontalLinearLayout.setClipChildren(false); - mHorizontalLinearLayout.setClipToPadding(false); - - DoubleLineTileLayout horizontalTileLayout = new DoubleLineTileLayout(context, - mUiEventLogger); - horizontalTileLayout.setPaddingRelative( - horizontalTileLayout.getPaddingStart(), - horizontalTileLayout.getPaddingTop(), - horizontalTileLayout.getPaddingEnd(), - mContext.getResources().getDimensionPixelSize( - R.dimen.qqs_horizonal_tile_padding_bottom)); - mHorizontalTileLayout = horizontalTileLayout; - mRegularTileLayout = new HeaderTileLayout(context, mUiEventLogger); - LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1); - int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing); - lp.setMarginStart(0); - lp.setMarginEnd(marginSize); - lp.gravity = Gravity.CENTER_VERTICAL; - mHorizontalLinearLayout.addView((View) mHorizontalTileLayout, lp); - - sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); - - boolean useHorizontal = shouldUseHorizontalTileLayout(); - mTileLayout = useHorizontal ? mHorizontalTileLayout : mRegularTileLayout; - mTileLayout.setListening(mListening); - addView(mHorizontalLinearLayout, 0 /* Between brightness and footer */); - ((View) mRegularTileLayout).setVisibility(!useHorizontal ? View.VISIBLE : View.GONE); - mHorizontalLinearLayout.setVisibility(useHorizontal ? View.VISIBLE : View.GONE); - addView((View) mRegularTileLayout, 0); - super.setPadding(0, 0, 0, 0); - applyBottomMargin((View) mRegularTileLayout); - } else { - sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); - mTileLayout = new HeaderTileLayout(context, mUiEventLogger); - mTileLayout.setListening(mListening); - addView((View) mTileLayout, 0 /* Between brightness and footer */); - super.setPadding(0, 0, 0, 0); - applyBottomMargin((View) mTileLayout); - } + sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); + applyBottomMargin((View) mRegularTileLayout); } private void applyBottomMargin(View view) { @@ -144,57 +84,47 @@ public class QuickQSPanel extends QSPanel { view.setLayoutParams(layoutParams); } - private void reAttachMediaHost() { - if (mMediaHost == null) { - return; - } - boolean horizontal = shouldUseHorizontalTileLayout(); - ViewGroup host = mMediaHost.getHostView(); - ViewGroup newParent = horizontal ? mHorizontalLinearLayout : this; - ViewGroup currentParent = (ViewGroup) host.getParent(); - if (currentParent != newParent) { - if (currentParent != null) { - currentParent.removeView(host); - } - newParent.addView(host); - LinearLayout.LayoutParams layoutParams = (LayoutParams) host.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; - layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.weight = horizontal ? 1.5f : 0; - layoutParams.bottomMargin = mMediaBottomMargin; - } + @Override + protected void addSecurityFooter() { + // No footer needed + } + + @Override + protected void addViewsAboveTiles() { + // Nothing to add above the tiles + } + + @Override + protected TileLayout createRegularTileLayout() { + return new QuickQSPanel.HeaderTileLayout(mContext, mUiEventLogger); } @Override - protected void addMediaHostView() { - mMediaHost.setVisibleChangedListener((visible) -> { - switchTileLayout(); - return null; - }); + protected QSTileLayout createHorizontalTileLayout() { + return new DoubleLineTileLayout(mContext, mUiEventLogger); + } + + @Override + protected void initMediaHostState() { mMediaHost.setExpansion(0.0f); mMediaHost.setShowsOnlyActiveMedia(true); mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); - reAttachMediaHost(); - updateMediaHostContentMargins(); } @Override - protected void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { - if (mUsingMediaPlayer) { - updateMargins((View) mRegularTileLayout, visualMarginStart, visualMarginEnd); - updateMargins((View) mHorizontalTileLayout, visualMarginStart, 0); - } else { - updateMargins((View) mTileLayout, visualMarginStart, visualMarginEnd); - } + protected boolean needsDynamicRowsAndColumns() { + return false; // QQS always have the same layout } @Override - protected void updatePadding() { - // QS Panel is setting a top padding by default, which we don't need. + protected boolean displayMediaMarginsOnMedia() { + // Margins should be on the container to visually center the view + return false; } @Override - protected void addDivider() { + protected void updatePadding() { + // QS Panel is setting a top padding by default, which we don't need. } @Override @@ -237,60 +167,6 @@ public class QuickQSPanel extends QSPanel { } @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - if (newConfig.orientation != mLastOrientation) { - mLastOrientation = newConfig.orientation; - switchTileLayout(); - } - } - - boolean switchTileLayout() { - if (!mUsingMediaPlayer) return false; - mShowHorizontalTileLayout = shouldUseHorizontalTileLayout(); - if (mShowHorizontalTileLayout && mHorizontalLinearLayout.getVisibility() == View.GONE) { - mHorizontalLinearLayout.setVisibility(View.VISIBLE); - ((View) mRegularTileLayout).setVisibility(View.GONE); - mTileLayout.setListening(false); - for (TileRecord record : mRecords) { - mTileLayout.removeTile(record); - record.tile.removeCallback(record.callback); - } - mTileLayout = mHorizontalTileLayout; - if (mHost != null) setTiles(mHost.getTiles()); - mTileLayout.setListening(mListening); - reAttachMediaHost(); - return true; - } else if (!mShowHorizontalTileLayout - && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) { - mHorizontalLinearLayout.setVisibility(View.GONE); - ((View) mRegularTileLayout).setVisibility(View.VISIBLE); - mTileLayout.setListening(false); - for (TileRecord record : mRecords) { - mTileLayout.removeTile(record); - record.tile.removeCallback(record.callback); - } - mTileLayout = mRegularTileLayout; - if (mHost != null) setTiles(mHost.getTiles()); - mTileLayout.setListening(mListening); - reAttachMediaHost(); - return true; - } - return false; - } - - private boolean shouldUseHorizontalTileLayout() { - return mMediaHost.getVisible() - && getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; - } - - /** Returns true if this panel currently uses a horizontal tile layout. */ - public boolean usesHorizontalLayout() { - return mShowHorizontalTileLayout; - } - - @Override public void setHost(QSTileHost host, QSCustomizer customizer) { super.setHost(host, customizer); setTiles(mHost.getTiles()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 20e47b2f2fa9..b5afe771926c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -15,6 +15,7 @@ package com.android.systemui.qs; import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; @@ -36,13 +37,14 @@ import android.service.notification.ZenModeConfig; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.view.ContextThemeWrapper; import android.view.DisplayCutout; import android.view.View; +import android.view.ViewGroup; import android.view.WindowInsets; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; @@ -55,6 +57,7 @@ import androidx.lifecycle.LifecycleRegistry; import com.android.settingslib.Utils; import com.android.systemui.BatteryMeterView; import com.android.systemui.DualToneHandler; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; @@ -149,6 +152,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements private int mWaterfallTopInset; private int mCutOutPaddingLeft; private int mCutOutPaddingRight; + private float mExpandedHeaderAlpha = 1.0f; + private float mKeyguardExpansionFraction; @Inject public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, @@ -344,6 +349,15 @@ public class QuickStatusBarHeader extends RelativeLayout implements com.android.internal.R.dimen.quick_qs_offset_height); mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams()); + ViewGroup.LayoutParams lp = getLayoutParams(); + if (mQsDisabled) { + lp.height = resources.getDimensionPixelSize( + com.android.internal.R.dimen.quick_qs_offset_height); + } else { + lp.height = WRAP_CONTENT; + } + setLayoutParams(lp); + updateStatusIconAlphaAnimator(); updateHeaderTextContainerAlphaAnimator(); } @@ -356,7 +370,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements private void updateHeaderTextContainerAlphaAnimator() { mHeaderTextContainerAlphaAnimator = new TouchAnimator.Builder() - .addFloat(mHeaderTextContainerView, "alpha", 0, 0, 1) + .addFloat(mHeaderTextContainerView, "alpha", 0, 0, mExpandedHeaderAlpha) .build(); } @@ -403,6 +417,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements updateResources(); } } + mKeyguardExpansionFraction = keyguardExpansionFraction; } public void disable(int state1, int state2, boolean animate) { @@ -596,4 +611,22 @@ public class QuickStatusBarHeader extends RelativeLayout implements } updateClockPadding(); } + + public void setExpandedScrollAmount(int scrollY) { + // The scrolling of the expanded qs has changed. Since the header text isn't part of it, + // but would overlap content, we're fading it out. + float newAlpha = 1.0f; + if (mHeaderTextContainerView.getHeight() > 0) { + newAlpha = MathUtils.map(0, mHeaderTextContainerView.getHeight() / 2.0f, 1.0f, 0.0f, + scrollY); + newAlpha = Interpolators.ALPHA_OUT.getInterpolation(newAlpha); + } + mHeaderTextContainerView.setScrollY(scrollY); + if (newAlpha != mExpandedHeaderAlpha) { + mExpandedHeaderAlpha = newAlpha; + mHeaderTextContainerView.setAlpha(MathUtils.lerp(0.0f, mExpandedHeaderAlpha, + mKeyguardExpansionFraction)); + updateHeaderTextContainerAlphaAnimator(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 383c29d90a22..694492a33524 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -17,6 +17,7 @@ import java.util.ArrayList; public class TileLayout extends ViewGroup implements QSTileLayout { + public static final int NO_MAX_COLUMNS = 100; private static final float TILE_ASPECT = 1.2f; private static final String TAG = "TileLayout"; @@ -36,6 +37,9 @@ public class TileLayout extends ViewGroup implements QSTileLayout { // Prototyping with less rows private final boolean mLessRows; + private int mMinRows = 1; + private int mMaxColumns = NO_MAX_COLUMNS; + private int mResourceColumns; public TileLayout(Context context) { this(context, null); @@ -64,6 +68,22 @@ public class TileLayout extends ViewGroup implements QSTileLayout { } } + @Override + public boolean setMinRows(int minRows) { + if (mMinRows != minRows) { + mMinRows = minRows; + updateResources(); + return true; + } + return false; + } + + @Override + public boolean setMaxColumns(int maxColumns) { + mMaxColumns = maxColumns; + return updateColumns(); + } + public void addTile(TileRecord tile) { mRecords.add(tile); tile.tile.setListening(this, mListening); @@ -91,21 +111,26 @@ public class TileLayout extends ViewGroup implements QSTileLayout { public boolean updateResources() { final Resources res = mContext.getResources(); - final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); + mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); mCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height); mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top); mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows)); - if (mLessRows) mMaxAllowedRows = Math.max(1, mMaxAllowedRows - 1); - if (mColumns != columns) { - mColumns = columns; + if (mLessRows) mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1); + if (updateColumns()) { requestLayout(); return true; } return false; } + private boolean updateColumns() { + int oldColumns = mColumns; + mColumns = Math.min(mResourceColumns, mMaxColumns); + return oldColumns != mColumns; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // If called with AT_MOST, it will limit the number of rows. If called with UNSPECIFIED @@ -142,18 +167,19 @@ public class TileLayout extends ViewGroup implements QSTileLayout { * Determines the maximum number of rows that can be shown based on height. Clips at a minimum * of 1 and a maximum of mMaxAllowedRows. * - * @param heightMeasureSpec Available height. + * @param allowedHeight The height this view has visually available * @param tilesCount Upper limit on the number of tiles to show. to prevent empty rows. */ - public boolean updateMaxRows(int heightMeasureSpec, int tilesCount) { - final int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mCellMarginTop + public boolean updateMaxRows(int allowedHeight, int tilesCount) { + final int availableHeight = allowedHeight - mCellMarginTop + // Add the cell margin in order to divide easily by the height + the margin below + mCellMarginVertical; final int previousRows = mRows; mRows = availableHeight / (mCellHeight + mCellMarginVertical); - if (mRows >= mMaxAllowedRows) { + if (mRows < mMinRows) { + mRows = mMinRows; + } else if (mRows >= mMaxAllowedRows) { mRows = mMaxAllowedRows; - } else if (mRows <= 1) { - mRows = 1; } if (mRows > (tilesCount + mColumns - 1) / mColumns) { mRows = (tilesCount + mColumns - 1) / mColumns; diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java index f3e2f104621e..f89185e3efa9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java @@ -118,6 +118,7 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation { try { if (mOverviewProxyService.getProxy() != null) { mOverviewProxyService.getProxy().onOverviewToggle(); + mOverviewProxyService.notifyToggleRecentApps(); } } catch (RemoteException e) { Log.e(TAG, "Cannot send toggle recents through proxy service.", e); diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 8a012b8b06f1..790b2585190d 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -200,11 +200,13 @@ public class OverviewProxyService extends CurrentUserTracker implements mInputFocusTransferStartY = event.getY(); mInputFocusTransferStartMillis = event.getEventTime(); statusBar.onInputFocusTransfer( - mInputFocusTransferStarted, 0 /* velocity */); + mInputFocusTransferStarted, false /* cancel */, + 0 /* velocity */); } if (action == ACTION_UP || action == ACTION_CANCEL) { mInputFocusTransferStarted = false; statusBar.onInputFocusTransfer(mInputFocusTransferStarted, + action == ACTION_CANCEL, (event.getY() - mInputFocusTransferStartY) / (event.getEventTime() - mInputFocusTransferStartMillis)); } @@ -692,7 +694,8 @@ public class OverviewProxyService extends CurrentUserTracker implements mHandler.post(()-> { mStatusBarOptionalLazy.ifPresent(statusBarLazy -> { mInputFocusTransferStarted = false; - statusBarLazy.get().onInputFocusTransfer(false, 0 /* velocity */); + statusBarLazy.get().onInputFocusTransfer(false, true /* cancel */, + 0 /* velocity */); }); }); } @@ -871,6 +874,12 @@ public class OverviewProxyService extends CurrentUserTracker implements } } + void notifyToggleRecentApps() { + for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { + mConnectionCallbacks.get(i).onToggleRecentApps(); + } + } + private void updateEnabledState() { mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent, MATCH_SYSTEM_ONLY, @@ -901,6 +910,8 @@ public class OverviewProxyService extends CurrentUserTracker implements default void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {} default void onOverviewShown(boolean fromHome) {} default void onQuickScrubStarted() {} + /** Notify the recents app (overview) is started by 3-button navigation. */ + default void onToggleRecentApps() {} /** Notify changes in the nav bar button alpha */ default void onNavBarButtonAlphaChanged(float alpha, boolean animate) {} default void onSystemUiStateChanged(int sysuiStateFlags) {} diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index f2e8599536ca..87597263168a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -311,7 +311,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis this, REQUEST_CODE, viewIntent, - Intent.FLAG_GRANT_READ_URI_PERMISSION)) + PendingIntent.FLAG_IMMUTABLE)) .addAction(shareAction) .addAction(deleteAction) .setAutoCancel(true) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 57436bc9e675..8c1e1dd0cac7 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -69,6 +69,7 @@ import android.util.Log; import android.util.MathUtils; import android.util.Slog; import android.view.Display; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.SurfaceControl; @@ -120,7 +121,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset public Consumer<Uri> finisher; public GlobalScreenshot.ActionsReadyListener mActionsReadyListener; public int errorMsgResId; - public boolean createDeleteAction; void clearImage() { image = null; @@ -174,6 +174,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133; private static final long SCREENSHOT_FLASH_OUT_DURATION_MS = 217; + // delay before starting to fade in dismiss button + private static final long SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS = 200; private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234; private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500; private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234; @@ -214,7 +216,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private Animator mScreenshotAnimation; private Runnable mOnCompleteRunnable; private Animator mDismissAnimation; - private SavedImageData mImageData; private boolean mInDarkMode = false; private boolean mDirectionLTR = true; private boolean mOrientationPortrait = true; @@ -233,9 +234,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset switch (msg.what) { case MESSAGE_CORNER_TIMEOUT: mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT); - if (mImageData != null) { - mNotificationsController.showSilentScreenshotNotification(mImageData); - } GlobalScreenshot.this.dismissScreenshot("timeout", false); mOnCompleteRunnable.run(); break; @@ -271,7 +269,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, PixelFormat.TRANSLUCENT); mWindowLayoutParams.setTitle("ScreenshotAnimation"); mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; @@ -376,6 +375,20 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset // Inflate the screenshot layout mScreenshotLayout = LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null); + mScreenshotLayout.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + dismissScreenshot("back pressed", true); + return true; + } + return false; + } + }); + // Get focus so that the key events go to the layout. + mScreenshotLayout.setFocusableInTouchMode(true); + mScreenshotLayout.requestFocus(); + mScreenshotAnimatedView = mScreenshotLayout.findViewById(R.id.global_screenshot_animated_view); mScreenshotAnimatedView.setClipToOutline(true); @@ -406,9 +419,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button); mDismissButton.setOnClickListener(view -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL); - if (mImageData != null) { - mNotificationsController.showSilentScreenshotNotification(mImageData); - } dismissScreenshot("dismiss_button", false); mOnCompleteRunnable.run(); }); @@ -437,7 +447,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset data.image = mScreenBitmap; data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; - data.createDeleteAction = false; if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot @@ -449,10 +458,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } - mImageData = null; // make sure we clear the current stored data - mNotificationsController.reset(); - mNotificationsController.setImage(mScreenBitmap); - mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); mSaveInBgTask.execute(); } @@ -669,7 +674,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset */ private void showUiOnActionsReady(SavedImageData imageData) { logSuccessOnActionsReady(imageData); - mImageData = imageData; AccessibilityManager accessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); @@ -773,6 +777,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotAnimatedView.setScaleX(currentScale); mScreenshotAnimatedView.setScaleY(currentScale); + mDismissButton.setAlpha(0); + mDismissButton.setVisibility(View.VISIBLE); + AnimatorSet dropInAnimation = new AnimatorSet(); ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1); flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS); @@ -794,6 +801,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS); float xPositionPct = SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS; + float dismissPct = + SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS; float scalePct = SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS; toCorner.addUpdateListener(animation -> { @@ -821,6 +830,19 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset float yCenter = MathUtils.lerp( startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t)); mScreenshotAnimatedView.setY(yCenter - bounds.height() * currentScaleY / 2f); + + if (t >= dismissPct) { + mDismissButton.setAlpha((t - dismissPct) / (1 - dismissPct)); + float currentX = mScreenshotAnimatedView.getX(); + float currentY = mScreenshotAnimatedView.getY(); + mDismissButton.setY(currentY - mDismissButton.getHeight() / 2f); + if (mDirectionLTR) { + mDismissButton.setX(currentX + + bounds.width() * currentScaleX - mDismissButton.getWidth() / 2f); + } else { + mDismissButton.setX(currentX - mDismissButton.getWidth() / 2f); + } + } }); toCorner.addListener(new AnimatorListenerAdapter() { @@ -845,13 +867,20 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); + mDismissButton.setAlpha(1); + float dismissOffset = mDismissButton.getWidth() / 2f; + float finalDismissX = mDirectionLTR + ? finalPos.x - dismissOffset + bounds.width() * cornerScale / 2f + : finalPos.x - dismissOffset - bounds.width() * cornerScale / 2f; + mDismissButton.setX(finalDismissX); + mDismissButton.setY( + finalPos.y - dismissOffset - bounds.height() * cornerScale / 2f); mScreenshotAnimatedView.setScaleX(1); mScreenshotAnimatedView.setScaleY(1); mScreenshotAnimatedView.setX(finalPos.x - bounds.width() * cornerScale / 2f); mScreenshotAnimatedView.setY(finalPos.y - bounds.height() * cornerScale / 2f); mScreenshotAnimatedView.setVisibility(View.GONE); mScreenshotPreview.setVisibility(View.VISIBLE); - mDismissButton.setVisibility(View.VISIBLE); mScreenshotLayout.forceLayout(); } }); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java deleted file mode 100644 index 0017b1f79b74..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot; - -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Insets; -import android.graphics.PixelFormat; -import android.graphics.PointF; -import android.graphics.Rect; -import android.media.MediaActionSound; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.PowerManager; -import android.util.DisplayMetrics; -import android.view.Display; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.SurfaceControl; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.animation.Interpolator; -import android.widget.ImageView; -import android.widget.Toast; - -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Main; - -import java.util.function.Consumer; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Class for handling device screen shots - * - * @deprecated will be removed when corner flow is complete and tested - */ -@Singleton -@Deprecated -public class GlobalScreenshotLegacy { - - // These strings are used for communicating the action invoked to - // ScreenshotNotificationSmartActionsProvider. - static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; - static final String EXTRA_ID = "android:screenshot_id"; - static final String ACTION_TYPE_DELETE = "Delete"; - static final String ACTION_TYPE_SHARE = "Share"; - static final String ACTION_TYPE_EDIT = "Edit"; - static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; - static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; - - static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id"; - static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification"; - static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip"; - - private static final String TAG = "GlobalScreenshot"; - - private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130; - private static final int SCREENSHOT_DROP_IN_DURATION = 430; - private static final int SCREENSHOT_DROP_OUT_DELAY = 500; - private static final int SCREENSHOT_DROP_OUT_DURATION = 430; - private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370; - private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320; - private static final float BACKGROUND_ALPHA = 0.5f; - private static final float SCREENSHOT_SCALE = 1f; - private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f; - private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f; - private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f; - private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f; - - private final ScreenshotNotificationsController mNotificationsController; - - private Context mContext; - private WindowManager mWindowManager; - private WindowManager.LayoutParams mWindowLayoutParams; - private Display mDisplay; - private DisplayMetrics mDisplayMetrics; - - private Bitmap mScreenBitmap; - private View mScreenshotLayout; - private ScreenshotSelectorView mScreenshotSelectorView; - private ImageView mBackgroundView; - private ImageView mScreenshotView; - private ImageView mScreenshotFlash; - - private AnimatorSet mScreenshotAnimation; - - private float mBgPadding; - private float mBgPaddingScale; - - private AsyncTask<Void, Void, Void> mSaveInBgTask; - - private MediaActionSound mCameraSound; - - /** - * @param context everything needs a context :( - */ - @Inject - public GlobalScreenshotLegacy( - Context context, @Main Resources resources, LayoutInflater layoutInflater, - ScreenshotNotificationsController screenshotNotificationsController) { - mContext = context; - mNotificationsController = screenshotNotificationsController; - - // Inflate the screenshot layout - mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot_legacy, null); - mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_background); - mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy); - - mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_flash); - mScreenshotSelectorView = mScreenshotLayout.findViewById( - R.id.global_screenshot_legacy_selector); - mScreenshotLayout.setFocusable(true); - mScreenshotSelectorView.setFocusable(true); - mScreenshotSelectorView.setFocusableInTouchMode(true); - mScreenshotLayout.setOnTouchListener((v, event) -> { - // Intercept and ignore all touch events - return true; - }); - - // Setup the window that we are going to use - mWindowLayoutParams = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, - WindowManager.LayoutParams.TYPE_SCREENSHOT, - WindowManager.LayoutParams.FLAG_FULLSCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, - PixelFormat.TRANSLUCENT); - mWindowLayoutParams.setTitle("ScreenshotAnimation"); - mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mWindowLayoutParams.setFitInsetsTypes(0 /* types */); - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mDisplay = mWindowManager.getDefaultDisplay(); - mDisplayMetrics = new DisplayMetrics(); - mDisplay.getRealMetrics(mDisplayMetrics); - - // Scale has to account for both sides of the bg - mBgPadding = (float) resources.getDimensionPixelSize( - R.dimen.global_screenshot_legacy_bg_padding); - mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels; - - - // Setup the Camera shutter sound - mCameraSound = new MediaActionSound(); - mCameraSound.load(MediaActionSound.SHUTTER_CLICK); - } - - /** - * Creates a new worker thread and saves the screenshot to the media store. - */ - private void saveScreenshotInWorkerThread( - Consumer<Uri> finisher, - @Nullable GlobalScreenshot.ActionsReadyListener actionsReadyListener) { - GlobalScreenshot.SaveImageInBackgroundData data = - new GlobalScreenshot.SaveImageInBackgroundData(); - data.image = mScreenBitmap; - data.finisher = finisher; - data.mActionsReadyListener = actionsReadyListener; - data.createDeleteAction = true; - if (mSaveInBgTask != null) { - mSaveInBgTask.cancel(false); - } - - mNotificationsController.reset(); - mNotificationsController.setImage(mScreenBitmap); - mNotificationsController.showSavingScreenshotNotification(); - - mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute(); - } - - /** - * Takes a screenshot of the current display and shows an animation. - */ - private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, - boolean navBarVisible, Rect crop) { - int rot = mDisplay.getRotation(); - int width = crop.width(); - int height = crop.height(); - - takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, - statusBarVisible, navBarVisible, null); - } - - private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, boolean statusBarVisible, - boolean navBarVisible, Rect screenboundsOfBitmap) { - mScreenBitmap = screenshot; - if (mScreenBitmap == null) { - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_capture_text); - finisher.accept(null); - return; - } - - // Optimizations - mScreenBitmap.setHasAlpha(false); - mScreenBitmap.prepareToDraw(); - - // Start the post-screenshot animation - startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, - statusBarVisible, navBarVisible, screenboundsOfBitmap); - } - - void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) { - mDisplay.getRealMetrics(mDisplayMetrics); - takeScreenshot(finisher, statusBarVisible, navBarVisible, - new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); - } - - void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, - Insets visibleInsets, int taskId, int userId, ComponentName topComponent, - Consumer<Uri> finisher) { - // TODO: use task Id, userId, topComponent for smart handler - // TODO: use visibleInsets for animation - takeScreenshot(screenshot, finisher, false, false, screenshotScreenBounds); - } - - /** - * Displays a screenshot selector - */ - void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible, - final boolean navBarVisible) { - mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); - mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - ScreenshotSelectorView view = (ScreenshotSelectorView) v; - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - view.startSelection((int) event.getX(), (int) event.getY()); - return true; - case MotionEvent.ACTION_MOVE: - view.updateSelection((int) event.getX(), (int) event.getY()); - return true; - case MotionEvent.ACTION_UP: - view.setVisibility(View.GONE); - mWindowManager.removeView(mScreenshotLayout); - final Rect rect = view.getSelectionRect(); - if (rect != null) { - if (rect.width() != 0 && rect.height() != 0) { - // Need mScreenshotLayout to handle it after the view disappears - mScreenshotLayout.post(() -> takeScreenshot( - finisher, statusBarVisible, navBarVisible, rect)); - } - } - - view.stopSelection(); - return true; - } - - return false; - } - }); - mScreenshotLayout.post(() -> { - mScreenshotSelectorView.setVisibility(View.VISIBLE); - mScreenshotSelectorView.requestFocus(); - }); - } - - /** - * Cancels screenshot request - */ - void stopScreenshot() { - // If the selector layer still presents on screen, we remove it and resets its state. - if (mScreenshotSelectorView.getSelectionRect() != null) { - mWindowManager.removeView(mScreenshotLayout); - mScreenshotSelectorView.stopSelection(); - } - } - - /** - * Clears current screenshot - */ - private void clearScreenshot() { - if (mScreenshotLayout.isAttachedToWindow()) { - mWindowManager.removeView(mScreenshotLayout); - } - - // Clear any references to the bitmap - mScreenBitmap = null; - mScreenshotView.setImageBitmap(null); - mBackgroundView.setVisibility(View.GONE); - mScreenshotView.setVisibility(View.GONE); - mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); - } - - /** - * Starts the animation after taking the screenshot - */ - private void startAnimation(final Consumer<Uri> finisher, int w, int h, - boolean statusBarVisible, boolean navBarVisible, @Nullable Rect screenBoundsOfBitmap) { - // If power save is on, show a toast so there is some visual indication that a screenshot - // has been taken. - PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - if (powerManager.isPowerSaveMode()) { - Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); - } - - // Add the view for the animation - mScreenshotView.setImageBitmap(mScreenBitmap); - mScreenshotLayout.requestFocus(); - - // Setup the animation with the screenshot just taken - if (mScreenshotAnimation != null) { - if (mScreenshotAnimation.isStarted()) { - mScreenshotAnimation.end(); - } - mScreenshotAnimation.removeAllListeners(); - } - - mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); - ValueAnimator screenshotDropInAnim = screenBoundsOfBitmap != null - ? createRectAnimation(screenBoundsOfBitmap) : createScreenshotDropInAnimation(); - ValueAnimator screenshotFadeOutAnim = - createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible); - mScreenshotAnimation = new AnimatorSet(); - mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim); - mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Save the screenshot once we have a bit of time now - saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() { - @Override - void onActionsReady(GlobalScreenshot.SavedImageData actionData) { - if (actionData.uri == null) { - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_capture_text); - } else { - mNotificationsController - .showScreenshotActionsNotification(actionData); - } - } - }); - clearScreenshot(); - } - }); - mScreenshotLayout.post(() -> { - // Play the shutter sound to notify that we've taken a screenshot - mCameraSound.play(MediaActionSound.SHUTTER_CLICK); - - mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mScreenshotView.buildLayer(); - mScreenshotAnimation.start(); - }); - } - - private ValueAnimator createScreenshotDropInAnimation() { - final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION) - / SCREENSHOT_DROP_IN_DURATION); - final float flashDurationPct = 2f * flashPeakDurationPct; - final Interpolator flashAlphaInterpolator = new Interpolator() { - @Override - public float getInterpolation(float x) { - // Flash the flash view in and out quickly - if (x <= flashDurationPct) { - return (float) Math.sin(Math.PI * (x / flashDurationPct)); - } - return 0; - } - }; - final Interpolator scaleInterpolator = new Interpolator() { - @Override - public float getInterpolation(float x) { - // We start scaling when the flash is at it's peak - if (x < flashPeakDurationPct) { - return 0; - } - return (x - flashDurationPct) / (1f - flashDurationPct); - } - }; - - Resources r = mContext.getResources(); - if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES) { - mScreenshotView.getBackground().setTint(Color.BLACK); - } else { - mScreenshotView.getBackground().setTintList(null); - } - - ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); - anim.setDuration(SCREENSHOT_DROP_IN_DURATION); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mBackgroundView.setAlpha(0f); - mBackgroundView.setVisibility(View.VISIBLE); - mScreenshotView.setAlpha(0f); - mScreenshotView.setTranslationX(0f); - mScreenshotView.setTranslationY(0f); - mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale); - mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale); - mScreenshotView.setVisibility(View.VISIBLE); - mScreenshotFlash.setAlpha(0f); - mScreenshotFlash.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationEnd(android.animation.Animator animation) { - mScreenshotFlash.setVisibility(View.GONE); - } - }); - anim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (Float) animation.getAnimatedValue(); - float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale) - - scaleInterpolator.getInterpolation(t) - * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE); - mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA); - mScreenshotView.setAlpha(t); - mScreenshotView.setScaleX(scaleT); - mScreenshotView.setScaleY(scaleT); - mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t)); - } - }); - return anim; - } - - /** - * If a bitmap was supplied to be used as the screenshot, animated from where that bitmap was - * on screen, rather than using the whole screen. - */ - private ValueAnimator createRectAnimation(Rect rect) { - mScreenshotView.setAdjustViewBounds(true); - mScreenshotView.setMaxHeight(rect.height()); - mScreenshotView.setMaxWidth(rect.width()); - - final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION) - / SCREENSHOT_DROP_IN_DURATION); - final float flashDurationPct = 2f * flashPeakDurationPct; - final Interpolator scaleInterpolator = x -> { - // We start scaling when the flash is at it's peak - if (x < flashPeakDurationPct) { - return 0; - } - return (x - flashDurationPct) / (1f - flashDurationPct); - }; - - ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); - anim.setDuration(SCREENSHOT_DROP_IN_DURATION); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mBackgroundView.setAlpha(0f); - mBackgroundView.setVisibility(View.VISIBLE); - mScreenshotView.setAlpha(0f); - mScreenshotView.setElevation(0f); - mScreenshotView.setTranslationX(0f); - mScreenshotView.setTranslationY(0f); - mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale); - mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale); - mScreenshotView.setVisibility(View.VISIBLE); - } - }); - anim.addUpdateListener(animation -> { - float t = (Float) animation.getAnimatedValue(); - float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale) - - scaleInterpolator.getInterpolation(t) - * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE); - mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA); - mScreenshotView.setAlpha(t); - }); - return anim; - } - - private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible, - boolean navBarVisible) { - ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); - anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mBackgroundView.setVisibility(View.GONE); - mScreenshotView.setVisibility(View.GONE); - mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); - } - }); - - if (!statusBarVisible || !navBarVisible) { - // There is no status bar/nav bar, so just fade the screenshot away in place - anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION); - anim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (Float) animation.getAnimatedValue(); - float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) - - t * (SCREENSHOT_DROP_IN_MIN_SCALE - - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE); - mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); - mScreenshotView.setAlpha(1f - t); - mScreenshotView.setScaleX(scaleT); - mScreenshotView.setScaleY(scaleT); - } - }); - } else { - // In the case where there is a status bar, animate to the origin of the bar (top-left) - final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION - / SCREENSHOT_DROP_OUT_DURATION; - final Interpolator scaleInterpolator = new Interpolator() { - @Override - public float getInterpolation(float x) { - if (x < scaleDurationPct) { - // Decelerate, and scale the input accordingly - return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f)); - } - return 1f; - } - }; - - // Determine the bounds of how to scale - float halfScreenWidth = (w - 2f * mBgPadding) / 2f; - float halfScreenHeight = (h - 2f * mBgPadding) / 2f; - final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET; - final PointF finalPos = new PointF( - -halfScreenWidth - + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth, - -halfScreenHeight - + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight); - - // Animate the screenshot to the status bar - anim.setDuration(SCREENSHOT_DROP_OUT_DURATION); - anim.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float t = (Float) animation.getAnimatedValue(); - float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale) - - scaleInterpolator.getInterpolation(t) - * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE); - mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA); - mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t)); - mScreenshotView.setScaleX(scaleT); - mScreenshotView.setScaleY(scaleT); - mScreenshotView.setTranslationX(t * finalPos.x); - mScreenshotView.setTranslationY(t * finalPos.y); - } - }); - } - return anim; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 221174f70d66..10e6902f139e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -88,7 +88,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; private final String mScreenshotId; private final boolean mSmartActionsEnabled; - private final boolean mCreateDeleteAction; private final Random mRandom = new Random(); SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) { @@ -102,8 +101,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate); mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, UUID.randomUUID()); - mCreateDeleteAction = data.createDeleteAction; - // Initialize screenshot notification smart actions provider. mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index 46fe7f4630bd..fbcd6ba0ff47 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -226,66 +226,6 @@ public class ScreenshotNotificationsController { } /** - * Shows a silent notification with the saved screenshot and actions that can be taken with it. - * - * @param actionData SavedImageData struct with image URI and actions - */ - public void showSilentScreenshotNotification( - GlobalScreenshot.SavedImageData actionData) { - mNotificationBuilder.addAction(actionData.shareAction); - mNotificationBuilder.addAction(actionData.editAction); - mNotificationBuilder.addAction(actionData.deleteAction); - for (Notification.Action smartAction : actionData.smartActions) { - mNotificationBuilder.addAction(smartAction); - } - - // Create the intent to show the screenshot in gallery - Intent launchIntent = new Intent(Intent.ACTION_VIEW); - launchIntent.setDataAndType(actionData.uri, "image/png"); - launchIntent.setFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); - - final long now = System.currentTimeMillis(); - - // Update the text and the icon for the existing notification - mPublicNotificationBuilder - .setContentTitle(mResources.getString(R.string.screenshot_saved_title)) - .setContentText(mResources.getString(R.string.screenshot_saved_text)) - .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0)) - .setSmallIcon(R.drawable.stat_notify_image) - .setCategory(Notification.CATEGORY_PROGRESS) - .setWhen(now) - .setShowWhen(true) - .setAutoCancel(true) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)) - .setGroup("silent") - .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY); - mNotificationBuilder - .setContentTitle(mResources.getString(R.string.screenshot_saved_title)) - .setContentText(mResources.getString(R.string.screenshot_saved_text)) - .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0)) - .setSmallIcon(R.drawable.stat_notify_image) - .setCategory(Notification.CATEGORY_PROGRESS) - .setWhen(now) - .setShowWhen(true) - .setAutoCancel(true) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)) - .setPublicVersion(mPublicNotificationBuilder.build()) - .setStyle(mNotificationStyle) - .setFlag(Notification.FLAG_NO_CLEAR, false) - .setGroup("silent") - .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY); - - SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true); - SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true); - - mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, - mNotificationBuilder.build()); - } - - /** * Sends a notification that the screenshot capture has failed. */ public void notifyScreenshotError(int msgResId) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 8322fe08d3c2..c05c8236def0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -48,7 +48,6 @@ public class TakeScreenshotService extends Service { private static final String TAG = "TakeScreenshotService"; private final GlobalScreenshot mScreenshot; - private final GlobalScreenshotLegacy mScreenshotLegacy; private final UserManager mUserManager; private final UiEventLogger mUiEventLogger; @@ -81,9 +80,6 @@ public class TakeScreenshotService extends Service { return; } - // TODO: clean up once notifications flow is fully deprecated - boolean useCornerFlow = true; - ScreenshotHelper.ScreenshotRequest screenshotRequest = (ScreenshotHelper.ScreenshotRequest) msg.obj; @@ -91,22 +87,10 @@ public class TakeScreenshotService extends Service { switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: - if (useCornerFlow) { - mScreenshot.takeScreenshot(uriConsumer, onComplete); - } else { - mScreenshotLegacy.takeScreenshot( - uriConsumer, screenshotRequest.getHasStatusBar(), - screenshotRequest.getHasNavBar()); - } + mScreenshot.takeScreenshot(uriConsumer, onComplete); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: - if (useCornerFlow) { - mScreenshot.takeScreenshotPartial(uriConsumer, onComplete); - } else { - mScreenshotLegacy.takeScreenshotPartial( - uriConsumer, screenshotRequest.getHasStatusBar(), - screenshotRequest.getHasNavBar()); - } + mScreenshot.takeScreenshotPartial(uriConsumer, onComplete); break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap( @@ -116,13 +100,8 @@ public class TakeScreenshotService extends Service { int taskId = screenshotRequest.getTaskId(); int userId = screenshotRequest.getUserId(); ComponentName topComponent = screenshotRequest.getTopComponent(); - if (useCornerFlow) { - mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets, - taskId, userId, topComponent, uriConsumer, onComplete); - } else { - mScreenshotLegacy.handleImageAsScreenshot(screenshot, screenBounds, insets, - taskId, userId, topComponent, uriConsumer); - } + mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets, + taskId, userId, topComponent, uriConsumer, onComplete); break; default: Log.d(TAG, "Invalid screenshot option: " + msg.what); @@ -131,11 +110,9 @@ public class TakeScreenshotService extends Service { }; @Inject - public TakeScreenshotService(GlobalScreenshot globalScreenshot, - GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager, + public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager, UiEventLogger uiEventLogger) { mScreenshot = globalScreenshot; - mScreenshotLegacy = globalScreenshotLegacy; mUserManager = userManager; mUiEventLogger = uiEventLogger; } @@ -148,8 +125,6 @@ public class TakeScreenshotService extends Service { @Override public boolean onUnbind(Intent intent) { if (mScreenshot != null) mScreenshot.stopScreenshot(); - // TODO remove once notifications flow is fully deprecated - if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot(); return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java index c8361c63e960..853a22505542 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java @@ -229,7 +229,11 @@ public class WindowManagerProxy { // as a result, the above will not capture any tasks; yet, we need to clean-up the // home task bounds. List<ActivityManager.RunningTaskInfo> freeHomeAndRecents = - TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS); + TaskOrganizer.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS); + // Filter out the root split tasks + freeHomeAndRecents.removeIf(p -> p.token.equals(tiles.mSecondary.token) + || p.token.equals(tiles.mPrimary.token)); + if (primaryChildren.isEmpty() && secondaryChildren.isEmpty() && freeHomeAndRecents.isEmpty()) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 43b47232d27d..8c7e071e0d3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -16,9 +16,15 @@ package com.android.systemui.statusbar; +import static com.android.systemui.DejankUtils.whitelistIpcs; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.res.ColorStateList; import android.graphics.Color; import android.hardware.biometrics.BiometricSourceType; @@ -43,6 +49,7 @@ import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; @@ -78,15 +85,19 @@ public class KeyguardIndicationController implements StateListener, private static final float BOUNCE_ANIMATION_FINAL_Y = 0f; private final Context mContext; + private final BroadcastDispatcher mBroadcastDispatcher; private final KeyguardStateController mKeyguardStateController; private final StatusBarStateController mStatusBarStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private ViewGroup mIndicationArea; private KeyguardIndicationTextView mTextView; + private KeyguardIndicationTextView mDisclosure; private final IBatteryStats mBatteryInfo; private final SettableWakeLock mWakeLock; private final DockManager mDockManager; + private final DevicePolicyManager mDevicePolicyManager; + private BroadcastReceiver mBroadcastReceiver; private LockscreenLockIconController mLockIconController; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -105,6 +116,7 @@ public class KeyguardIndicationController implements StateListener, private int mChargingWattage; private int mBatteryLevel; private long mChargingTimeRemaining; + private float mDisclosureMaxAlpha; private String mMessageToShowOnScreenOn; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; @@ -128,8 +140,12 @@ public class KeyguardIndicationController implements StateListener, StatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, + BroadcastDispatcher broadcastDispatcher, + DevicePolicyManager devicePolicyManager, IBatteryStats iBatteryStats) { mContext = context; + mBroadcastDispatcher = broadcastDispatcher; + mDevicePolicyManager = devicePolicyManager; mKeyguardStateController = keyguardStateController; mStatusBarStateController = statusBarStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -151,7 +167,22 @@ public class KeyguardIndicationController implements StateListener, mTextView = indicationArea.findViewById(R.id.keyguard_indication_text); mInitialTextColorState = mTextView != null ? mTextView.getTextColors() : ColorStateList.valueOf(Color.WHITE); + mDisclosure = indicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure); + mDisclosureMaxAlpha = mDisclosure.getAlpha(); updateIndication(false /* animate */); + updateDisclosure(); + + if (mBroadcastReceiver == null) { + // Update the disclosure proactively to avoid IPC on the critical path. + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateDisclosure(); + } + }; + mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, new IntentFilter( + DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + } } public void setLockIconController(LockscreenLockIconController lockIconController) { @@ -190,6 +221,23 @@ public class KeyguardIndicationController implements StateListener, return mUpdateMonitorCallback; } + private void updateDisclosure() { + // NOTE: Because this uses IPC, avoid calling updateDisclosure() on a critical path. + if (whitelistIpcs(mDevicePolicyManager::isDeviceManaged)) { + final CharSequence organizationName = + mDevicePolicyManager.getDeviceOwnerOrganizationName(); + if (organizationName != null) { + mDisclosure.switchIndication(mContext.getResources().getString( + R.string.do_disclosure_with_name, organizationName)); + } else { + mDisclosure.switchIndication(R.string.do_disclosure_generic); + } + mDisclosure.setVisibility(View.VISIBLE); + } else { + mDisclosure.setVisibility(View.GONE); + } + } + public void setVisible(boolean visible) { mVisible = visible; mIndicationArea.setVisibility(visible ? View.VISIBLE : View.GONE); @@ -574,6 +622,11 @@ public class KeyguardIndicationController implements StateListener, } @Override + public void onDozeAmountChanged(float linear, float eased) { + mDisclosure.setAlpha((1 - linear) * mDisclosureMaxAlpha); + } + + @Override public void onUnlockedChanged() { updateIndication(!mDozing); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 72d9d0ee8f8f..4d09071c6b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -104,6 +104,9 @@ public class NotificationListener extends NotificationListenerWithPlugins { listener.onNotificationPosted(sbn, completeMap); } } + for (NotificationHandler listener : mNotificationHandlers) { + listener.onNotificationsInitialized(); + } }); onSilentStatusBarIconsVisibilityChanged( mNotificationManager.shouldHideSilentStatusBarIcons()); @@ -224,5 +227,10 @@ public class NotificationListener extends NotificationListenerWithPlugins { void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap); void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason); void onNotificationRankingUpdate(RankingMap rankingMap); + + /** + * Called after the listener has connected to NoMan and posted any current notifications. + */ + void onNotificationsInitialized(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index 6a3302473e63..382715a3fb77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -293,7 +293,7 @@ public class ActivityLaunchAnimator { .withCornerRadius(mCornerRadius) .withVisibility(true) .build(); - mSyncRtTransactionApplier.scheduleApply(true /* earlyWakeup */, params); + mSyncRtTransactionApplier.scheduleApply(params); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java index 5ee4693a32bf..e0532c3e2b28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java @@ -17,9 +17,11 @@ package com.android.systemui.statusbar.notification; import android.content.res.Resources; +import android.text.Layout; import android.util.Pools; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import com.android.internal.widget.IMessagingLayout; import com.android.internal.widget.MessagingGroup; @@ -229,6 +231,15 @@ public class MessagingLayoutTransformState extends TransformState { return result; } + private boolean hasEllipses(TextView textView) { + Layout layout = textView.getLayout(); + return layout != null && layout.getEllipsisCount(layout.getLineCount() - 1) > 0; + } + + private boolean needsReflow(TextView own, TextView other) { + return hasEllipses(own) != hasEllipses(other); + } + /** * Transform two groups towards each other. * @@ -238,13 +249,20 @@ public class MessagingLayoutTransformState extends TransformState { float transformationAmount, boolean to) { boolean useLinearTransformation = otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating(); - transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(), - true /* sameAsAny */, useLinearTransformation); + TextView ownSenderView = ownGroup.getSenderView(); + TextView otherSenderView = otherGroup.getSenderView(); + transformView(transformationAmount, to, ownSenderView, otherSenderView, + // Normally this would be handled by the TextViewMessageState#sameAs check, but in + // this case it doesn't work because our text won't match, due to the appended colon + // in the collapsed view. + !needsReflow(ownSenderView, otherSenderView), + useLinearTransformation); int totalAvatarTranslation = transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(), true /* sameAsAny */, useLinearTransformation); List<MessagingMessage> ownMessages = ownGroup.getMessages(); List<MessagingMessage> otherMessages = otherGroup.getMessages(); float previousTranslation = 0; + boolean isLastView = true; for (int i = 0; i < ownMessages.size(); i++) { View child = ownMessages.get(ownMessages.size() - 1 - i).getView(); if (isGone(child)) { @@ -278,6 +296,9 @@ public class MessagingLayoutTransformState extends TransformState { mMessagingLayout.setMessagingClippingDisabled(true); } if (otherChild == null) { + if (isLastView) { + previousTranslation = ownSenderView.getTranslationY(); + } child.setTranslationY(previousTranslation); setClippingDeactivated(child, true); } else if (ownGroup.getIsolatedMessage() == child || otherIsIsolated) { @@ -287,6 +308,7 @@ public class MessagingLayoutTransformState extends TransformState { } else { previousTranslation = child.getTranslationY(); } + isLastView = false; } ownGroup.updateClipRect(); return totalAvatarTranslation; @@ -382,6 +404,9 @@ public class MessagingLayoutTransformState extends TransformState { if (view.getParent() == null) { return true; } + if (view.getWidth() == 0) { + return true; + } final ViewGroup.LayoutParams lp = view.getLayoutParams(); if (lp instanceof MessagingLinearLayout.LayoutParams && ((MessagingLinearLayout.LayoutParams) lp).hide) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index adb51a5d959a..9abc66056452 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -385,6 +385,10 @@ public class NotificationEntryManager implements public void onNotificationRankingUpdate(RankingMap rankingMap) { updateNotificationRanking(rankingMap); } + + @Override + public void onNotificationsInitialized() { + } }; /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 45987b6eb21b..c1acfbadef45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -47,7 +47,6 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.Notification; import android.os.RemoteException; -import android.os.SystemClock; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; @@ -82,6 +81,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.notifcollection.RankingAppliedEvent; import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent; import com.android.systemui.util.Assert; +import com.android.systemui.util.time.SystemClock; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -95,6 +95,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Singleton; @@ -125,6 +126,7 @@ import javax.inject.Singleton; @Singleton public class NotifCollection implements Dumpable { private final IStatusBarService mStatusBarService; + private final SystemClock mClock; private final FeatureFlags mFeatureFlags; private final NotifCollectionLogger mLogger; private final LogBufferEulogizer mEulogizer; @@ -142,20 +144,24 @@ public class NotifCollection implements Dumpable { private boolean mAttached = false; private boolean mAmDispatchingToOtherCode; + private long mInitializedTimestamp = 0; @Inject public NotifCollection( IStatusBarService statusBarService, - DumpManager dumpManager, + SystemClock clock, FeatureFlags featureFlags, NotifCollectionLogger logger, - LogBufferEulogizer logBufferEulogizer) { + LogBufferEulogizer logBufferEulogizer, + DumpManager dumpManager) { Assert.isMainThread(); mStatusBarService = statusBarService; + mClock = clock; + mFeatureFlags = featureFlags; mLogger = logger; mEulogizer = logBufferEulogizer; + dumpManager.registerDumpable(TAG, this); - mFeatureFlags = featureFlags; } /** Initializes the NotifCollection and registers it to receive notification events. */ @@ -376,9 +382,10 @@ public class NotifCollection implements Dumpable { final NotificationEntry entry = mNotificationSet.get(sbn.getKey()); if (entry == null) { - throw mEulogizer.record( + crashIfNotInitializing( new IllegalStateException("No notification to remove with key " + sbn.getKey())); + return; } entry.mCancellationReason = reason; @@ -394,6 +401,10 @@ public class NotifCollection implements Dumpable { dispatchEventsAndRebuildList(); } + private void onNotificationsInitialized() { + mInitializedTimestamp = mClock.uptimeMillis(); + } + private void postNotification( StatusBarNotification sbn, Ranking ranking) { @@ -401,7 +412,7 @@ public class NotifCollection implements Dumpable { if (entry == null) { // A new notification! - entry = new NotificationEntry(sbn, ranking, SystemClock.uptimeMillis()); + entry = new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); mEventQueue.add(new InitEntryEvent(entry)); mEventQueue.add(new BindEntryEvent(entry, sbn)); mNotificationSet.put(sbn.getKey(), entry); @@ -628,6 +639,23 @@ public class NotifCollection implements Dumpable { } } + // While the NotificationListener is connecting to NotificationManager, there is a short period + // during which it's possible for us to receive events about notifications we don't yet know + // about (or that otherwise don't make sense). Until that race condition is fixed, we create a + // "forgiveness window" of five seconds during which we won't crash if we receive nonsensical + // messages from system server. + private void crashIfNotInitializing(RuntimeException exception) { + final boolean isRecentlyInitialized = mInitializedTimestamp == 0 + || mClock.uptimeMillis() - mInitializedTimestamp + < INITIALIZATION_FORGIVENESS_WINDOW; + + if (isRecentlyInitialized) { + mLogger.logIgnoredError(exception.getMessage()); + } else { + throw mEulogizer.record(exception); + } + } + private static Ranking requireRanking(RankingMap rankingMap, String key) { // TODO: Modify RankingMap so that we don't have to make a copy here Ranking ranking = new Ranking(); @@ -742,6 +770,11 @@ public class NotifCollection implements Dumpable { public void onNotificationRankingUpdate(RankingMap rankingMap) { NotifCollection.this.onNotificationRankingUpdate(rankingMap); } + + @Override + public void onNotificationsInitialized() { + NotifCollection.this.onNotificationsInitialized(); + } }; private static final String TAG = "NotifCollection"; @@ -773,4 +806,6 @@ public class NotifCollection implements Dumpable { static final int REASON_NOT_CANCELED = -1; public static final int REASON_UNKNOWN = 0; + + private static final long INITIALIZATION_FORGIVENESS_WINDOW = TimeUnit.SECONDS.toMillis(5); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index 596235cfb4d0..1710daa16735 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -153,6 +153,11 @@ public class GroupCoalescer implements Dumpable { applyRanking(rankingMap); mHandler.onNotificationRankingUpdate(rankingMap); } + + @Override + public void onNotificationsInitialized() { + mHandler.onNotificationsInitialized(); + } }; private void maybeEmitBatch(StatusBarNotification sbn) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index 0eb2d64e8682..76751eaaecb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -20,6 +20,7 @@ import android.os.RemoteException import android.service.notification.NotificationListenerService.RankingMap import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.ERROR import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.LogLevel.WTF @@ -167,6 +168,14 @@ class NotifCollectionLogger @Inject constructor( "LIFETIME EXTENSION ENDED for $str1 by '$str2'; $int1 remaining extensions" }) } + + fun logIgnoredError(message: String?) { + buffer.log(TAG, ERROR, { + str1 = message + }, { + "ERROR suppressed due to initialization forgiveness: $str1" + }) + } } private const val TAG = "NotifCollection" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index 08be4f872415..011ad19b41db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -255,23 +255,11 @@ class IconManager @Inject constructor( @Throws(InflationException::class) private fun createPeopleAvatar(entry: NotificationEntry): Icon? { - // Attempt to extract form shortcut. - val conversationId = entry.ranking.channel.conversationId - val query = LauncherApps.ShortcutQuery() - .setPackage(entry.sbn.packageName) - .setQueryFlags( - LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC - or LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED) - .setShortcutIds(listOf(conversationId)) - val shortcuts = launcherApps.getShortcuts(query, entry.sbn.user) var ic: Icon? = null - if (shortcuts != null && shortcuts.isNotEmpty()) { - ic = shortcuts[0].icon - } - // Fall back to notification large icon if available - if (ic == null) { - ic = entry.sbn.notification.getLargeIcon() + val shortcut = entry.ranking.shortcutInfo + if (shortcut != null) { + ic = launcherApps.getShortcutIcon(shortcut) } // Fall back to extract from message @@ -290,6 +278,11 @@ class IconManager @Inject constructor( } } + // Fall back to notification large icon if available + if (ic == null) { + ic = entry.sbn.notification.getLargeIcon() + } + // Revert to small icon if still not available if (ic == null) { ic = entry.sbn.notification.smallIcon diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index ad047889f29f..bd0d0b31e4dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -29,6 +29,7 @@ import android.util.Log; import androidx.annotation.Nullable; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; @@ -36,6 +37,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -58,6 +60,7 @@ import javax.inject.Inject; */ public class NotificationLogger implements StateListener { private static final String TAG = "NotificationLogger"; + private static final boolean DEBUG = false; /** The minimum delay in ms between reports of notification visibility. */ private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500; @@ -79,7 +82,12 @@ public class NotificationLogger implements StateListener { private long mLastVisibilityReportUptimeMs; private NotificationListContainer mListContainer; private final Object mDozingLock = new Object(); - private boolean mDozing; + @GuardedBy("mDozingLock") + private Boolean mDozing = null; // Use null to indicate state is not yet known + @GuardedBy("mDozingLock") + private Boolean mLockscreen = null; // Use null to indicate state is not yet known + private Boolean mPanelExpanded = null; // Use null to indicate state is not yet known + private boolean mLogging = false; protected final OnChildLocationsChangedListener mNotificationLocationsChangedListener = new OnChildLocationsChangedListener() { @@ -247,33 +255,44 @@ public class NotificationLogger implements StateListener { } public void stopNotificationLogging() { - // Report all notifications as invisible and turn down the - // reporter. - if (!mCurrentlyVisibleNotifications.isEmpty()) { - logNotificationVisibilityChanges( - Collections.emptyList(), mCurrentlyVisibleNotifications); - recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); + if (mLogging) { + mLogging = false; + if (DEBUG) { + Log.i(TAG, "stopNotificationLogging: log notifications invisible"); + } + // Report all notifications as invisible and turn down the + // reporter. + if (!mCurrentlyVisibleNotifications.isEmpty()) { + logNotificationVisibilityChanges( + Collections.emptyList(), mCurrentlyVisibleNotifications); + recycleAllVisibilityObjects(mCurrentlyVisibleNotifications); + } + mHandler.removeCallbacks(mVisibilityReporter); + mListContainer.setChildLocationsChangedListener(null); } - mHandler.removeCallbacks(mVisibilityReporter); - mListContainer.setChildLocationsChangedListener(null); } public void startNotificationLogging() { - mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener); - // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't - // cause the scroller to emit child location events. Hence generate - // one ourselves to guarantee that we're reporting visible - // notifications. - // (Note that in cases where the scroller does emit events, this - // additional event doesn't break anything.) - mNotificationLocationsChangedListener.onChildLocationsChanged(); - mNotificationPanelLogger.logPanelShown(mListContainer.hasPulsingNotifications(), - mEntryManager.getVisibleNotifications()); + if (!mLogging) { + mLogging = true; + if (DEBUG) { + Log.i(TAG, "startNotificationLogging"); + } + mListContainer.setChildLocationsChangedListener(mNotificationLocationsChangedListener); + // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't + // cause the scroller to emit child location events. Hence generate + // one ourselves to guarantee that we're reporting visible + // notifications. + // (Note that in cases where the scroller does emit events, this + // additional event doesn't break anything.) + mNotificationLocationsChangedListener.onChildLocationsChanged(); + } } private void setDozing(boolean dozing) { synchronized (mDozingLock) { mDozing = dozing; + maybeUpdateLoggingStatus(); } } @@ -343,19 +362,12 @@ public class NotificationLogger implements StateListener { for (int i = 0; i < N; i++) { newlyVisibleKeyAr[i] = newlyVisibleAr[i].key; } - - synchronized (mDozingLock) { - // setNotificationsShown should only be called if we are confident that - // the user has seen the notification, aka not when ambient display is on - if (!mDozing) { - // TODO: Call NotificationEntryManager to do this, once it exists. - // TODO: Consider not catching all runtime exceptions here. - try { - mNotificationListener.setNotificationsShown(newlyVisibleKeyAr); - } catch (RuntimeException e) { - Log.d(TAG, "failed setNotificationsShown: ", e); - } - } + // TODO: Call NotificationEntryManager to do this, once it exists. + // TODO: Consider not catching all runtime exceptions here. + try { + mNotificationListener.setNotificationsShown(newlyVisibleKeyAr); + } catch (RuntimeException e) { + Log.d(TAG, "failed setNotificationsShown: ", e); } } recycleAllVisibilityObjects(newlyVisibleAr); @@ -400,14 +412,64 @@ public class NotificationLogger implements StateListener { @Override public void onStateChanged(int newState) { - // don't care about state change + if (DEBUG) { + Log.i(TAG, "onStateChanged: new=" + newState); + } + synchronized (mDozingLock) { + mLockscreen = (newState == StatusBarState.KEYGUARD + || newState == StatusBarState.SHADE_LOCKED); + } } @Override public void onDozingChanged(boolean isDozing) { + if (DEBUG) { + Log.i(TAG, "onDozingChanged: new=" + isDozing); + } setDozing(isDozing); } + @GuardedBy("mDozingLock") + private void maybeUpdateLoggingStatus() { + if (mPanelExpanded == null || mDozing == null) { + if (DEBUG) { + Log.i(TAG, "Panel status unclear: panelExpandedKnown=" + + (mPanelExpanded == null) + " dozingKnown=" + (mDozing == null)); + } + return; + } + // Once we know panelExpanded and Dozing, turn logging on & off when appropriate + boolean lockscreen = mLockscreen == null ? false : mLockscreen; + if (mPanelExpanded && !mDozing) { + mNotificationPanelLogger.logPanelShown(lockscreen, + mEntryManager.getVisibleNotifications()); + if (DEBUG) { + Log.i(TAG, "Notification panel shown, lockscreen=" + lockscreen); + } + startNotificationLogging(); + } else { + if (DEBUG) { + Log.i(TAG, "Notification panel hidden, lockscreen=" + lockscreen); + } + stopNotificationLogging(); + } + } + + /** + * Called by StatusBar to notify the logger that the panel expansion has changed. + * The panel may be showing any of the normal notification panel, the AOD, or the bouncer. + * @param isExpanded True if the panel is expanded. + */ + public void onPanelExpandedChanged(boolean isExpanded) { + if (DEBUG) { + Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded); + } + mPanelExpanded = isExpanded; + synchronized (mDozingLock) { + maybeUpdateLoggingStatus(); + } + } + /** * Called when the notification is expanded / collapsed. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 0bdac39f35e9..c87b9986ca55 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.row.StackScrollerDecorView import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.children +import com.android.systemui.util.takeUntil import com.android.systemui.util.foldToSparseArray import javax.inject.Inject @@ -197,7 +198,7 @@ class NotificationSectionsManager @Inject internal constructor( else -> null } - private fun logShadeContents() = parent.children.forEachIndexed { i, child -> + private fun logShadeChild(i: Int, child: View) { when { child === incomingHeaderView -> logger.logIncomingHeader(i) child === mediaControlsView -> logger.logMediaControls(i) @@ -216,6 +217,7 @@ class NotificationSectionsManager @Inject internal constructor( } } } + private fun logShadeContents() = parent.children.forEachIndexed(::logShadeChild) private val isUsingMultipleSections: Boolean get() = sectionsFeatureManager.getNumberOfBuckets() > 1 @@ -223,6 +225,57 @@ class NotificationSectionsManager @Inject internal constructor( @VisibleForTesting fun updateSectionBoundaries() = updateSectionBoundaries("test") + private interface SectionUpdateState<out T : ExpandableView> { + val header: T + var currentPosition: Int? + var targetPosition: Int? + fun adjustViewPosition() + } + + private fun <T : ExpandableView> expandableViewHeaderState(header: T): SectionUpdateState<T> = + object : SectionUpdateState<T> { + override val header = header + override var currentPosition: Int? = null + override var targetPosition: Int? = null + + override fun adjustViewPosition() { + val target = targetPosition + val current = currentPosition + if (target == null) { + if (current != null) { + parent.removeView(header) + } + } else { + if (current == null) { + // If the header is animating away, it will still have a parent, so + // detach it first + // TODO: We should really cancel the active animations here. This will + // happen automatically when the view's intro animation starts, but + // it's a fragile link. + header.transientContainer?.removeTransientView(header) + header.transientContainer = null + parent.addView(header, target) + } else { + parent.changeViewPosition(header, target) + } + } + } + } + + private fun <T : StackScrollerDecorView> decorViewHeaderState( + header: T + ): SectionUpdateState<T> { + val inner = expandableViewHeaderState(header) + return object : SectionUpdateState<T> by inner { + override fun adjustViewPosition() { + inner.adjustViewPosition() + if (targetPosition != null && currentPosition == null) { + header.isContentVisible = true + } + } + } + } + /** * Should be called whenever notifs are added, removed, or updated. Updates section boundary * bookkeeping and adds/moves/removes section headers if appropriate. @@ -238,233 +291,136 @@ class NotificationSectionsManager @Inject internal constructor( // Then, once we find the start of a new section, we track that position as the "target" for // the section header, adjusted for the case where existing headers are in front of that // target, but won't be once they are moved / removed after the pass has completed. + val showHeaders = statusBarStateController.state != StatusBarState.KEYGUARD val usingPeopleFiltering = sectionsFeatureManager.isFilteringEnabled() val usingMediaControls = sectionsFeatureManager.isMediaControlsEnabled() - var peopleNotifsPresent = false - var currentMediaControlsIdx = -1 - val mediaControlsTarget = if (usingMediaControls) 0 else -1 - var currentIncomingHeaderIdx = -1 - var incomingHeaderTarget = -1 - var currentPeopleHeaderIdx = -1 - var peopleHeaderTarget = -1 - var currentAlertingHeaderIdx = -1 - var alertingHeaderTarget = -1 - var currentGentleHeaderIdx = -1 - var gentleHeaderTarget = -1 + val mediaState = mediaControlsView?.let(::expandableViewHeaderState) + val incomingState = incomingHeaderView?.let(::decorViewHeaderState) + val peopleState = peopleHeaderView?.let(::decorViewHeaderState) + val alertingState = alertingHeaderView?.let(::decorViewHeaderState) + val gentleState = silentHeaderView?.let(::decorViewHeaderState) + + fun getSectionState(view: View): SectionUpdateState<ExpandableView>? = when { + view === mediaControlsView -> mediaState + view === incomingHeaderView -> incomingState + view === peopleHeaderView -> peopleState + view === alertingHeaderView -> alertingState + view === silentHeaderView -> gentleState + else -> null + } + val headersOrdered = sequenceOf( + mediaState, incomingState, peopleState, alertingState, gentleState + ).filterNotNull() + + var peopleNotifsPresent = false var lastNotifIndex = 0 - var lastIncomingIndex = -1 - var prev: ExpandableNotificationRow? = null - - for ((i, child) in parent.children.withIndex()) { - when { - // Track the existing positions of the headers - child === incomingHeaderView -> { - logger.logIncomingHeader(i) - currentIncomingHeaderIdx = i - } - child === mediaControlsView -> { - logger.logMediaControls(i) - currentMediaControlsIdx = i - } - child === peopleHeaderView -> { - logger.logConversationsHeader(i) - currentPeopleHeaderIdx = i - } - child === alertingHeaderView -> { - logger.logAlertingHeader(i) - currentAlertingHeaderIdx = i + var nextBucket: Int? = null + var inIncomingSection = false + + // Iterating backwards allows for easier construction of the Incoming section, as opposed + // to backtracking when a discontinuity in the sections is discovered. + // Iterating to -1 in order to support the case where a header is at the very top of the + // shade. + for (i in parent.childCount - 1 downTo -1) { + val child: View? = parent.getChildAt(i) + child?.let { + logShadeChild(i, child) + // If this child is a header, update the tracked positions + getSectionState(child)?.let { state -> + state.currentPosition = i + // If headers that should appear above this one in the shade already have a + // target index, then we need to decrement them in order to account for this one + // being either removed, or moved below them. + headersOrdered.takeUntil { it === state } + .forEach { it.targetPosition = it.targetPosition?.minus(1) } } - child === silentHeaderView -> { - logger.logSilentHeader(i) - currentGentleHeaderIdx = i - } - child !is ExpandableNotificationRow -> logger.logOther(i, child.javaClass) - else -> { - lastNotifIndex = i - // Is there a section discontinuity? This usually occurs due to HUNs - if (prev?.entry?.bucket?.let { it > child.entry.bucket } == true) { - // Remove existing headers, and move the Incoming header if necessary - incomingHeaderTarget = when { - !showHeaders -> -1 - incomingHeaderTarget != -1 -> incomingHeaderTarget - peopleHeaderTarget != -1 -> peopleHeaderTarget - alertingHeaderTarget != -1 -> alertingHeaderTarget - gentleHeaderTarget != -1 -> gentleHeaderTarget - else -> 0 - } - peopleHeaderTarget = -1 - alertingHeaderTarget = -1 - gentleHeaderTarget = -1 - // Walk backwards changing all previous notifications to the Incoming - // section - for (j in i - 1 downTo lastIncomingIndex + 1) { - val prevChild = parent.getChildAt(j) - if (prevChild is ExpandableNotificationRow) { - prevChild.entry.bucket = BUCKET_HEADS_UP - } - } - // Track the new bottom of the Incoming section - lastIncomingIndex = i - 1 - } - val isHeadsUp = child.isHeadsUp - when (child.entry.bucket) { - BUCKET_FOREGROUND_SERVICE -> logger.logForegroundService(i, isHeadsUp) - BUCKET_PEOPLE -> { - logger.logConversation(i, isHeadsUp) - peopleNotifsPresent = true - if (showHeaders && peopleHeaderTarget == -1) { - peopleHeaderTarget = i - // Offset the target if there are other headers before this that - // will be moved. - if (currentIncomingHeaderIdx != -1 && incomingHeaderTarget == -1) { - peopleHeaderTarget-- - } - if (currentPeopleHeaderIdx != -1) { - peopleHeaderTarget-- - } - if (currentAlertingHeaderIdx != -1) { - peopleHeaderTarget-- - } - if (currentGentleHeaderIdx != -1) { - peopleHeaderTarget-- - } - } - } - BUCKET_ALERTING -> { - logger.logAlerting(i, isHeadsUp) - if (showHeaders && usingPeopleFiltering && alertingHeaderTarget == -1) { - alertingHeaderTarget = i - // Offset the target if there are other headers before this that - // will be moved. - if (currentIncomingHeaderIdx != -1 && incomingHeaderTarget == -1) { - alertingHeaderTarget-- - } - if (currentPeopleHeaderIdx != -1 && peopleHeaderTarget == -1) { - // People header will be removed - alertingHeaderTarget-- - } - if (currentAlertingHeaderIdx != -1) { - alertingHeaderTarget-- - } - if (currentGentleHeaderIdx != -1) { - alertingHeaderTarget-- - } - } - } - BUCKET_SILENT -> { - logger.logSilent(i, isHeadsUp) - if (showHeaders && gentleHeaderTarget == -1) { - gentleHeaderTarget = i - // Offset the target if there are other headers before this that - // will be moved. - if (currentIncomingHeaderIdx != -1 && incomingHeaderTarget == -1) { - gentleHeaderTarget-- - } - if (currentPeopleHeaderIdx != -1 && peopleHeaderTarget == -1) { - // People header will be removed - gentleHeaderTarget-- - } - if (currentAlertingHeaderIdx != -1 && alertingHeaderTarget == -1) { - // Alerting header will be removed - gentleHeaderTarget-- - } - if (currentGentleHeaderIdx != -1) { - gentleHeaderTarget-- - } - } - } - } + } + + val row = child as? ExpandableNotificationRow + + // Is there a section discontinuity? This usually occurs due to HUNs + inIncomingSection = inIncomingSection || nextBucket?.let { next -> + row?.entry?.bucket?.let { curr -> next < curr } + } == true - prev = child + if (inIncomingSection) { + // Update the bucket to reflect that it's being placed in the Incoming section + row?.entry?.bucket = BUCKET_HEADS_UP + } + + // Insert a header in front of the next row, if there's a boundary between it and this + // row, or if it is the topmost row. + val isSectionBoundary = nextBucket != null && + (child == null || row != null && nextBucket != row.entry.bucket) + if (isSectionBoundary && showHeaders) { + when (nextBucket) { + BUCKET_HEADS_UP -> incomingState?.targetPosition = i + 1 + BUCKET_PEOPLE -> peopleState?.targetPosition = i + 1 + BUCKET_ALERTING -> alertingState?.targetPosition = i + 1 + BUCKET_SILENT -> gentleState?.targetPosition = i + 1 } } - } - if (showHeaders && usingPeopleFiltering && peopleHubVisible && peopleHeaderTarget == -1) { - // Insert the people header even if there are no people visible, in order to show - // the hub. Put it directly above the next header. - peopleHeaderTarget = when { - alertingHeaderTarget != -1 -> alertingHeaderTarget - gentleHeaderTarget != -1 -> gentleHeaderTarget - else -> lastNotifIndex // Put it at the end of the list. + row ?: continue + + // Check if there are any people notifications + peopleNotifsPresent = peopleNotifsPresent || row.entry.bucket == BUCKET_PEOPLE + + if (nextBucket == null) { + lastNotifIndex = i } + nextBucket = row.entry.bucket + } + + if (showHeaders && usingPeopleFiltering && peopleHubVisible) { + peopleState?.targetPosition = peopleState?.targetPosition + // Insert the people header even if there are no people visible, in order to + // show the hub. Put it directly above the next header. + ?: alertingState?.targetPosition + ?: gentleState?.targetPosition + // Put it at the end of the list. + ?: lastNotifIndex + // Offset the target to account for the current position of the people header. - if (currentPeopleHeaderIdx != -1 && currentPeopleHeaderIdx < peopleHeaderTarget) { - peopleHeaderTarget-- + peopleState?.targetPosition = peopleState?.currentPosition?.let { current -> + peopleState?.targetPosition?.let { target -> + if (current < target) target - 1 else target + } } } + mediaState?.targetPosition = if (usingMediaControls) 0 else null + logger.logStr("New header target positions:") - logger.logIncomingHeader(incomingHeaderTarget) - logger.logMediaControls(mediaControlsTarget) - logger.logConversationsHeader(peopleHeaderTarget) - logger.logAlertingHeader(alertingHeaderTarget) - logger.logSilentHeader(gentleHeaderTarget) - - // Add headers in reverse order to preserve indices - silentHeaderView?.let { - adjustHeaderVisibilityAndPosition(gentleHeaderTarget, it, currentGentleHeaderIdx) - } - alertingHeaderView?.let { - adjustHeaderVisibilityAndPosition(alertingHeaderTarget, it, currentAlertingHeaderIdx) - } - peopleHeaderView?.let { - adjustHeaderVisibilityAndPosition(peopleHeaderTarget, it, currentPeopleHeaderIdx) - } - incomingHeaderView?.let { - adjustHeaderVisibilityAndPosition(incomingHeaderTarget, it, currentIncomingHeaderIdx) - } - mediaControlsView?.let { - adjustViewPosition(mediaControlsTarget, it, currentMediaControlsIdx) - } + logger.logMediaControls(mediaState?.targetPosition ?: -1) + logger.logIncomingHeader(incomingState?.targetPosition ?: -1) + logger.logConversationsHeader(peopleState?.targetPosition ?: -1) + logger.logAlertingHeader(alertingState?.targetPosition ?: -1) + logger.logSilentHeader(gentleState?.targetPosition ?: -1) + + // Update headers in reverse order to preserve indices, otherwise movements earlier in the + // list will affect the target indices of the headers later in the list. + headersOrdered.asIterable().reversed().forEach { it.adjustViewPosition() } logger.logStr("Final order:") logShadeContents() logger.logStr("Section boundary update complete") // Update headers to reflect state of section contents - silentHeaderView?.setAreThereDismissableGentleNotifs( - parent.hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE) - ) - peopleHeaderView?.canSwipe = showHeaders && peopleHubVisible && !peopleNotifsPresent - if (peopleHeaderTarget != currentPeopleHeaderIdx) { - peopleHeaderView?.resetTranslation() - } - } - - private fun adjustHeaderVisibilityAndPosition( - targetPosition: Int, - header: StackScrollerDecorView, - currentPosition: Int - ) { - adjustViewPosition(targetPosition, header, currentPosition) - if (targetPosition != -1 && currentPosition == -1) { - header.isContentVisible = true + silentHeaderView?.run { + val hasActiveClearableNotifications = this@NotificationSectionsManager.parent + .hasActiveClearableNotifications(NotificationStackScrollLayout.ROWS_GENTLE) + setAreThereDismissableGentleNotifs(hasActiveClearableNotifications) } - } - - private fun adjustViewPosition( - targetPosition: Int, - view: ExpandableView, - currentPosition: Int - ) { - if (targetPosition == -1) { - if (currentPosition != -1) { - parent.removeView(view) - } - } else { - if (currentPosition == -1) { - // If the header is animating away, it will still have a parent, so detach it first - // TODO: We should really cancel the active animations here. This will happen - // automatically when the view's intro animation starts, but it's a fragile link. - view.transientContainer?.removeTransientView(view) - view.transientContainer = null - parent.addView(view, targetPosition) - } else { - parent.changeViewPosition(view, targetPosition) + peopleHeaderView?.run { + canSwipe = showHeaders && peopleHubVisible && !peopleNotifsPresent + peopleState?.targetPosition?.let { targetPosition -> + if (targetPosition != peopleState.currentPosition) { + resetTranslation() + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 978394ca05ab..cad1c91975bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -17,7 +17,11 @@ package com.android.systemui.statusbar.phone; import static android.view.Display.INVALID_DISPLAY; +import android.app.ActivityManager; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.PixelFormat; @@ -75,6 +79,8 @@ import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; import com.android.systemui.tracing.nano.SystemUiTraceProto; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Executor; /** @@ -121,9 +127,18 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa } }; + private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { + @Override + public void onTaskStackChanged() { + mGestureBlockingActivityRunning = isGestureBlockingActivityRunning(); + } + }; + private final Context mContext; private final OverviewProxyService mOverviewProxyService; - private PluginManager mPluginManager; + private final PluginManager mPluginManager; + // Activities which should not trigger Back gesture. + private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>(); private final Point mDisplaySize = new Point(); private final int mDisplayId; @@ -162,6 +177,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private boolean mIsEnabled; private boolean mIsNavBarShownTransiently; private boolean mIsBackGestureAllowed; + private boolean mGestureBlockingActivityRunning; private InputMonitor mInputMonitor; private InputEventReceiver mInputEventReceiver; @@ -203,6 +219,29 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mMainExecutor = context.getMainExecutor(); mOverviewProxyService = overviewProxyService; mPluginManager = pluginManager; + ComponentName recentsComponentName = ComponentName.unflattenFromString( + context.getString(com.android.internal.R.string.config_recentsComponentName)); + if (recentsComponentName != null) { + String recentsPackageName = recentsComponentName.getPackageName(); + PackageManager manager = context.getPackageManager(); + try { + Resources resources = manager.getResourcesForApplication(recentsPackageName); + int resId = resources.getIdentifier( + "gesture_blocking_activities", "array", recentsPackageName); + + if (resId == 0) { + Log.e(TAG, "No resource found for gesture-blocking activities"); + } else { + String[] gestureBlockingActivities = resources.getStringArray(resId); + for (String gestureBlockingActivity : gestureBlockingActivities) { + mGestureBlockingActivities.add( + ComponentName.unflattenFromString(gestureBlockingActivity)); + } + } + } catch (NameNotFoundException e) { + Log.e(TAG, "Failed to add gesture blocking activities", e); + } + } Dependency.get(ProtoTracer.class).add(this); mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT, @@ -324,6 +363,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mGestureNavigationSettingsObserver.unregister(); mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); mPluginManager.removePluginListener(this); + ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); try { WindowManagerGlobal.getWindowManagerService() @@ -338,6 +378,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa updateDisplaySize(); mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, mContext.getMainThreadHandler()); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); try { WindowManagerGlobal.getWindowManagerService() @@ -491,6 +532,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mLogGesture = false; mInRejectedExclusion = false; mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed + && !mGestureBlockingActivityRunning && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); if (mAllowGesture) { @@ -633,6 +675,13 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa pw.println(" mEdgeWidthRight=" + mEdgeWidthRight); } + private boolean isGestureBlockingActivityRunning() { + ActivityManager.RunningTaskInfo runningTask = + ActivityManagerWrapper.getInstance().getRunningTask(); + ComponentName topActivity = runningTask == null ? null : runningTask.topActivity; + return topActivity != null && mGestureBlockingActivities.contains(topActivity); + } + @Override public void writeToProto(SystemUiTraceProto proto) { if (proto.edgeBackGestureHandler == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index ae7867d68af4..b47c59acb82d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -125,6 +125,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private KeyguardAffordanceView mRightAffordanceView; private KeyguardAffordanceView mLeftAffordanceView; private ViewGroup mIndicationArea; + private TextView mEnterpriseDisclosure; private TextView mIndicationText; private ViewGroup mPreviewContainer; private ViewGroup mOverlayContainer; @@ -238,6 +239,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mRightAffordanceView = findViewById(R.id.camera_button); mLeftAffordanceView = findViewById(R.id.left_button); mIndicationArea = findViewById(R.id.keyguard_indication_area); + mEnterpriseDisclosure = findViewById( + R.id.keyguard_indication_enterprise_disclosure); mIndicationText = findViewById(R.id.keyguard_indication_text); mIndicationBottomMargin = getResources().getDimensionPixelSize( R.dimen.keyguard_indication_margin_bottom); @@ -315,6 +318,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } // Respect font size setting. + mEnterpriseDisclosure.setTextSize(TypedValue.COMPLEX_UNIT_PX, + getResources().getDimensionPixelSize( + com.android.internal.R.dimen.text_size_small_material)); mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize( com.android.internal.R.dimen.text_size_small_material)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java index 83d398d3e7ae..0d6597f1b11b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java @@ -39,7 +39,7 @@ import javax.inject.Singleton; public class LockscreenGestureLogger { /** - * Contains Lockscreen related Westworld UiEvent enums. + * Contains Lockscreen related statsd UiEvent enums. */ public enum LockscreenUiEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "Lockscreen > Pull shade open") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 8954d98d0201..df121f0faaf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -323,6 +323,20 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback buttonDispatcher.setAlpha(forceVisible ? 1f : alpha, animate); } } + + @Override + public void onOverviewShown(boolean fromHome) { + // If the overview has fixed orientation that may change display to natural rotation, + // we don't want the user rotation to be reset. So after user returns to application, + // it can keep in the original rotation. + mNavigationBarView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce(); + } + + @Override + public void onToggleRecentApps() { + // The same case as onOverviewShown but only for 3-button navigation. + mNavigationBarView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce(); + } }; private NavigationBarTransitions.DarkIntensityListener mOrientationHandleIntensityListener = @@ -628,6 +642,12 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback resetSecondaryHandle(); } else { int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation); + if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) { + // Curious if starting quickswitch can change between the if check and our delta + Log.d(TAG, "secondary nav delta rotation: " + deltaRotation + + " current: " + mCurrentRotation + + " starting: " + mStartingQuickSwitchRotation); + } int height = 0; int width = 0; Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds(); @@ -691,6 +711,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback dumpBarTransitions(pw, "mNavigationBarView", mNavigationBarView.getBarTransitions()); } + pw.print(" mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation); + pw.print(" mCurrentRotation=" + mCurrentRotation); pw.print(" mNavigationBarView="); if (mNavigationBarView == null) { pw.println("null"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 4821d8c46ed4..e83b159c4911 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -822,7 +822,7 @@ public class NavigationBarView extends FrameLayout implements */ public void updateSlippery() { setSlippery(!isQuickStepSwipeUpEnabled() || - (mPanelView.isFullyExpanded() && !mPanelView.isCollapsing())); + (mPanelView != null && mPanelView.isFullyExpanded() && !mPanelView.isCollapsing())); } private void setSlippery(boolean slippery) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java index daefef5e826d..c211de08cb8e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java @@ -16,14 +16,19 @@ package com.android.systemui.statusbar.phone; +import static android.content.Intent.ACTION_OVERLAY_CHANGED; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; import android.content.pm.PackageManager; import android.content.res.ApkAssets; +import android.os.PatternMatcher; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -79,6 +84,19 @@ public class NavigationModeController implements Dumpable { } }; + // The primary user SysUI process doesn't get AppInfo changes from overlay package changes for + // the secondary user (b/158613864), so we need to update the interaction mode here as well + // as a fallback if we don't receive the configuration change + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Log.d(TAG, "ACTION_OVERLAY_CHANGED"); + } + updateCurrentInteractionMode(true /* notify */); + } + }; + @Inject public NavigationModeController(Context context, @@ -92,6 +110,11 @@ public class NavigationModeController implements Dumpable { mUiBgExecutor = uiBgExecutor; deviceProvisionedController.addCallback(mDeviceProvisionedCallback); + IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); + overlayFilter.addDataScheme("package"); + overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL); + mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null); + configurationController.addCallback(new ConfigurationController.ConfigurationListener() { @Override public void onOverlayChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index d884bdd47930..e720d820fd76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -1328,11 +1328,15 @@ public class NotificationPanelViewController extends PanelViewController { * * @param velocity unit is in px / millis */ - public void stopWaitingForOpenPanelGesture(final float velocity) { + public void stopWaitingForOpenPanelGesture(boolean cancel, final float velocity) { if (mExpectingSynthesizedDown) { mExpectingSynthesizedDown = false; - maybeVibrateOnOpening(); - fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */); + if (cancel) { + collapse(false /* delayed */, 1.0f /* speedUpFactor */); + } else { + maybeVibrateOnOpening(); + fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */); + } onTrackingStopped(false); } } @@ -3071,7 +3075,7 @@ public class NotificationPanelViewController extends PanelViewController { return new TouchHandler() { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) { + if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) { return false; } initDownStates(event); @@ -3098,7 +3102,8 @@ public class NotificationPanelViewController extends PanelViewController { @Override public boolean onTouch(View v, MotionEvent event) { - if (mBlockTouches || (mQs != null && mQs.isCustomizing())) { + if (mBlockTouches || (mQsFullyExpanded && mQs != null + && mQs.disallowPanelTouches())) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java index de9c745cb357..59b10e416b03 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java @@ -74,6 +74,7 @@ public class RotationButtonController { private Consumer<Integer> mRotWatcherListener; private boolean mListenersRegistered = false; private boolean mIsNavigationBarShowing; + private boolean mSkipOverrideUserLockPrefsOnce; private final Runnable mRemoveRotationProposal = () -> setRotateSuggestionButtonState(false /* visible */); @@ -349,7 +350,20 @@ public class RotationButtonController { mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_SHOWN); } + /** + * Makes {@link #shouldOverrideUserLockPrefs} always return {@code false} once. It is used to + * avoid losing original user rotation when display rotation is changed by entering the fixed + * orientation overview. + */ + void setSkipOverrideUserLockPrefsOnce() { + mSkipOverrideUserLockPrefsOnce = true; + } + private boolean shouldOverrideUserLockPrefs(final int rotation) { + if (mSkipOverrideUserLockPrefsOnce) { + mSkipOverrideUserLockPrefsOnce = false; + return false; + } // Only override user prefs when returning to the natural rotation (normally portrait). // Don't let apps that force landscape or 180 alter user lock. return rotation == NATURAL_ROTATION; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index e2714af33247..a21ca5320518 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1772,6 +1772,9 @@ public class StatusBar extends SystemUI implements DemoMode, } public void setPanelExpanded(boolean isExpanded) { + if (mPanelExpanded != isExpanded) { + mNotificationLogger.onPanelExpandedChanged(isExpanded); + } mPanelExpanded = isExpanded; updateHideIconsForBouncer(false /* animate */); mNotificationShadeWindowController.setPanelExpanded(isExpanded); @@ -2090,7 +2093,7 @@ public class StatusBar extends SystemUI implements DemoMode, /** * Called when another window is about to transfer it's input focus. */ - public void onInputFocusTransfer(boolean start, float velocity) { + public void onInputFocusTransfer(boolean start, boolean cancel, float velocity) { if (!mCommandQueue.panelsEnabled()) { return; } @@ -2098,7 +2101,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (start) { mNotificationPanelViewController.startWaitingForOpenPanelGesture(); } else { - mNotificationPanelViewController.stopWaitingForOpenPanelGesture(velocity); + mNotificationPanelViewController.stopWaitingForOpenPanelGesture(cancel, velocity); } } @@ -2878,7 +2881,6 @@ public class StatusBar extends SystemUI implements DemoMode, } // Visibility reporting - protected void handleVisibleToUserChanged(boolean visibleToUser) { if (visibleToUser) { handleVisibleToUserChangedImpl(visibleToUser); @@ -2900,12 +2902,12 @@ public class StatusBar extends SystemUI implements DemoMode, } } - /** - * The LEDs are turned off when the notification panel is shown, even just a little bit. - * See also StatusBar.setPanelExpanded for another place where we attempt to do this. - */ - private void handleVisibleToUserChangedImpl(boolean visibleToUser) { + // Visibility reporting + + void handleVisibleToUserChangedImpl(boolean visibleToUser) { if (visibleToUser) { + /* The LEDs are turned off when the notification panel is shown, even just a little bit. + * See also StatusBar.setPanelExpanded for another place where we attempt to do this. */ boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); boolean clearNotificationEffects = !mPresenter.isPresenterFullyCollapsed() && @@ -3982,7 +3984,7 @@ public class StatusBar extends SystemUI implements DemoMode, } else if (mIsKeyguard && !unlocking) { mScrimController.transitionTo(ScrimState.KEYGUARD); } else if (mBubbleController.isStackExpanded()) { - mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED); + mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED, mUnlockScrimCallback); } else { mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index bc94cdeba37f..81d0699a29e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -460,6 +460,18 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } @Override + public void onStartedWakingUp() { + mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() + .setAnimationsDisabled(false); + } + + @Override + public void onStartedGoingToSleep() { + mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() + .setAnimationsDisabled(true); + } + + @Override public void onFinishedGoingToSleep() { mBouncer.onScreenTurnedOff(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java index 0db76ecf67fb..2d8784dc41bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java @@ -28,6 +28,7 @@ import android.graphics.PixelFormat; import android.graphics.RecordingCanvas; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.os.Trace; import android.view.RenderNodeAnimator; import android.view.View; import android.view.ViewConfiguration; @@ -73,6 +74,11 @@ public class KeyButtonRipple extends Drawable { private final HashSet<Animator> mRunningAnimations = new HashSet<>(); private final ArrayList<Animator> mTmpArray = new ArrayList<>(); + private final TraceAnimatorListener mExitHwTraceAnimator = + new TraceAnimatorListener("exitHardware"); + private final TraceAnimatorListener mEnterHwTraceAnimator = + new TraceAnimatorListener("enterHardware"); + public enum Type { OVAL, ROUNDED_RECT @@ -220,7 +226,7 @@ public class KeyButtonRipple extends Drawable { @Override public void jumpToCurrentState() { - cancelAnimations(); + endAnimations("jumpToCurrentState", false /* cancel */); } @Override @@ -253,13 +259,19 @@ public class KeyButtonRipple extends Drawable { mHandler.removeCallbacksAndMessages(null); } - private void cancelAnimations() { + private void endAnimations(String reason, boolean cancel) { + Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel); + Trace.endSection(); mVisible = false; mTmpArray.addAll(mRunningAnimations); int size = mTmpArray.size(); for (int i = 0; i < size; i++) { Animator a = mTmpArray.get(i); - a.cancel(); + if (cancel) { + a.cancel(); + } else { + a.end(); + } } mTmpArray.clear(); mRunningAnimations.clear(); @@ -284,7 +296,7 @@ public class KeyButtonRipple extends Drawable { } private void enterSoftware() { - cancelAnimations(); + endAnimations("enterSoftware", true /* cancel */); mVisible = true; mGlowAlpha = getMaxGlowAlpha(); ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale", @@ -370,7 +382,7 @@ public class KeyButtonRipple extends Drawable { } private void enterHardware() { - cancelAnimations(); + endAnimations("enterHardware", true /* cancel */); mVisible = true; mDrawingHardwareGlow = true; setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2)); @@ -387,6 +399,7 @@ public class KeyButtonRipple extends Drawable { endAnim.setDuration(ANIMATION_DURATION_SCALE); endAnim.setInterpolator(mInterpolator); endAnim.addListener(mAnimatorListener); + endAnim.addListener(mEnterHwTraceAnimator); endAnim.setTarget(mTargetView); if (isHorizontal()) { @@ -428,6 +441,7 @@ public class KeyButtonRipple extends Drawable { opacityAnim.setDuration(ANIMATION_DURATION_FADE); opacityAnim.setInterpolator(Interpolators.ALPHA_OUT); opacityAnim.addListener(mAnimatorListener); + opacityAnim.addListener(mExitHwTraceAnimator); opacityAnim.setTarget(mTargetView); opacityAnim.start(); @@ -438,16 +452,41 @@ public class KeyButtonRipple extends Drawable { private final AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRunningAnimations.remove(animation); + if (mRunningAnimations.isEmpty() && !mPressed) { + mVisible = false; + mDrawingHardwareGlow = false; + invalidateSelf(); + } + } + }; + + private static final class TraceAnimatorListener extends AnimatorListenerAdapter { + private final String mName; + TraceAnimatorListener(String name) { + mName = name; + } + + @Override + public void onAnimationStart(Animator animation) { + Trace.beginSection("KeyButtonRipple.start." + mName); + Trace.endSection(); + } + + @Override + public void onAnimationCancel(Animator animation) { + Trace.beginSection("KeyButtonRipple.cancel." + mName); + Trace.endSection(); + } + @Override public void onAnimationEnd(Animator animation) { - mRunningAnimations.remove(animation); - if (mRunningAnimations.isEmpty() && !mPressed) { - mVisible = false; - mDrawingHardwareGlow = false; - invalidateSelf(); - } + Trace.beginSection("KeyButtonRipple.end." + mName); + Trace.endSection(); } - }; + } /** * Interpolator with a smooth log deceleration diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 0ca8ef009173..8d7ecd09e760 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -441,6 +441,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @Override public void abortCurrentGesture() { + Log.d("b/63783866", "KeyButtonView.abortCurrentGesture"); setPressed(false); mRipple.abortDelayedRipple(); mGestureAborted = true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 270f248e1a34..ce5bb0508c0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -367,8 +367,14 @@ public class UserSwitcherController implements Dumpable { int id; if (record.isGuest && record.info == null) { // No guest user. Create one. - UserInfo guest = mUserManager.createGuest( - mContext, mContext.getString(com.android.settingslib.R.string.guest_nickname)); + UserInfo guest; + try { + guest = mUserManager.createGuest(mContext, + mContext.getString(com.android.settingslib.R.string.guest_nickname)); + } catch (UserManager.UserOperationException e) { + Log.e(TAG, "Couldn't create guest user", e); + return; + } if (guest == null) { // Couldn't create guest, most likely because there already exists one, we just // haven't reloaded the user list yet. diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt index c91033e4745a..631ea9d61361 100644 --- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt +++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt @@ -22,4 +22,14 @@ import android.view.ViewGroup val ViewGroup.children get() = sequence { for (i in 0 until childCount) yield(getChildAt(i)) - }
\ No newline at end of file + } + +/** Inclusive version of [Iterable.takeWhile] */ +fun <T> Sequence<T>.takeUntil(pred: (T) -> Boolean): Sequence<T> = sequence { + for (x in this@takeUntil) { + yield(x) + if (pred(x)) { + break + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt new file mode 100644 index 000000000000..bab93475c8bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt @@ -0,0 +1,60 @@ +/* + * 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.util + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout + +/** + * Basically a normal linear layout but doesn't grow its children with weight 1 even when its + * measured with exactly. + */ +class NeverExactlyLinearLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + + val (widthExactly, usedWidthSpec, width) = getNonExactlyMeasureSpec(widthMeasureSpec) + val (heightExactly, usedHeightSpec, height) = getNonExactlyMeasureSpec(heightMeasureSpec) + + super.onMeasure(usedWidthSpec, usedHeightSpec) + if (widthExactly || heightExactly) { + val newWidth = if (widthExactly) width else measuredWidth + val newHeight = if (heightExactly) height else measuredHeight + setMeasuredDimension(newWidth, newHeight) + } + } + + /** + * Obtain a measurespec that's not exactly + * + * @return a triple, where we return 1. if this was exactly, 2. the new measurespec, 3. the size + * of the measurespec + */ + private fun getNonExactlyMeasureSpec(measureSpec: Int): Triple<Boolean, Int, Int> { + var newSpec = measureSpec + val isExactly = MeasureSpec.getMode(measureSpec) == MeasureSpec.EXACTLY + val size = MeasureSpec.getSize(measureSpec) + if (isExactly) { + newSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST) + } + return Triple(isExactly, newSpec, size) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt b/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt index accb81eae32a..1a25c84a7965 100644 --- a/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt @@ -19,6 +19,22 @@ package com.android.systemui.util import android.util.SparseArray /** + * Transforms a sequence of Key/Value pairs into a SparseArray. + * + * See [kotlin.collections.toMap]. + */ +fun <T> Sequence<Pair<Int, T>>.toSparseArray(size: Int = -1): SparseArray<T> { + val sparseArray = when { + size < 0 -> SparseArray<T>() + else -> SparseArray<T>(size) + } + for ((i, v) in this) { + sparseArray.put(i, v) + } + return sparseArray +} + +/** * Transforms an [Array] into a [SparseArray], by applying each element to [keySelector] in order to * generate the index at which it will be placed. If two elements produce the same index, the latter * replaces the former in the final result. diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt index ecd3afd687b3..a284a747da21 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt @@ -67,6 +67,40 @@ class FloatProperties { } /** + * Represents the width of a [Rect]. Typically used to animate resizing a Rect horizontally. + * + * This property's getter returns [Rect.width], and its setter changes the value of + * [Rect.right] by adding the animated width value to [Rect.left]. + */ + @JvmField + val RECT_WIDTH = object : FloatPropertyCompat<Rect>("RectWidth") { + override fun getValue(rect: Rect): Float { + return rect.width().toFloat() + } + + override fun setValue(rect: Rect, value: Float) { + rect.right = rect.left + value.toInt() + } + } + + /** + * Represents the height of a [Rect]. Typically used to animate resizing a Rect vertically. + * + * This property's getter returns [Rect.height], and its setter changes the value of + * [Rect.bottom] by adding the animated height value to [Rect.top]. + */ + @JvmField + val RECT_HEIGHT = object : FloatPropertyCompat<Rect>("RectHeight") { + override fun getValue(rect: Rect): Float { + return rect.height().toFloat() + } + + override fun setValue(rect: Rect, value: Float) { + rect.bottom = rect.top + value.toInt() + } + } + + /** * Represents the x-coordinate of a [RectF]. Typically used to animate moving a RectF * horizontally. * diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt index 701ff5ecf8a1..e5b126d7ff7f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt @@ -17,6 +17,8 @@ package com.android.systemui.util.animation import android.content.Context +import android.graphics.Canvas +import android.graphics.PointF import android.graphics.Rect import android.util.AttributeSet import android.view.View @@ -36,7 +38,9 @@ class TransitionLayout @JvmOverloads constructor( defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) { + private val boundsRect = Rect() private val originalGoneChildrenSet: MutableSet<Int> = mutableSetOf() + private val originalViewAlphas: MutableMap<Int, Float> = mutableMapOf() private var measureAsConstraint: Boolean = false private var currentState: TransitionViewState = TransitionViewState() private var updateScheduled = false @@ -67,6 +71,7 @@ class TransitionLayout @JvmOverloads constructor( if (child.visibility == GONE) { originalGoneChildrenSet.add(child.id) } + originalViewAlphas[child.id] = child.alpha } } @@ -144,11 +149,26 @@ class TransitionLayout @JvmOverloads constructor( } } + override fun dispatchDraw(canvas: Canvas?) { + val clip = !boundsRect.isEmpty + if (clip) { + canvas?.save() + canvas?.clipRect(boundsRect) + } + super.dispatchDraw(canvas) + if (clip) { + canvas?.restore() + } + } + private fun updateBounds() { val layoutLeft = left val layoutTop = top setLeftTopRightBottom(layoutLeft, layoutTop, layoutLeft + currentState.width, layoutTop + currentState.height) + translationX = currentState.translation.x + translationY = currentState.translation.y + boundsRect.set(0, 0, (width + translationX).toInt(), (height + translationY).toInt()) } /** @@ -198,6 +218,8 @@ class TransitionLayout @JvmOverloads constructor( if (originalGoneChildrenSet.contains(child.id)) { child.visibility = View.GONE } + // Reset the alphas, to only have the alphas present from the constraintset + child.alpha = originalViewAlphas[child.id] ?: 1.0f } // Let's now apply the constraintSet to get the full state constraintSet.applyTo(this) @@ -230,11 +252,13 @@ class TransitionViewState { var widgetStates: MutableMap<Int, WidgetState> = mutableMapOf() var width: Int = 0 var height: Int = 0 + val translation = PointF() fun copy(reusedState: TransitionViewState? = null): TransitionViewState { // we need a deep copy of this, so we can't use a data class val copy = reusedState ?: TransitionViewState() copy.width = width copy.height = height + copy.translation.set(translation.x, translation.y) for (entry in widgetStates) { copy.widgetStates[entry.key] = entry.value.copy() } @@ -252,6 +276,7 @@ class TransitionViewState { } width = transitionLayout.measuredWidth height = transitionLayout.measuredHeight + translation.set(0.0f, 0.0f) } } diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt index 9ee141053861..b73aeb30009c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt @@ -17,6 +17,7 @@ package com.android.systemui.util.animation import android.animation.ValueAnimator +import android.graphics.PointF import android.util.MathUtils import com.android.systemui.Interpolators @@ -43,6 +44,7 @@ open class TransitionLayoutController { private var currentState = TransitionViewState() private var animationStartState: TransitionViewState? = null private var state = TransitionViewState() + private var pivot = PointF() private var animator: ValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f) init { @@ -63,6 +65,7 @@ open class TransitionLayoutController { startState = animationStartState!!, endState = state, progress = animator.animatedFraction, + pivot = pivot, resultState = currentState) view.setState(currentState) } @@ -75,8 +78,10 @@ open class TransitionLayoutController { startState: TransitionViewState, endState: TransitionViewState, progress: Float, + pivot: PointF, resultState: TransitionViewState ) { + this.pivot.set(pivot) val view = transitionLayout ?: return val childCount = view.childCount for (i in 0 until childCount) { @@ -178,6 +183,8 @@ open class TransitionLayoutController { progress).toInt() height = MathUtils.lerp(startState.height.toFloat(), endState.height.toFloat(), progress).toInt() + translation.x = (endState.width - width) * pivot.x + translation.y = (endState.height - height) * pivot.y } } diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt index 5a2b064c5389..47b607fc6285 100644 --- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt @@ -178,6 +178,18 @@ abstract class MagnetizedObject<T : Any>( var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null /** + * Method that is called when the object should be animated stuck to the target. The default + * implementation uses the object's x and y properties to animate the object centered inside the + * target. You can override this if you need custom animation. + * + * The method is invoked with the MagneticTarget that the object is sticking to, the X and Y + * velocities of the gesture that brought the object into the magnetic radius, whether or not it + * was flung, and a callback you must call after your animation completes. + */ + var animateStuckToTarget: (MagneticTarget, Float, Float, Boolean, (() -> Unit)?) -> Unit = + ::animateStuckToTargetInternal + + /** * Sets whether forcefully flinging the object vertically towards a target causes it to be * attracted to the target and then released immediately, despite never being dragged within the * magnetic field. @@ -373,7 +385,7 @@ abstract class MagnetizedObject<T : Any>( targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf cancelAnimations() magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!) - animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false) + animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false, null) vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK) } else if (targetObjectIsInMagneticFieldOf == null && objectStuckToTarget) { @@ -430,8 +442,8 @@ abstract class MagnetizedObject<T : Any>( targetObjectIsStuckTo = flungToTarget animateStuckToTarget(flungToTarget, velX, velY, true) { - targetObjectIsStuckTo = null magnetListener.onReleasedInTarget(flungToTarget) + targetObjectIsStuckTo = null vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK) } @@ -465,7 +477,7 @@ abstract class MagnetizedObject<T : Any>( } /** Animates sticking the object to the provided target with the given start velocities. */ - private fun animateStuckToTarget( + private fun animateStuckToTargetInternal( target: MagneticTarget, velX: Float, velY: Float, @@ -581,10 +593,10 @@ abstract class MagnetizedObject<T : Any>( * multiple objects. */ class MagneticTarget( - internal val targetView: View, + val targetView: View, var magneticFieldRadiusPx: Int ) { - internal val centerOnScreen = PointF() + val centerOnScreen = PointF() private val tempLoc = IntArray(2) diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index 6131e3b504af..369552fc814d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -340,7 +340,7 @@ public class Events { } /** - * Logs an event to the event log and UiEvent (Westworld) logging. Compare writeEvent, which + * Logs an event to the event log and UiEvent (statsd) logging. Compare writeEvent, which * adds more log destinations. * @param tag One of the EVENT_* codes above. * @param list Any additional event-specific arguments, documented above. diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index 32e3a7fc032e..7b114525adcd 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -19,11 +19,13 @@ package com.android.systemui.wm; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; +import android.os.ServiceManager; import android.util.Slog; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; @@ -36,6 +38,7 @@ import android.view.WindowInsets; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import com.android.internal.view.IInputMethodManager; import com.android.systemui.TransactionPool; import com.android.systemui.dagger.qualifiers.Main; @@ -352,6 +355,16 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged dispatchEndPositioning(mDisplayId, mCancelled, t); if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { t.hide(mImeSourceControl.getLeash()); + final IInputMethodManager imms = getImms(); + if (imms != null) { + try { + // Remove the IME surface to make the insets invisible for + // non-client controlled insets. + imms.removeImeSurface(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to remove IME surface.", e); + } + } } t.apply(); mTransactionPool.release(t); @@ -382,9 +395,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged * Called when the IME position is starting to animate. * * @param hiddenTop The y position of the top of the IME surface when it is hidden. - * @param shownTop The y position of the top of the IME surface when it is shown. - * @param showing {@code true} when we are animating from hidden to shown, {@code false} - * when animating from shown to hidden. + * @param shownTop The y position of the top of the IME surface when it is shown. + * @param showing {@code true} when we are animating from hidden to shown, {@code false} + * when animating from shown to hidden. */ default void onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, SurfaceControl.Transaction t) {} @@ -406,4 +419,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged default void onImeEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t) {} } + + public IInputMethodManager getImms() { + return IInputMethodManager.Stub.asInterface( + ServiceManager.getService(Context.INPUT_METHOD_SERVICE)); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index e324d844144c..a867825e223d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -18,7 +18,9 @@ package com.android.keyguard; import static android.view.WindowInsets.Type.ime; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -110,6 +112,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { mKeyguardSecurityContainer.startDisappearAnimation(null); verify(mSecurityView).startDisappearAnimation(eq(null)); - verify(mWindowInsetsController).hide(eq(ime())); + verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(), + any(), any()); } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 7bc453ac9aa1..023879926563 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -452,12 +452,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void requiresAuthentication_whenEncryptedKeyguard_andBypass() { - testStrongAuthExceptOnBouncer( - KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT); - } - - @Test public void requiresAuthentication_whenTimeoutKeyguard_andBypass() { testStrongAuthExceptOnBouncer( KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); @@ -513,10 +507,20 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testIgnoresAuth_whenLockdown() { + testIgnoresAuth( + KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + } + + @Test + public void testIgnoresAuth_whenEncrypted() { + testIgnoresAuth( + KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT); + } + + private void testIgnoresAuth(int strongAuth) { mKeyguardUpdateMonitor.dispatchStartedWakingUp(); mTestableLooper.processAllMessages(); - when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn( - KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth); mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); verify(mFaceManager, never()).authenticate(any(), any(), anyInt(), any(), any(), anyInt()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java index b2c35867e789..713aef9e8a91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java @@ -35,6 +35,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.FakeBroadcastDispatcher; +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.FalsingManager; @@ -72,8 +73,8 @@ public abstract class SysuiTestCase { public void SysuiSetup() throws Exception { SystemUIFactory.createFromConfig(mContext); mDependency = new TestableDependency(mContext); - mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Handler.class), - mock(Looper.class), mock(DumpManager.class)); + mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.class), + mock(DumpManager.class), mock(BroadcastDispatcherLogger.class)); mRealInstrumentation = InstrumentationRegistry.getInstrumentation(); Instrumentation inst = spy(mRealInstrumentation); diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt index 3357c5863d46..4ed284ede634 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.broadcast import android.content.BroadcastReceiver import android.content.Context +import android.content.Intent import android.content.IntentFilter import android.os.Handler import android.os.Looper @@ -27,6 +28,7 @@ import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dump.DumpManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -76,6 +78,8 @@ class BroadcastDispatcherTest : SysuiTestCase() { private lateinit var intentFilterOther: IntentFilter @Mock private lateinit var mockHandler: Handler + @Mock + private lateinit var logger: BroadcastDispatcherLogger private lateinit var executor: Executor @@ -93,9 +97,9 @@ class BroadcastDispatcherTest : SysuiTestCase() { broadcastDispatcher = TestBroadcastDispatcher( mockContext, - Handler(testableLooper.looper), testableLooper.looper, mock(DumpManager::class.java), + logger, mapOf(0 to mockUBRUser0, 1 to mockUBRUser1)) // These should be valid filters @@ -173,7 +177,12 @@ class BroadcastDispatcherTest : SysuiTestCase() { @Test fun testRegisterCurrentAsActualUser() { - setUserMock(mockContext, user1) + val intent = Intent(Intent.ACTION_USER_SWITCHED).apply { + putExtra(Intent.EXTRA_USER_HANDLE, user1.identifier) + } + broadcastDispatcher.onReceive(mockContext, intent) + testableLooper.processAllMessages() + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, mockHandler, UserHandle.CURRENT) @@ -236,11 +245,11 @@ class BroadcastDispatcherTest : SysuiTestCase() { private class TestBroadcastDispatcher( context: Context, - mainHandler: Handler, bgLooper: Looper, dumpManager: DumpManager, + logger: BroadcastDispatcherLogger, var mockUBRMap: Map<Int, UserBroadcastDispatcher> - ) : BroadcastDispatcher(context, mainHandler, bgLooper, dumpManager) { + ) : BroadcastDispatcher(context, bgLooper, dumpManager, logger) { override fun createUBRForUser(userId: Int): UserBroadcastDispatcher { return mockUBRMap.getOrDefault(userId, mock(UserBroadcastDispatcher::class.java)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt index 9a5773a7a3b4..09a091689a23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt @@ -24,15 +24,16 @@ import android.os.UserHandle import android.util.ArraySet import android.util.Log import com.android.systemui.SysuiTestableContext +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dump.DumpManager import java.util.concurrent.Executor class FakeBroadcastDispatcher( context: SysuiTestableContext, - handler: Handler, looper: Looper, - dumpManager: DumpManager -) : BroadcastDispatcher(context, handler, looper, dumpManager) { + dumpManager: DumpManager, + logger: BroadcastDispatcherLogger +) : BroadcastDispatcher(context, looper, dumpManager, logger) { private val registeredReceivers = ArraySet<BroadcastReceiver>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt index 847e442f1a49..443357694f4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt @@ -26,6 +26,7 @@ import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import junit.framework.Assert.assertEquals @@ -40,6 +41,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.anyString import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.never @@ -62,6 +64,8 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { private val USER_HANDLE = UserHandle.of(USER_ID) fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() + fun <T> any(): T = Mockito.any() + fun <T> eq(v: T) = Mockito.eq(v) ?: v } @Mock @@ -72,6 +76,8 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { private lateinit var mockContext: Context @Mock private lateinit var mPendingResult: BroadcastReceiver.PendingResult + @Mock + private lateinit var logger: BroadcastDispatcherLogger @Captor private lateinit var argumentCaptor: ArgumentCaptor<IntentFilter> @@ -91,7 +97,7 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { fakeExecutor = FakeExecutor(FakeSystemClock()) userBroadcastDispatcher = UserBroadcastDispatcher( - mockContext, USER_ID, testableLooper.looper) + mockContext, USER_ID, testableLooper.looper, logger) userBroadcastDispatcher.pendingResult = mPendingResult } @@ -106,6 +112,13 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { } @Test + fun testNotRegisteredOnStart_logging() { + testableLooper.processAllMessages() + + verify(logger, never()).logContextReceiverRegistered(anyInt(), any()) + } + + @Test fun testSingleReceiverRegistered() { intentFilter = IntentFilter(ACTION_1) @@ -126,6 +139,18 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { } @Test + fun testSingleReceiverRegistered_logging() { + intentFilter = IntentFilter(ACTION_1) + + userBroadcastDispatcher.registerReceiver( + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) + testableLooper.processAllMessages() + + verify(logger).logReceiverRegistered(USER_HANDLE.identifier, broadcastReceiver) + verify(logger).logContextReceiverRegistered(eq(USER_HANDLE.identifier), any()) + } + + @Test fun testSingleReceiverUnregistered() { intentFilter = IntentFilter(ACTION_1) @@ -145,6 +170,21 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { } @Test + fun testSingleReceiverUnregistered_logger() { + intentFilter = IntentFilter(ACTION_1) + + userBroadcastDispatcher.registerReceiver( + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) + testableLooper.processAllMessages() + + userBroadcastDispatcher.unregisterReceiver(broadcastReceiver) + testableLooper.processAllMessages() + + verify(logger).logReceiverUnregistered(USER_HANDLE.identifier, broadcastReceiver) + verify(logger).logContextReceiverUnregistered(USER_HANDLE.identifier) + } + + @Test fun testFilterHasAllActionsAndCategories_twoReceivers() { intentFilter = IntentFilter(ACTION_1) intentFilterOther = IntentFilter(ACTION_2).apply { @@ -196,6 +236,30 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { } @Test + fun testDispatch_logger() { + intentFilter = IntentFilter(ACTION_1) + intentFilterOther = IntentFilter(ACTION_2) + + userBroadcastDispatcher.registerReceiver( + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) + userBroadcastDispatcher.registerReceiver( + ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE)) + + val intent = Intent(ACTION_2) + + userBroadcastDispatcher.onReceive(mockContext, intent) + testableLooper.processAllMessages() + fakeExecutor.runAllReady() + + val captor = ArgumentCaptor.forClass(Int::class.java) + verify(logger) + .logBroadcastReceived(captor.capture(), eq(USER_HANDLE.identifier), eq(intent)) + verify(logger).logBroadcastDispatched(captor.value, ACTION_2, broadcastReceiverOther) + verify(logger, never()) + .logBroadcastDispatched(eq(captor.value), any(), eq(broadcastReceiver)) + } + + @Test fun testDispatchToCorrectReceiver_differentFiltersSameReceiver() { intentFilter = IntentFilter(ACTION_1) intentFilterOther = IntentFilter(ACTION_2) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 1e48b990b19d..36398a6fc122 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -92,6 +92,7 @@ import com.android.systemui.util.FloatingContentCoordinator; import com.google.common.collect.ImmutableList; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -303,6 +304,10 @@ public class BubbleControllerTest extends SysuiTestCase { public void testPromoteBubble_autoExpand() throws Exception { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); mBubbleController.removeBubble( mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); @@ -330,6 +335,10 @@ public class BubbleControllerTest extends SysuiTestCase { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry(), /* suppressFlyout */ false, /* showInShade */ true); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); mBubbleController.removeBubble( mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); @@ -407,6 +416,7 @@ public class BubbleControllerTest extends SysuiTestCase { } @Test + @Ignore("Currently broken.") public void testCollapseAfterChangingExpandedBubble() { // Mark it as a bubble and add it explicitly mEntryListener.onPendingEntryAdded(mRow.getEntry()); @@ -431,15 +441,16 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mSysUiStateBubblesExpanded); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Switch which bubble is expanded - mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey( + mRow.getEntry().getKey())); mBubbleData.setExpanded(true); - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -541,27 +552,27 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Dismiss currently expanded mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()) - .getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()) - .getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -837,6 +848,12 @@ public class BubbleControllerTest extends SysuiTestCase { mRow2.getEntry(), /* suppressFlyout */ false, /* showInShade */ false); mBubbleController.updateBubble( mRow3.getEntry(), /* suppressFlyout */ false, /* showInShade */ false); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow3.getEntry().getKey())) + .thenReturn(mRow3.getEntry()); assertEquals(mBubbleData.getBubbles().size(), 3); mBubbleData.setMaxOverflowBubbles(1); @@ -906,6 +923,8 @@ public class BubbleControllerTest extends SysuiTestCase { // GIVEN a group summary with a bubble child ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -925,6 +944,8 @@ public class BubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -946,6 +967,8 @@ public class BubbleControllerTest extends SysuiTestCase { // GIVEN a group summary with two (non-bubble) children and one bubble child ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index 17022da3ecde..1ca2f02db1bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -537,7 +537,7 @@ public class BubbleDataTest extends SysuiTestCase { // Verify the selection was cleared. verifyUpdateReceived(); assertThat(mBubbleData.isExpanded()).isFalse(); - assertSelectionCleared(); + assertThat(mBubbleData.getSelectedBubble()).isNull(); } // EXPANDED / ADD / UPDATE diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java index 72f816ff56b5..be03923e7264 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java @@ -86,8 +86,7 @@ public class BubbleTest extends SysuiTestCase { final String msg = "Hello there!"; doReturn(Notification.Style.class).when(mNotif).getNotificationStyle(); mExtras.putCharSequence(Notification.EXTRA_TEXT, msg); - assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -98,8 +97,7 @@ public class BubbleTest extends SysuiTestCase { mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg); // Should be big text, not the small text. - assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -107,8 +105,7 @@ public class BubbleTest extends SysuiTestCase { doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle(); // Media notifs don't get update messages. - assertNull(BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertNull(BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -124,7 +121,7 @@ public class BubbleTest extends SysuiTestCase { // Should be the last one only. assertEquals("Really? I prefer them that way.", - BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message); + BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -139,11 +136,8 @@ public class BubbleTest extends SysuiTestCase { "Oh, hello!", 0, "Mady").toBundle()}); // Should be the last one only. - assertEquals("Oh, hello!", - BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message); - assertEquals("Mady", - BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).senderName); + assertEquals("Oh, hello!", BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); + assertEquals("Mady", BubbleViewInfoTask.extractFlyoutMessage(mEntry).senderName); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index 0be24729dff9..1c70db3a548e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -87,6 +87,7 @@ import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.FloatingContentCoordinator; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -301,6 +302,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { public void testRemoveBubble_withDismissedNotif_notInOverflow() { mEntryListener.onEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); @@ -364,6 +367,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { } @Test + @Ignore("Currently broken.") public void testCollapseAfterChangingExpandedBubble() { // Mark it as a bubble and add it explicitly mEntryListener.onEntryAdded(mRow.getEntry()); @@ -386,14 +390,14 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { true, mRow.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry())); // Switch which bubble is expanded mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); mBubbleData.setExpanded(true); - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -486,27 +490,27 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Dismiss currently expanded mBubbleController.removeBubble( mBubbleData.getBubbleInStackWithKey( - stackView.getExpandedBubble().getKey()).getEntry().getKey(), + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one mBubbleController.removeBubble( mBubbleData.getBubbleInStackWithKey( - stackView.getExpandedBubble().getKey()).getEntry().getKey(), + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -765,6 +769,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -783,6 +789,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -805,6 +813,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); // WHEN the summary is dismissed diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt index 1d02b8dba910..9b8fd11febe3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt @@ -32,7 +32,7 @@ class BubblePersistentRepositoryTest : SysuiTestCase() { private val bubbles = listOf( BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0), - BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428), + BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428, "title"), BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) ) private lateinit var repository: BubblePersistentRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt index f9d611c2bb33..76c58339726c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -37,9 +37,10 @@ class BubbleVolatileRepositoryTest : SysuiTestCase() { private val user0 = UserHandle.of(0) private val user10 = UserHandle.of(10) - private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1", 120, 0) - private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2", 0, 16537428) - private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3", 120, 0) + private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0) + private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", + "key-2", 0, 16537428, "title") + private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) private val bubbles = listOf(bubble1, bubble2, bubble3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt index 49467874dd8b..81687c7fbe1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt @@ -31,17 +31,17 @@ import java.io.ByteArrayOutputStream class BubbleXmlHelperTest : SysuiTestCase() { private val bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), + BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title"), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) ) @Test fun testWriteXml() { val expectedEntries = """ - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> """.trimIndent() ByteArrayOutputStream().use { writeXml(it, bubbles) @@ -54,12 +54,12 @@ class BubbleXmlHelperTest : SysuiTestCase() { @Test fun testReadXml() { val src = """ - <?xml version='1.0' encoding='utf-8' standalone='yes' ?> - <bs> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> - </bs> +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<bs> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +</bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) assertEquals("failed parsing bubbles from xml\n$src", bubbles, actual) 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 329af2b7f62b..e08fe7a13a60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -16,6 +16,8 @@ package com.android.systemui.globalactions; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -34,11 +36,13 @@ import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.content.ContentResolver; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Color; import android.media.AudioManager; import android.net.ConnectivityManager; import android.os.Handler; +import android.os.RemoteException; import android.os.UserManager; import android.service.dreams.IDreamManager; import android.telephony.TelephonyManager; @@ -424,10 +428,12 @@ public class GlobalActionsDialogTest extends SysuiTestCase { } @Test - public void testShouldShowLockScreenMessage() { + public void testShouldShowLockScreenMessage() throws RemoteException { mGlobalActionsDialog = spy(mGlobalActionsDialog); mGlobalActionsDialog.mDialog = null; when(mKeyguardStateController.isUnlocked()).thenReturn(false); + when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); + when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); mGlobalActionsDialog.mShowLockScreenCardsAndControls = false; setupDefaultActions(); when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); @@ -438,13 +444,19 @@ public class GlobalActionsDialogTest extends SysuiTestCase { GlobalActionsDialog.ActionsDialog dialog = mGlobalActionsDialog.mDialog; assertThat(dialog).isNotNull(); assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.VISIBLE); + + // Dismiss the dialog so that it does not pollute other tests + mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); } @Test - public void testShouldNotShowLockScreenMessage_whenWalletOrControlsShownOnLockScreen() { + public void testShouldNotShowLockScreenMessage_whenWalletOrControlsShownOnLockScreen() + throws RemoteException { mGlobalActionsDialog = spy(mGlobalActionsDialog); mGlobalActionsDialog.mDialog = null; when(mKeyguardStateController.isUnlocked()).thenReturn(false); + when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); + when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); mGlobalActionsDialog.mShowLockScreenCardsAndControls = true; setupDefaultActions(); when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); @@ -455,13 +467,20 @@ public class GlobalActionsDialogTest extends SysuiTestCase { GlobalActionsDialog.ActionsDialog dialog = mGlobalActionsDialog.mDialog; assertThat(dialog).isNotNull(); assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE); + + // Dismiss the dialog so that it does not pollute other tests + mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); } @Test - public void testShouldNotShowLockScreenMessage_whenControlsAndWalletBothDisabled() { + public void testShouldNotShowLockScreenMessage_whenControlsAndWalletBothDisabled() + throws RemoteException { mGlobalActionsDialog = spy(mGlobalActionsDialog); mGlobalActionsDialog.mDialog = null; when(mKeyguardStateController.isUnlocked()).thenReturn(false); + + when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); + when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); mGlobalActionsDialog.mShowLockScreenCardsAndControls = true; setupDefaultActions(); when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); @@ -473,6 +492,13 @@ public class GlobalActionsDialogTest extends SysuiTestCase { GlobalActionsDialog.ActionsDialog dialog = mGlobalActionsDialog.mDialog; assertThat(dialog).isNotNull(); assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE); + + // Dismiss the dialog so that it does not pollute other tests + mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); + } + + private UserInfo newUserInfo() { + return new UserInfo(0, null, null, UserInfo.FLAG_PRIMARY, null); } private void setupDefaultActions() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 737ced63eed0..e6a41fbac3b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -31,6 +31,7 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.SeekBar import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.LiveData import androidx.test.filters.SmallTest import com.android.systemui.R @@ -75,9 +76,9 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var holder: PlayerViewHolder @Mock private lateinit var view: TransitionLayout - @Mock private lateinit var mediaHostStatesManager: MediaHostStatesManager @Mock private lateinit var seekBarViewModel: SeekBarViewModel @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress> + @Mock private lateinit var mediaViewController: MediaViewController private lateinit var appIcon: ImageView private lateinit var appName: TextView private lateinit var albumView: ImageView @@ -104,8 +105,10 @@ public class MediaControlPanelTest : SysuiTestCase() { @Before fun setUp() { bgExecutor = FakeExecutor(FakeSystemClock()) + whenever(mediaViewController.expandedLayout).thenReturn(mock(ConstraintSet::class.java)) + whenever(mediaViewController.collapsedLayout).thenReturn(mock(ConstraintSet::class.java)) - player = MediaControlPanel(context, bgExecutor, activityStarter, mediaHostStatesManager, + player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController, seekBarViewModel) whenever(seekBarViewModel.progress).thenReturn(seekBarData) @@ -172,7 +175,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindWhenUnattached() { val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, null, null, device, null) + emptyList(), PACKAGE, null, null, device, true, null) player.bind(state) assertThat(player.isPlaying()).isFalse() } @@ -181,7 +184,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindText() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device, null) + emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) player.bind(state) assertThat(appName.getText()).isEqualTo(APP) assertThat(titleText.getText()).isEqualTo(TITLE) @@ -192,7 +195,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindBackgroundColor() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device, null) + emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) player.bind(state) val list = ArgumentCaptor.forClass(ColorStateList::class.java) verify(view).setBackgroundTintList(list.capture()) @@ -203,7 +206,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device, null) + emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) player.bind(state) assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) assertThat(seamless.isEnabled()).isTrue() @@ -213,7 +216,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindDisabledDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, null) + emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null) player.bind(state) assertThat(seamless.isEnabled()).isFalse() assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( @@ -224,7 +227,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindNullDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, null, null) + emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null) player.bind(state) assertThat(seamless.isEnabled()).isTrue() assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index bed5c9eb6df5..618ee892b2b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -79,7 +79,8 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mManager.addListener(mListener); mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, - new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, null, KEY, false); + new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, KEY, + false); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index 6fcf6e37572b..6c7f2e8d7925 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -71,6 +72,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var lmm: LocalMediaManager @Mock private lateinit var mr2: MediaRouter2Manager private lateinit var fakeExecutor: FakeExecutor + @Mock private lateinit var dumpster: DumpManager @Mock private lateinit var listener: MediaDeviceManager.Listener @Mock private lateinit var device: MediaDevice @Mock private lateinit var icon: Drawable @@ -85,7 +87,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Before fun setUp() { fakeExecutor = FakeExecutor(FakeSystemClock()) - manager = MediaDeviceManager(context, lmmFactory, mr2, fakeExecutor, mediaDataManager) + manager = MediaDeviceManager(context, lmmFactory, mr2, fakeExecutor, mediaDataManager, + dumpster) manager.addListener(listener) // Configure mocks. @@ -116,7 +119,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken())) } mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null, - emptyList(), emptyList(), PACKAGE, session.sessionToken, null, null, null) + emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null, + device = null, active = true, resumeAction = null) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt index 7d44327b0d38..916fd0fe11b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -38,6 +38,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.`when` import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @@ -93,11 +94,16 @@ class MediaTimeoutListenerTest : SysuiTestCase() { } session.setActive(true) mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null, - emptyList(), emptyList(), PACKAGE, session.sessionToken, null, null, null) + emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null, + device = null, active = true, resumeAction = null) } @Test fun testOnMediaDataLoaded_registersPlaybackListener() { + val playingState = mock(android.media.session.PlaybackState::class.java) + `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING) + + `when`(mediaController.playbackState).thenReturn(playingState) mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) @@ -108,6 +114,13 @@ class MediaTimeoutListenerTest : SysuiTestCase() { } @Test + fun testOnMediaDataLoaded_registersTimeout_whenPaused() { + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) + verify(executor).executeDelayed(capture(timeoutCaptor), anyLong()) + } + + @Test fun testOnMediaDataRemoved_unregistersPlaybackListener() { mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) mediaTimeoutListener.onMediaDataRemoved(KEY) @@ -164,4 +177,4 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) assertThat(mediaTimeoutListener.isTimedOut(KEY)).isFalse() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt index 24e9bd837d5d..c8ef9fbf06e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt @@ -40,6 +40,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.any +import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -204,7 +205,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Test fun updateElapsedTime() { - // GIVEN that the PlaybackState contins the current position + // GIVEN that the PlaybackState contains the current position val position = 200L val state = PlaybackState.Builder().run { setState(PlaybackState.STATE_PLAYING, position, 1f) @@ -246,7 +247,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test - fun handleSeek() { + fun onSeek() { whenever(mockController.getTransportControls()).thenReturn(mockTransport) viewModel.updateController(mockController) // WHEN user input is dispatched @@ -258,19 +259,73 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test - fun handleProgressChangedUser() { + fun onSeekWithFalse() { whenever(mockController.getTransportControls()).thenReturn(mockTransport) viewModel.updateController(mockController) + // WHEN a false is received during the seek gesture + val pos = 42L + with(viewModel) { + onSeekStarting() + onSeekFalse() + onSeek(pos) + } + fakeExecutor.runAllReady() + // THEN the seek is rejected and the transport never receives seekTo + verify(mockTransport, never()).seekTo(pos) + } + + @Test + fun onSeekProgress() { + val pos = 42L + with(viewModel) { + onSeekStarting() + onSeekProgress(pos) + } + fakeExecutor.runAllReady() + // THEN then elapsed time should be updated + assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(pos) + } + + @Test + fun onSeekProgressWithSeekStarting() { + val pos = 42L + with(viewModel) { + onSeekProgress(pos) + } + fakeExecutor.runAllReady() + // THEN then elapsed time should not be updated + assertThat(viewModel.progress.value!!.elapsedTime).isNull() + } + + @Test + fun onProgressChangedFromUser() { // WHEN user starts dragging the seek bar val pos = 42 - viewModel.seekBarListener.onProgressChanged(SeekBar(context), pos, true) + val bar = SeekBar(context) + with(viewModel.seekBarListener) { + onStartTrackingTouch(bar) + onProgressChanged(bar, pos, true) + } fakeExecutor.runAllReady() - // THEN transport controls should be used - verify(mockTransport).seekTo(pos.toLong()) + // THEN then elapsed time should be updated + assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(pos) + } + + @Test + fun onProgressChangedFromUserWithoutStartTrackingTouch() { + // WHEN user starts dragging the seek bar + val pos = 42 + val bar = SeekBar(context) + with(viewModel.seekBarListener) { + onProgressChanged(bar, pos, true) + } + fakeExecutor.runAllReady() + // THEN then elapsed time should not be updated + assertThat(viewModel.progress.value!!.elapsedTime).isNull() } @Test - fun handleProgressChangedOther() { + fun onProgressChangedNotFromUser() { whenever(mockController.getTransportControls()).thenReturn(mockTransport) viewModel.updateController(mockController) // WHEN user starts dragging the seek bar @@ -282,7 +337,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test - fun handleStartTrackingTouch() { + fun onStartTrackingTouch() { whenever(mockController.getTransportControls()).thenReturn(mockTransport) viewModel.updateController(mockController) // WHEN user starts dragging the seek bar @@ -297,7 +352,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test - fun handleStopTrackingTouch() { + fun onStopTrackingTouch() { whenever(mockController.getTransportControls()).thenReturn(mockTransport) viewModel.updateController(mockController) // WHEN user ends drag @@ -312,6 +367,26 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + fun onStopTrackingTouchAfterProgress() { + whenever(mockController.getTransportControls()).thenReturn(mockTransport) + viewModel.updateController(mockController) + // WHEN user starts dragging the seek bar + val pos = 42 + val progPos = 84 + val bar = SeekBar(context).apply { + progress = pos + } + with(viewModel.seekBarListener) { + onStartTrackingTouch(bar) + onProgressChanged(bar, progPos, true) + onStopTrackingTouch(bar) + } + fakeExecutor.runAllReady() + // THEN then elapsed time should be updated + verify(mockTransport).seekTo(eq(pos.toLong())) + } + + @Test fun queuePollTaskWhenPlaying() { // GIVEN that the track is playing val state = PlaybackState.Builder().run { @@ -369,7 +444,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { } // AND the playback state is playing val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_STOPPED, 200L, 1f) + setState(PlaybackState.STATE_PLAYING, 200L, 1f) build() } whenever(mockController.getPlaybackState()).thenReturn(state) @@ -398,6 +473,90 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Test + fun noQueuePollTaskWhenSeeking() { + // GIVEN listening + viewModel.listening = true + // AND the playback state is playing + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + viewModel.updateController(mockController) + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // WHEN seek starts + viewModel.onSeekStarting() + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // THEN an update task is not queued because we don't want it fighting with the user when + // they are trying to move the thumb. + assertThat(fakeExecutor.numPending()).isEqualTo(0) + } + + @Test + fun queuePollTaskWhenDoneSeekingWithFalse() { + // GIVEN listening + viewModel.listening = true + // AND the playback state is playing + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + viewModel.updateController(mockController) + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // WHEN seek finishes after a false + with(viewModel) { + onSeekStarting() + onSeekFalse() + onSeek(42L) + } + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // THEN an update task is queued because the gesture was ignored and progress was restored. + assertThat(fakeExecutor.numPending()).isEqualTo(1) + } + + @Test + fun noQueuePollTaskWhenDoneSeeking() { + // GIVEN listening + viewModel.listening = true + // AND the playback state is playing + val state = PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } + whenever(mockController.getPlaybackState()).thenReturn(state) + viewModel.updateController(mockController) + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // WHEN seek finishes after a false + with(viewModel) { + onSeekStarting() + onSeek(42L) + } + with(fakeExecutor) { + advanceClockToNext() + runAllReady() + } + // THEN no update task is queued because we are waiting for an updated playback state to be + // returned in response to the seek. + assertThat(fakeExecutor.numPending()).isEqualTo(0) + } + + @Test fun startListeningQueuesPollTask() { // GIVEN not listening viewModel.listening = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java index 05b31c86559b..cbb0711f78f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java @@ -51,6 +51,7 @@ import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.policy.SecurityController; +import com.android.systemui.util.animation.UniqueObjectHostView; import org.junit.Before; import org.junit.Test; @@ -108,12 +109,14 @@ public class QSPanelTest extends SysuiTestCase { mDependency.injectMockDependency(SecurityController.class); mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mContext.addMockSystemService(Context.USER_SERVICE, mock(UserManager.class)); + when(mMediaHost.getHostView()).thenReturn(new UniqueObjectHostView(getContext())); mUiEventLogger = new UiEventLoggerFake(); mTestableLooper.runWithLooper(() -> { mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class); mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher, mQSLogger, mMediaHost, mUiEventLogger); + mQsPanel.onFinishInflate(); // Provides a parent with non-zero size for QSPanel mParentView = new FrameLayout(mContext); mParentView.addView(mQsPanel); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 23099d783586..22f50d0fb591 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -25,17 +25,21 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.Instrumentation; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; import android.graphics.Color; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; @@ -44,6 +48,7 @@ import android.os.BatteryManager; import android.os.Looper; import android.os.RemoteException; import android.os.UserManager; +import android.view.View; import android.view.ViewGroup; import androidx.test.InstrumentationRegistry; @@ -52,13 +57,16 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.IBatteryStats; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; +import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.wakelock.WakeLockFake; @@ -75,6 +83,10 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class KeyguardIndicationControllerTest extends SysuiTestCase { + private static final String ORGANIZATION_NAME = "organization"; + + private String mDisclosureWithOrganization; + @Mock private DevicePolicyManager mDevicePolicyManager; @Mock @@ -82,6 +94,12 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Mock private KeyguardStateController mKeyguardStateController; @Mock + private KeyguardIndicationTextView mDisclosure; + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private LockIcon mLockIcon; + @Mock private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -112,11 +130,17 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mContext.addMockSystemService(UserManager.class, mUserManager); mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class)); mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class)); + mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name, + ORGANIZATION_NAME); when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true); when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true); + + when(mIndicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure)) + .thenReturn(mDisclosure); when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView); + when(mDisclosure.getAlpha()).thenReturn(1f); mWakeLock = new WakeLockFake(); mWakeLockBuilder = new WakeLockFake.Builder(mContext); @@ -130,10 +154,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController = new KeyguardIndicationController(mContext, mWakeLockBuilder, mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor, - mDockManager, mIBatteryStats); + mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats); mController.setIndicationArea(mIndicationArea); mController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); clearInvocations(mIBatteryStats); + verify(mDisclosure).getAlpha(); } @Test @@ -215,6 +240,106 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } @Test + public void disclosure_unmanaged() { + when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); + createController(); + + verify(mDisclosure).setVisibility(View.GONE); + verifyNoMoreInteractions(mDisclosure); + } + + @Test + public void disclosure_managedNoOwnerName() { + when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); + when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null); + createController(); + + verify(mDisclosure).setVisibility(View.VISIBLE); + verify(mDisclosure).switchIndication(R.string.do_disclosure_generic); + verifyNoMoreInteractions(mDisclosure); + } + + @Test + public void disclosure_hiddenWhenDozing() { + when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); + when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null); + createController(); + + mController.setVisible(true); + mController.onDozeAmountChanged(1, 1); + mController.setDozing(true); + + verify(mDisclosure).setVisibility(View.VISIBLE); + verify(mDisclosure).setAlpha(0f); + verify(mDisclosure).switchIndication(R.string.do_disclosure_generic); + verifyNoMoreInteractions(mDisclosure); + } + + @Test + public void disclosure_visibleWhenDozing() { + when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); + when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null); + createController(); + + mController.setVisible(true); + mController.onDozeAmountChanged(0, 0); + mController.setDozing(false); + + verify(mDisclosure).setVisibility(View.VISIBLE); + verify(mDisclosure).setAlpha(1f); + verify(mDisclosure).switchIndication(R.string.do_disclosure_generic); + verifyNoMoreInteractions(mDisclosure); + } + + @Test + public void disclosure_managedOwnerName() { + when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); + when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME); + createController(); + + verify(mDisclosure).setVisibility(View.VISIBLE); + verify(mDisclosure).switchIndication(mDisclosureWithOrganization); + verifyNoMoreInteractions(mDisclosure); + } + + @Test + public void disclosure_updateOnTheFly() { + ArgumentCaptor<BroadcastReceiver> receiver = ArgumentCaptor.forClass( + BroadcastReceiver.class); + doNothing().when(mBroadcastDispatcher).registerReceiver(receiver.capture(), any()); + + when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); + createController(); + + final KeyguardUpdateMonitorCallback monitor = mController.getKeyguardCallback(); + reset(mDisclosure); + + when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); + when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null); + receiver.getValue().onReceive(mContext, new Intent()); + + verify(mDisclosure).setVisibility(View.VISIBLE); + verify(mDisclosure).switchIndication(R.string.do_disclosure_generic); + verifyNoMoreInteractions(mDisclosure); + reset(mDisclosure); + + when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); + when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME); + receiver.getValue().onReceive(mContext, new Intent()); + + verify(mDisclosure).setVisibility(View.VISIBLE); + verify(mDisclosure).switchIndication(mDisclosureWithOrganization); + verifyNoMoreInteractions(mDisclosure); + reset(mDisclosure); + + when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); + receiver.getValue().onReceive(mContext, new Intent()); + + verify(mDisclosure).setVisibility(View.GONE); + verifyNoMoreInteractions(mDisclosure); + } + + @Test public void transientIndication_holdsWakeLock_whenDozing() { createController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index ca9cc299b36d..363fe95aae18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -80,6 +80,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -131,6 +132,7 @@ public class NotifCollectionTest extends SysuiTestCase { private InOrder mListenerInOrder; private NoManSimulator mNoMan; + private FakeSystemClock mClock = new FakeSystemClock(); @Before public void setUp() { @@ -146,10 +148,11 @@ public class NotifCollectionTest extends SysuiTestCase { mCollection = new NotifCollection( mStatusBarService, - mock(DumpManager.class), + mClock, mFeatureFlags, mLogger, - mEulogizer); + mEulogizer, + mock(DumpManager.class)); mCollection.attach(mGroupCoalescer); mCollection.addCollectionListener(mCollectionListener); mCollection.setBuildListener(mBuildListener); @@ -161,6 +164,8 @@ public class NotifCollectionTest extends SysuiTestCase { mNoMan = new NoManSimulator(); mNoMan.addListener(mNotifHandler); + + mNotifHandler.onNotificationsInitialized(); } @Test @@ -1268,6 +1273,42 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mInterceptor3, never()).shouldInterceptDismissal(clearable); } + @Test(expected = IllegalStateException.class) + public void testClearNotificationThrowsIfMissing() { + // GIVEN that enough time has passed that we're beyond the forgiveness window + mClock.advanceTime(5001); + + // WHEN we get a remove event for a notification we don't know about + final NotificationEntry container = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE) + .setId(47) + .build(); + mNotifHandler.onNotificationRemoved( + container.getSbn(), + new RankingMap(new Ranking[]{ container.getRanking() })); + + // THEN an exception is thrown + } + + @Test + public void testClearNotificationDoesntThrowIfInForgivenessWindow() { + // GIVEN that some time has passed but we're still within the initialization forgiveness + // window + mClock.advanceTime(4999); + + // WHEN we get a remove event for a notification we don't know about + final NotificationEntry container = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE) + .setId(47) + .build(); + mNotifHandler.onNotificationRemoved( + container.getSbn(), + new RankingMap(new Ranking[]{ container.getRanking() })); + + // THEN no exception is thrown, but no event is fired + verify(mCollectionListener, never()).onEntryRemoved(any(NotificationEntry.class), anyInt()); + } + private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) { return new NotificationEntryBuilder() .setPkg(pkg) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt new file mode 100644 index 000000000000..b63e66f1ebe3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt @@ -0,0 +1,209 @@ +/* + * 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.statusbar.notification.icon; + +import android.app.ActivityManager; +import android.app.Notification; +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.app.Person +import android.content.pm.LauncherApps +import android.content.pm.ShortcutInfo +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.os.SystemClock +import android.os.UserHandle +import android.testing.AndroidTestingRunner; +import androidx.test.InstrumentationRegistry +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +import org.junit.runner.RunWith; +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.anyInt +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class IconManagerTest: SysuiTestCase() { + companion object { + private const val TEST_PACKAGE_NAME = "test"; + private const val TEST_UID = 0; + } + + + private var id = 0 + private val context = InstrumentationRegistry.getTargetContext(); + @Mock private lateinit var shortcut: ShortcutInfo + @Mock private lateinit var shortcutIc: Icon + @Mock private lateinit var messageIc: Icon + @Mock private lateinit var largeIc: Icon + @Mock private lateinit var smallIc: Icon + @Mock private lateinit var drawable: Drawable + @Mock private lateinit var row: ExpandableNotificationRow + + @Mock private lateinit var notifCollection: CommonNotifCollection + @Mock private lateinit var launcherApps: LauncherApps + + private val iconBuilder = IconBuilder(context) + + private lateinit var iconManager: IconManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(shortcutIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable) + `when`(messageIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable) + `when`(largeIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable) + `when`(smallIc.loadDrawableAsUser(any(), anyInt())).thenReturn(drawable) + + `when`(shortcut.icon).thenReturn(shortcutIc) + `when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc) + + iconManager = IconManager(notifCollection, launcherApps, iconBuilder) + } + + @Test + fun testCreateIcons_importantConversation_shortcutIcon() { + val entry = notificationEntry(true, true, true) + entry?.channel?.isImportantConversation = true + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, shortcutIc) + } + + @Test + fun testCreateIcons_importantConversation_messageIcon() { + val entry = notificationEntry(false, true, true) + entry?.channel?.isImportantConversation = true + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, messageIc) + } + + @Test + fun testCreateIcons_importantConversation_largeIcon() { + val entry = notificationEntry(false, false, true) + entry?.channel?.isImportantConversation = true + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, largeIc) + } + + @Test + fun testCreateIcons_importantConversation_smallIcon() { + val entry = notificationEntry(false, false, false) + entry?.channel?.isImportantConversation = true + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, smallIc) + } + + @Test + fun testCreateIcons_notImportantConversation() { + val entry = notificationEntry(true, true, true) + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, smallIc) + } + + @Test + fun testCreateIcons_sensitiveImportantConversation() { + val entry = notificationEntry(true, false, false) + entry?.setSensitive(true, true); + entry?.channel?.isImportantConversation = true + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.statusBarIcon?.sourceIcon, shortcutIc) + assertEquals(entry?.icons?.shelfIcon?.sourceIcon, smallIc) + assertEquals(entry?.icons?.aodIcon?.sourceIcon, smallIc) + } + + @Test + fun testUpdateIcons_sensitivityChange() { + val entry = notificationEntry(true, false, false) + entry?.channel?.isImportantConversation = true + entry?.setSensitive(true, true); + entry?.let { + iconManager.createIcons(it) + } + assertEquals(entry?.icons?.aodIcon?.sourceIcon, smallIc) + entry?.setSensitive(false, false); + entry?.let { + iconManager.updateIcons(it) + } + assertEquals(entry?.icons?.shelfIcon?.sourceIcon, shortcutIc) + } + + private fun notificationEntry( + hasShortcut: Boolean, + hasMessage: Boolean, + hasLargeIcon: Boolean + ): NotificationEntry? { + val n = Notification.Builder(mContext, "id") + .setSmallIcon(smallIc) + .setContentTitle("Title") + .setContentText("Text") + + if (hasMessage) { + n.style = Notification.MessagingStyle("") + .addMessage(Notification.MessagingStyle.Message( + "", + SystemClock.currentThreadTimeMillis(), + Person.Builder().setIcon(messageIc).build() + )) + } + + if (hasLargeIcon) { + n.setLargeIcon(largeIc) + } + + val builder = NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setId(id++) + .setNotification(n.build()) + .setChannel(NotificationChannel("id", "", IMPORTANCE_DEFAULT)) + .setUser(UserHandle(ActivityManager.getCurrentUser())) + + if (hasShortcut) { + builder.setShortcutInfo(shortcut) + } + + val entry = builder.build() + entry.row = row + entry.setSensitive(false, true); + return entry + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index a3a46f67ee40..06bad80d6f87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -20,6 +20,8 @@ import static com.android.systemui.statusbar.notification.stack.NotificationSect import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -42,6 +44,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -163,28 +166,61 @@ public class NotificationLoggerTest extends SysuiTestCase { mUiBgExecutor.runAllReady(); Mockito.reset(mBarService); - mLogger.stopNotificationLogging(); + setStateAsleep(); + mLogger.onDozingChanged(false); // Wake to lockscreen + mLogger.onDozingChanged(true); // And go back to sleep, turning off logging mUiBgExecutor.runAllReady(); // The visibility objects are recycled by NotificationLogger, so we can't use specific // matchers here. verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any()); } + private void setStateAsleep() { + mLogger.onPanelExpandedChanged(true); + mLogger.onDozingChanged(true); + mLogger.onStateChanged(StatusBarState.KEYGUARD); + } + + private void setStateAwake() { + mLogger.onPanelExpandedChanged(false); + mLogger.onDozingChanged(false); + mLogger.onStateChanged(StatusBarState.SHADE); + } + + @Test + public void testLogPanelShownOnWake() { + when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry)); + setStateAsleep(); + mLogger.onDozingChanged(false); // Wake to lockscreen + assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); + assertTrue(mNotificationPanelLoggerFake.get(0).isLockscreen); + assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); + Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0]; + assertEquals(TEST_PACKAGE_NAME, n.packageName); + assertEquals(TEST_UID, n.uid); + assertEquals(1, n.instanceId); + assertFalse(n.isGroupSummary); + assertEquals(1 + BUCKET_ALERTING, n.section); + } + @Test - public void testLogPanelShownOnLoggingStart() { + public void testLogPanelShownOnShadePull() { when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry)); - mLogger.startNotificationLogging(); + setStateAwake(); + // Now expand panel + mLogger.onPanelExpandedChanged(true); assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); - assertEquals(false, mNotificationPanelLoggerFake.get(0).isLockscreen); + assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen); assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0]; assertEquals(TEST_PACKAGE_NAME, n.packageName); assertEquals(TEST_UID, n.uid); assertEquals(1, n.instanceId); - assertEquals(false, n.isGroupSummary); + assertFalse(n.isGroupSummary); assertEquals(1 + BUCKET_ALERTING, n.section); } + @Test public void testLogPanelShownHandlesNullInstanceIds() { // Construct a NotificationEntry like mEntry, but with a null instance id. @@ -198,7 +234,8 @@ public class NotificationLoggerTest extends SysuiTestCase { entry.setRow(mRow); when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(entry)); - mLogger.startNotificationLogging(); + setStateAsleep(); + mLogger.onDozingChanged(false); // Wake to lockscreen assertEquals(1, mNotificationPanelLoggerFake.getCalls().size()); assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length); Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0]; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index c55391a387d8..243503d1d8a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -403,11 +403,11 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { enablePeopleFiltering(); setupMockStack( - PERSON.headsUp(), // personHeaderTarget = 0 - INCOMING_HEADER, // currentIncomingHeaderIdx = 1 - ALERTING.headsUp(), // alertingHeaderTarget = 1 - PEOPLE_HEADER, // currentPeopleHeaderIdx = 3 - PERSON // + PERSON.headsUp(), + INCOMING_HEADER, + ALERTING.headsUp(), + PEOPLE_HEADER, + PERSON ); mSectionsManager.updateSectionBoundaries(); @@ -520,6 +520,70 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { ChildType.GENTLE); } + @Test + public void testRemoveIncomingHeader() { + enablePeopleFiltering(); + enableMediaControls(); + + setupMockStack( + MEDIA_CONTROLS, + INCOMING_HEADER, + PERSON, + ALERTING, + PEOPLE_HEADER, + ALERTING_HEADER, + ALERTING, + ALERTING, + GENTLE_HEADER, + GENTLE, + GENTLE + ); + + mSectionsManager.updateSectionBoundaries(); + + verifyMockStack( + ChildType.MEDIA_CONTROLS, + ChildType.PEOPLE_HEADER, + ChildType.PERSON, + ChildType.ALERTING_HEADER, + ChildType.ALERTING, + ChildType.ALERTING, + ChildType.ALERTING, + ChildType.GENTLE_HEADER, + ChildType.GENTLE, + ChildType.GENTLE + ); + } + + @Test + public void testExpandIncomingSection() { + enablePeopleFiltering(); + + setupMockStack( + INCOMING_HEADER, + PERSON, + ALERTING, + PEOPLE_HEADER, + ALERTING, + PERSON, + ALERTING_HEADER, + ALERTING + ); + + mSectionsManager.updateSectionBoundaries(); + + verifyMockStack( + ChildType.INCOMING_HEADER, + ChildType.HEADS_UP, + ChildType.HEADS_UP, + ChildType.HEADS_UP, + ChildType.PEOPLE_HEADER, + ChildType.PERSON, + ChildType.ALERTING_HEADER, + ChildType.ALERTING + ); + } + private void enablePeopleFiltering() { when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true); } @@ -657,7 +721,13 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { final List<View> children = new ArrayList<>(); when(mNssl.getChildCount()).thenAnswer(invocation -> children.size()); when(mNssl.getChildAt(anyInt())) - .thenAnswer(invocation -> children.get(invocation.getArgument(0))); + .thenAnswer(invocation -> { + Integer index = invocation.getArgument(0); + if (index == null || index < 0 || index >= children.size()) { + return null; + } + return children.get(index); + }); when(mNssl.indexOfChild(any())) .thenAnswer(invocation -> children.indexOf(invocation.getArgument(0))); doAnswer(invocation -> { diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java index fd6f171487a9..f14def6a3a02 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java @@ -37,8 +37,8 @@ public final class TetheringConstants { private TetheringConstants() { } /** - * Extra used for communicating with the TetherService. Includes the type of tethering to - * enable if any. + * Extra used for communicating with the TetherService and TetherProvisioningActivity. + * Includes the type of tethering to enable if any. */ public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; /** @@ -56,8 +56,38 @@ public final class TetheringConstants { */ public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; /** - * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} - * which will receive provisioning results. Can be left empty. + * Extra used for communicating with the TetherService and TetherProvisioningActivity. + * Contains the {@link ResultReceiver} which will receive provisioning results. + * Can not be empty. */ public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; + + /** + * Extra used for communicating with the TetherService and TetherProvisioningActivity. + * Contains the subId of current active cellular upstream. + * @hide + */ + public static final String EXTRA_TETHER_SUBID = "android.net.extra.TETHER_SUBID"; + + /** + * Extra used for telling TetherProvisioningActivity the entitlement package name and class + * name to start UI entitlement check. + * @hide + */ + public static final String EXTRA_TETHER_UI_PROVISIONING_APP_NAME = + "android.net.extra.TETHER_UI_PROVISIONING_APP_NAME"; + + /** + * Extra used for telling TetherService the intent action to start silent entitlement check. + * @hide + */ + public static final String EXTRA_TETHER_SILENT_PROVISIONING_ACTION = + "android.net.extra.TETHER_SILENT_PROVISIONING_ACTION"; + + /** + * Extra used for TetherService to receive the response of provisioning check. + * @hide + */ + public static final String EXTRA_TETHER_PROVISIONING_RESPONSE = + "android.net.extra.TETHER_PROVISIONING_RESPONSE"; } diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt index e90a2ccaa2a3..8f072e4cd217 100644 --- a/packages/Tethering/jarjar-rules.txt +++ b/packages/Tethering/jarjar-rules.txt @@ -15,3 +15,6 @@ rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.t rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1 rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1 + +# Classes from net-utils-framework-common +rule com.android.net.module.util.** com.android.networkstack.tethering.util.@1
\ No newline at end of file diff --git a/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index fc27b6add052..20f30ea7a460 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -25,6 +25,8 @@ import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStats.UID_TETHERING; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; +import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; + import android.app.usage.NetworkStatsManager; import android.net.INetd; import android.net.MacAddress; @@ -36,6 +38,7 @@ import android.net.ip.IpServer; import android.net.netstats.provider.NetworkStatsProvider; import android.net.util.SharedLog; import android.net.util.TetheringUtils.ForwardedStats; +import android.os.ConditionVariable; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -47,11 +50,13 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; import java.net.Inet6Address; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.Map; import java.util.Objects; /** @@ -65,8 +70,7 @@ import java.util.Objects; */ public class BpfCoordinator { private static final String TAG = BpfCoordinator.class.getSimpleName(); - @VisibleForTesting - static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; // TODO: Make it customizable. + private static final int DUMP_TIMEOUT_MS = 10_000; @VisibleForTesting enum StatsType { @@ -85,6 +89,13 @@ public class BpfCoordinator { @Nullable private final BpfTetherStatsProvider mStatsProvider; + // True if BPF offload is supported, false otherwise. The BPF offload could be disabled by + // a runtime resource overlay package or device configuration. This flag is only initialized + // in the constructor because it is hard to unwind all existing change once device + // configuration is changed. Especially the forwarding rules. Keep the same setting + // to make it simpler. See also TetheringConfiguration. + private final boolean mIsBpfEnabled; + // Tracks whether BPF tethering is started or not. This is set by tethering before it // starts the first IpServer and is cleared by tethering shortly before the last IpServer // is stopped. Note that rule updates (especially deletions, but sometimes additions as @@ -142,22 +153,34 @@ public class BpfCoordinator { }; @VisibleForTesting - public static class Dependencies { - int getPerformPollInterval() { - // TODO: Consider make this configurable. - return DEFAULT_PERFORM_POLL_INTERVAL_MS; - } + public abstract static class Dependencies { + /** Get handler. */ + @NonNull public abstract Handler getHandler(); + + /** Get netd. */ + @NonNull public abstract INetd getNetd(); + + /** Get network stats manager. */ + @NonNull public abstract NetworkStatsManager getNetworkStatsManager(); + + /** Get shared log. */ + @NonNull public abstract SharedLog getSharedLog(); + + /** Get tethering configuration. */ + @Nullable public abstract TetheringConfiguration getTetherConfig(); } @VisibleForTesting - public BpfCoordinator(@NonNull Handler handler, @NonNull INetd netd, - @NonNull NetworkStatsManager nsm, @NonNull SharedLog log, @NonNull Dependencies deps) { - mHandler = handler; - mNetd = netd; - mLog = log.forSubComponent(TAG); + public BpfCoordinator(@NonNull Dependencies deps) { + mDeps = deps; + mHandler = mDeps.getHandler(); + mNetd = mDeps.getNetd(); + mLog = mDeps.getSharedLog().forSubComponent(TAG); + mIsBpfEnabled = isBpfEnabled(); BpfTetherStatsProvider provider = new BpfTetherStatsProvider(); try { - nsm.registerNetworkStatsProvider(getClass().getSimpleName(), provider); + mDeps.getNetworkStatsManager().registerNetworkStatsProvider( + getClass().getSimpleName(), provider); } catch (RuntimeException e) { // TODO: Perhaps not allow to use BPF offload because the reregistration failure // implied that no data limit could be applies on a metered upstream if any. @@ -165,7 +188,6 @@ public class BpfCoordinator { provider = null; } mStatsProvider = provider; - mDeps = deps; } /** @@ -177,6 +199,11 @@ public class BpfCoordinator { public void startPolling() { if (mPollingStarted) return; + if (!mIsBpfEnabled) { + mLog.i("Offload disabled"); + return; + } + mPollingStarted = true; maybeSchedulePollingStats(); @@ -211,6 +238,8 @@ public class BpfCoordinator { */ public void tetherOffloadRuleAdd( @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { + if (!mIsBpfEnabled) return; + try { // TODO: Perhaps avoid to add a duplicate rule. mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel()); @@ -250,6 +279,8 @@ public class BpfCoordinator { */ public void tetherOffloadRuleRemove( @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { + if (!mIsBpfEnabled) return; + try { // TODO: Perhaps avoid to remove a non-existent rule. mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel()); @@ -293,6 +324,8 @@ public class BpfCoordinator { * Note that this can be only called on handler thread. */ public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) { + if (!mIsBpfEnabled) return; + final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get( ipServer); if (rules == null) return; @@ -308,6 +341,8 @@ public class BpfCoordinator { * Note that this can be only called on handler thread. */ public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) { + if (!mIsBpfEnabled) return; + final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get( ipServer); if (rules == null) return; @@ -330,6 +365,8 @@ public class BpfCoordinator { * Note that this can be only called on handler thread. */ public void addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface) { + if (!mIsBpfEnabled) return; + if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return; // The same interface index to name mapping may be added by different IpServer objects or @@ -344,6 +381,77 @@ public class BpfCoordinator { } } + /** + * Dump information. + * Block the function until all the data are dumped on the handler thread or timed-out. The + * reason is that dumpsys invokes this function on the thread of caller and the data may only + * be allowed to be accessed on the handler thread. + */ + public void dump(@NonNull IndentingPrintWriter pw) { + final ConditionVariable dumpDone = new ConditionVariable(); + mHandler.post(() -> { + pw.println("mIsBpfEnabled: " + mIsBpfEnabled); + pw.println("Polling " + (mPollingStarted ? "started" : "not started")); + pw.println("Stats provider " + (mStatsProvider != null + ? "registered" : "not registered")); + pw.println("Upstream quota: " + mInterfaceQuotas.toString()); + pw.println("Polling interval: " + getPollingInterval() + " ms"); + + pw.println("Forwarding stats:"); + pw.increaseIndent(); + if (mStats.size() == 0) { + pw.println("<empty>"); + } else { + dumpStats(pw); + } + pw.decreaseIndent(); + + pw.println("Forwarding rules:"); + pw.increaseIndent(); + if (mIpv6ForwardingRules.size() == 0) { + pw.println("<empty>"); + } else { + dumpIpv6ForwardingRules(pw); + } + pw.decreaseIndent(); + + dumpDone.open(); + }); + if (!dumpDone.block(DUMP_TIMEOUT_MS)) { + pw.println("... dump timed-out after " + DUMP_TIMEOUT_MS + "ms"); + } + } + + private void dumpStats(@NonNull IndentingPrintWriter pw) { + for (int i = 0; i < mStats.size(); i++) { + final int upstreamIfindex = mStats.keyAt(i); + final ForwardedStats stats = mStats.get(upstreamIfindex); + pw.println(String.format("%d(%s) - %s", upstreamIfindex, mInterfaceNames.get( + upstreamIfindex), stats.toString())); + } + } + + private void dumpIpv6ForwardingRules(@NonNull IndentingPrintWriter pw) { + for (Map.Entry<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> entry : + mIpv6ForwardingRules.entrySet()) { + IpServer ipServer = entry.getKey(); + // The rule downstream interface index is paired with the interface name from + // IpServer#interfaceName. See #startIPv6, #updateIpv6ForwardingRules in IpServer. + final String downstreamIface = ipServer.interfaceName(); + pw.println("[" + downstreamIface + "]: iif(iface) oif(iface) v6addr srcmac dstmac"); + + pw.increaseIndent(); + LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = entry.getValue(); + for (Ipv6ForwardingRule rule : rules.values()) { + final int upstreamIfindex = rule.upstreamIfindex; + pw.println(String.format("%d(%s) %d(%s) %s %s %s", upstreamIfindex, + mInterfaceNames.get(upstreamIfindex), rule.downstreamIfindex, + downstreamIface, rule.address, rule.srcMac, rule.dstMac)); + } + pw.decreaseIndent(); + } + } + /** IPv6 forwarding rule class. */ public static class Ipv6ForwardingRule { public final int upstreamIfindex; @@ -474,6 +582,11 @@ public class BpfCoordinator { } } + private boolean isBpfEnabled() { + final TetheringConfiguration config = mDeps.getTetherConfig(); + return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */; + } + private int getInterfaceIndexFromRules(@NonNull String ifName) { for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules .values()) { @@ -625,6 +738,17 @@ public class BpfCoordinator { updateQuotaAndStatsFromSnapshot(tetherStatsList); } + @VisibleForTesting + int getPollingInterval() { + // The valid range of interval is DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long. + // Ignore the config value is less than the minimum polling interval. Note that the + // minimum interval definition is invoked as OffloadController#isPollingStatsNeeded does. + // TODO: Perhaps define a minimum polling interval constant. + final TetheringConfiguration config = mDeps.getTetherConfig(); + final int configInterval = (config != null) ? config.getOffloadPollInterval() : 0; + return Math.max(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, configInterval); + } + private void maybeSchedulePollingStats() { if (!mPollingStarted) return; @@ -632,6 +756,23 @@ public class BpfCoordinator { mHandler.removeCallbacks(mScheduledPollingTask); } - mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval()); + mHandler.postDelayed(mScheduledPollingTask, getPollingInterval()); + } + + // Return forwarding rule map. This is used for testing only. + // Note that this can be only called on handler thread. + @NonNull + @VisibleForTesting + final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> + getForwardingRulesForTesting() { + return mIpv6ForwardingRules; + } + + // Return upstream interface name map. This is used for testing only. + // Note that this can be only called on handler thread. + @NonNull + @VisibleForTesting + final SparseArray<String> getInterfaceNamesForTesting() { + return mInterfaceNames; } } diff --git a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java index 3c6e8d88ed13..9dace709d734 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java @@ -19,6 +19,10 @@ package com.android.networkstack.tethering; import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE; import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK; import static android.net.TetheringConstants.EXTRA_RUN_PROVISION; +import static android.net.TetheringConstants.EXTRA_TETHER_PROVISIONING_RESPONSE; +import static android.net.TetheringConstants.EXTRA_TETHER_SILENT_PROVISIONING_ACTION; +import static android.net.TetheringConstants.EXTRA_TETHER_SUBID; +import static android.net.TetheringConstants.EXTRA_TETHER_UI_PROVISIONING_APP_NAME; import static android.net.TetheringManager.TETHERING_BLUETOOTH; import static android.net.TetheringManager.TETHERING_ETHERNET; import static android.net.TetheringManager.TETHERING_INVALID; @@ -69,7 +73,6 @@ public class EntitlementManager { protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; private static final String ACTION_PROVISIONING_ALARM = "com.android.networkstack.tethering.PROVISIONING_RECHECK_ALARM"; - private static final String EXTRA_SUBID = "subId"; private final ComponentName mSilentProvisioningService; private static final int MS_PER_HOUR = 60 * 60 * 1000; @@ -197,9 +200,9 @@ public class EntitlementManager { // till upstream change to cellular. if (mUsingCellularAsUpstream) { if (showProvisioningUi) { - runUiTetherProvisioning(downstreamType, config.activeDataSubId); + runUiTetherProvisioning(downstreamType, config); } else { - runSilentTetherProvisioning(downstreamType, config.activeDataSubId); + runSilentTetherProvisioning(downstreamType, config); } mNeedReRunProvisioningUi = false; } else { @@ -262,9 +265,9 @@ public class EntitlementManager { if (mCurrentEntitlementResults.indexOfKey(downstream) < 0) { if (mNeedReRunProvisioningUi) { mNeedReRunProvisioningUi = false; - runUiTetherProvisioning(downstream, config.activeDataSubId); + runUiTetherProvisioning(downstream, config); } else { - runSilentTetherProvisioning(downstream, config.activeDataSubId); + runSilentTetherProvisioning(downstream, config); } } } @@ -361,7 +364,7 @@ public class EntitlementManager { * @param subId default data subscription ID. */ @VisibleForTesting - protected void runSilentTetherProvisioning(int type, int subId) { + protected Intent runSilentTetherProvisioning(int type, final TetheringConfiguration config) { if (DBG) mLog.i("runSilentTetherProvisioning: " + type); // For silent provisioning, settings would stop tethering when entitlement fail. ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null); @@ -369,17 +372,20 @@ public class EntitlementManager { Intent intent = new Intent(); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_RUN_PROVISION, true); + intent.putExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION, config.provisioningAppNoUi); + intent.putExtra(EXTRA_TETHER_PROVISIONING_RESPONSE, config.provisioningResponse); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); - intent.putExtra(EXTRA_SUBID, subId); + intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId); intent.setComponent(mSilentProvisioningService); // Only admin user can change tethering and SilentTetherProvisioning don't need to // show UI, it is fine to always start setting's background service as system user. mContext.startService(intent); + return intent; } - private void runUiTetherProvisioning(int type, int subId) { + private void runUiTetherProvisioning(int type, final TetheringConfiguration config) { ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null); - runUiTetherProvisioning(type, subId, receiver); + runUiTetherProvisioning(type, config, receiver); } /** @@ -389,17 +395,20 @@ public class EntitlementManager { * @param receiver to receive entitlement check result. */ @VisibleForTesting - protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { + protected Intent runUiTetherProvisioning(int type, final TetheringConfiguration config, + ResultReceiver receiver) { if (DBG) mLog.i("runUiTetherProvisioning: " + type); Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); + intent.putExtra(EXTRA_TETHER_UI_PROVISIONING_APP_NAME, config.provisioningApp); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); - intent.putExtra(EXTRA_SUBID, subId); + intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // Only launch entitlement UI for system user. Entitlement UI should not appear for other // user because only admin user is allowed to change tethering. mContext.startActivity(intent); + return intent; } // Not needed to check if this don't run on the handler thread because it's private. @@ -631,7 +640,7 @@ public class EntitlementManager { receiver.send(cacheValue, null); } else { ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver); - runUiTetherProvisioning(downstream, config.activeDataSubId, proxy); + runUiTetherProvisioning(downstream, config, proxy); } } } diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java index df6745855067..c72ac52740d7 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -18,6 +18,7 @@ package com.android.networkstack.tethering; import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.NETWORK_STACK; +import static android.content.pm.PackageManager.GET_ACTIVITIES; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.usb.UsbManager.USB_CONFIGURED; import static android.hardware.usb.UsbManager.USB_CONNECTED; @@ -62,6 +63,7 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE; +import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothProfile; @@ -70,6 +72,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; import android.net.EthernetManager; @@ -109,7 +112,6 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceSpecificException; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -285,8 +287,6 @@ public class Tethering { mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new LinkedHashSet<>(); - mBpfCoordinator = mDeps.getBpfCoordinator( - mHandler, mNetd, mLog, new BpfCoordinator.Dependencies()); IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); @@ -324,6 +324,36 @@ public class Tethering { // Load tethering configuration. updateConfiguration(); + // Must be initialized after tethering configuration is loaded because BpfCoordinator + // constructor needs to use the configuration. + mBpfCoordinator = mDeps.getBpfCoordinator( + new BpfCoordinator.Dependencies() { + @NonNull + public Handler getHandler() { + return mHandler; + } + + @NonNull + public INetd getNetd() { + return mNetd; + } + + @NonNull + public NetworkStatsManager getNetworkStatsManager() { + return mContext.getSystemService(NetworkStatsManager.class); + } + + @NonNull + public SharedLog getSharedLog() { + return mLog; + } + + @Nullable + public TetheringConfiguration getTetherConfig() { + return mConfig; + } + }); + startStateMachineUpdaters(); } @@ -782,11 +812,30 @@ public class Tethering { } } + private boolean isProvisioningNeededButUnavailable() { + return isTetherProvisioningRequired() && !doesEntitlementPackageExist(); + } + boolean isTetherProvisioningRequired() { final TetheringConfiguration cfg = mConfig; return mEntitlementMgr.isTetherProvisioningRequired(cfg); } + private boolean doesEntitlementPackageExist() { + // provisioningApp must contain package and class name. + if (mConfig.provisioningApp.length != 2) { + return false; + } + + final PackageManager pm = mContext.getPackageManager(); + try { + pm.getPackageInfo(mConfig.provisioningApp[0], GET_ACTIVITIES); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + return true; + } + // TODO: Figure out how to update for local hotspot mode interfaces. private void sendTetherStateChangedBroadcast() { if (!isTetheringSupported()) return; @@ -2145,14 +2194,14 @@ public class Tethering { // gservices could set the secure setting to 1 though to enable it on a build where it // had previously been turned off. boolean isTetheringSupported() { - final int defaultVal = - SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1; + final int defaultVal = mDeps.isTetheringDenied() ? 0 : 1; final boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.TETHER_SUPPORTED, defaultVal) != 0; final boolean tetherEnabledInSettings = tetherSupported && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING); - return tetherEnabledInSettings && hasTetherableConfiguration(); + return tetherEnabledInSettings && hasTetherableConfiguration() + && !isProvisioningNeededButUnavailable(); } void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { @@ -2216,6 +2265,11 @@ public class Tethering { mOffloadController.dump(pw); pw.decreaseIndent(); + pw.println("BPF offload:"); + pw.increaseIndent(); + mBpfCoordinator.dump(pw); + pw.decreaseIndent(); + pw.println("Private address coordinator:"); pw.increaseIndent(); mPrivateAddressCoordinator.dump(pw); @@ -2350,7 +2404,7 @@ public class Tethering { final TetherState tetherState = new TetherState( new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator, makeControlCallback(), mConfig.enableLegacyDhcpServer, - mConfig.enableBpfOffload, mPrivateAddressCoordinator, + mConfig.isBpfOffloadEnabled(), mPrivateAddressCoordinator, mDeps.getIpServerDependencies())); mTetherStates.put(iface, tetherState); tetherState.ipServer.start(); diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java index 48a600dfe6e1..18b2b7804fb0 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java @@ -101,16 +101,17 @@ public class TetheringConfiguration { public final String[] legacyDhcpRanges; public final String[] defaultIPv4DNS; public final boolean enableLegacyDhcpServer; - // TODO: Add to TetheringConfigurationParcel if required. - public final boolean enableBpfOffload; public final String[] provisioningApp; public final String provisioningAppNoUi; public final int provisioningCheckPeriod; + public final String provisioningResponse; public final int activeDataSubId; private final int mOffloadPollInterval; + // TODO: Add to TetheringConfigurationParcel if required. + private final boolean mEnableBpfOffload; public TetheringConfiguration(Context ctx, SharedLog log, int id) { final SharedLog configLog = log.forSubComponent("config"); @@ -137,14 +138,17 @@ public class TetheringConfiguration { legacyDhcpRanges = getLegacyDhcpRanges(res); defaultIPv4DNS = copy(DEFAULT_IPV4_DNS); - enableBpfOffload = getEnableBpfOffload(res); + mEnableBpfOffload = getEnableBpfOffload(res); enableLegacyDhcpServer = getEnableLegacyDhcpServer(res); provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app); - provisioningAppNoUi = getProvisioningAppNoUi(res); + provisioningAppNoUi = getResourceString(res, + R.string.config_mobile_hotspot_provision_app_no_ui); provisioningCheckPeriod = getResourceInteger(res, R.integer.config_mobile_hotspot_provision_check_period, 0 /* No periodic re-check */); + provisioningResponse = getResourceString(res, + R.string.config_mobile_hotspot_provision_response); mOffloadPollInterval = getResourceInteger(res, R.integer.config_tether_offload_poll_interval, @@ -218,7 +222,7 @@ public class TetheringConfiguration { pw.println(provisioningAppNoUi); pw.print("enableBpfOffload: "); - pw.println(enableBpfOffload); + pw.println(mEnableBpfOffload); pw.print("enableLegacyDhcpServer: "); pw.println(enableLegacyDhcpServer); @@ -240,7 +244,7 @@ public class TetheringConfiguration { toIntArray(preferredUpstreamIfaceTypes))); sj.add(String.format("provisioningApp:%s", makeString(provisioningApp))); sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi)); - sj.add(String.format("enableBpfOffload:%s", enableBpfOffload)); + sj.add(String.format("enableBpfOffload:%s", mEnableBpfOffload)); sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer)); return String.format("TetheringConfiguration{%s}", sj.toString()); } @@ -279,6 +283,10 @@ public class TetheringConfiguration { return mOffloadPollInterval; } + public boolean isBpfOffloadEnabled() { + return mEnableBpfOffload; + } + private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) { final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types); final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length); @@ -337,9 +345,9 @@ public class TetheringConfiguration { return copy(LEGACY_DHCP_DEFAULT_RANGE); } - private static String getProvisioningAppNoUi(Resources res) { + private static String getResourceString(Resources res, final int resId) { try { - return res.getString(R.string.config_mobile_hotspot_provision_app_no_ui); + return res.getString(resId); } catch (Resources.NotFoundException e) { return ""; } diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java index d637c8646b4a..131a5fbf2abe 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java @@ -26,6 +26,8 @@ import android.net.util.SharedLog; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.SystemProperties; +import android.text.TextUtils; import androidx.annotation.NonNull; @@ -44,11 +46,8 @@ public abstract class TetheringDependencies { * Get a reference to the BpfCoordinator to be used by tethering. */ public @NonNull BpfCoordinator getBpfCoordinator( - @NonNull Handler handler, @NonNull INetd netd, @NonNull SharedLog log, @NonNull BpfCoordinator.Dependencies deps) { - final NetworkStatsManager statsManager = - (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE); - return new BpfCoordinator(handler, netd, statsManager, log, deps); + return new BpfCoordinator(deps); } /** @@ -150,4 +149,11 @@ public abstract class TetheringDependencies { * Get a reference to BluetoothAdapter to be used by tethering. */ public abstract BluetoothAdapter getBluetoothAdapter(); + + /** + * Get SystemProperties which indicate whether tethering is denied. + */ + public boolean isTetheringDenied() { + return TextUtils.equals(SystemProperties.get("ro.tether.denied"), "true"); + } } diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index c3bc915a232d..4f8860539158 100644 --- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -89,12 +89,14 @@ import android.os.test.TestLooper; import android.text.TextUtils; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.PrivateAddressCoordinator; +import com.android.networkstack.tethering.TetheringConfiguration; import org.junit.Before; import org.junit.Test; @@ -142,6 +144,7 @@ public class IpServerTest { @Mock private IpServer.Dependencies mDependencies; @Mock private PrivateAddressCoordinator mAddressCoordinator; @Mock private NetworkStatsManager mStatsManager; + @Mock private TetheringConfiguration mTetherConfig; @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor; @@ -225,10 +228,35 @@ public class IpServerTest { MockitoAnnotations.initMocks(this); when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog); when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(mTestAddress); - - BpfCoordinator bc = new BpfCoordinator(new Handler(mLooper.getLooper()), mNetd, - mStatsManager, mSharedLog, new BpfCoordinator.Dependencies()); - mBpfCoordinator = spy(bc); + when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */); + + mBpfCoordinator = spy(new BpfCoordinator( + new BpfCoordinator.Dependencies() { + @NonNull + public Handler getHandler() { + return new Handler(mLooper.getLooper()); + } + + @NonNull + public INetd getNetd() { + return mNetd; + } + + @NonNull + public NetworkStatsManager getNetworkStatsManager() { + return mStatsManager; + } + + @NonNull + public SharedLog getSharedLog() { + return mSharedLog; + } + + @Nullable + public TetheringConfiguration getTetherConfig() { + return mTetherConfig; + } + })); } @Test @@ -671,18 +699,21 @@ public class IpServerTest { } } - private TetherOffloadRuleParcel matches( + @NonNull + private static TetherOffloadRuleParcel matches( int upstreamIfindex, InetAddress dst, MacAddress dstMac) { return argThat(new TetherOffloadRuleParcelMatcher(upstreamIfindex, dst, dstMac)); } + @NonNull private static Ipv6ForwardingRule makeForwardingRule( int upstreamIfindex, @NonNull InetAddress dst, @NonNull MacAddress dstMac) { return new Ipv6ForwardingRule(upstreamIfindex, TEST_IFACE_PARAMS.index, (Inet6Address) dst, TEST_IFACE_PARAMS.macAddr, dstMac); } - private TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) { + @NonNull + private static TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) { TetherStatsParcel parcel = new TetherStatsParcel(); parcel.ifIndex = ifIndex; return parcel; diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java index e2d7aab4e33f..64242ae8255f 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -25,48 +25,76 @@ import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStats.UID_TETHERING; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; -import static com.android.networkstack.tethering.BpfCoordinator - .DEFAULT_PERFORM_POLL_INTERVAL_MS; import static com.android.networkstack.tethering.BpfCoordinator.StatsType; import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE; import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID; - -import static junit.framework.Assert.assertNotNull; - +import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.annotation.NonNull; import android.app.usage.NetworkStatsManager; import android.net.INetd; +import android.net.InetAddresses; +import android.net.MacAddress; import android.net.NetworkStats; +import android.net.TetherOffloadRuleParcel; import android.net.TetherStatsParcel; +import android.net.ip.IpServer; import android.net.util.SharedLog; import android.os.Handler; import android.os.test.TestLooper; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.testutils.TestableNetworkStatsProviderCbBinder; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.net.Inet6Address; +import java.net.InetAddress; import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; @RunWith(AndroidJUnit4.class) @SmallTest public class BpfCoordinatorTest { + private static final int DOWNSTREAM_IFINDEX = 10; + private static final MacAddress DOWNSTREAM_MAC = MacAddress.ALL_ZEROS_ADDRESS; + private static final InetAddress NEIGH_A = InetAddresses.parseNumericAddress("2001:db8::1"); + private static final InetAddress NEIGH_B = InetAddresses.parseNumericAddress("2001:db8::2"); + private static final MacAddress MAC_A = MacAddress.fromString("00:00:00:00:00:0a"); + private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b"); + @Mock private NetworkStatsManager mStatsManager; @Mock private INetd mNetd; + @Mock private IpServer mIpServer; + @Mock private TetheringConfiguration mTetherConfig; + // Late init since methods must be called by the thread that created this object. private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb; private BpfCoordinator.BpfTetherStatsProvider mTetherStatsProvider; @@ -75,14 +103,35 @@ public class BpfCoordinatorTest { private final TestLooper mTestLooper = new TestLooper(); private BpfCoordinator.Dependencies mDeps = new BpfCoordinator.Dependencies() { - @Override - int getPerformPollInterval() { - return DEFAULT_PERFORM_POLL_INTERVAL_MS; + @NonNull + public Handler getHandler() { + return new Handler(mTestLooper.getLooper()); + } + + @NonNull + public INetd getNetd() { + return mNetd; + } + + @NonNull + public NetworkStatsManager getNetworkStatsManager() { + return mStatsManager; + } + + @NonNull + public SharedLog getSharedLog() { + return new SharedLog("test"); + } + + @Nullable + public TetheringConfiguration getTetherConfig() { + return mTetherConfig; } }; @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */); } private void waitForIdle() { @@ -95,9 +144,7 @@ public class BpfCoordinatorTest { @NonNull private BpfCoordinator makeBpfCoordinator() throws Exception { - BpfCoordinator coordinator = new BpfCoordinator( - new Handler(mTestLooper.getLooper()), mNetd, mStatsManager, new SharedLog("test"), - mDeps); + final BpfCoordinator coordinator = new BpfCoordinator(mDeps); final ArgumentCaptor<BpfCoordinator.BpfTetherStatsProvider> tetherStatsProviderCaptor = ArgumentCaptor.forClass(BpfCoordinator.BpfTetherStatsProvider.class); @@ -130,9 +177,11 @@ public class BpfCoordinatorTest { return parcel; } + // Set up specific tether stats list and wait for the stats cache is updated by polling thread + // in the coordinator. Beware of that it is only used for the default polling interval. private void setTetherOffloadStatsList(TetherStatsParcel[] tetherStatsList) throws Exception { when(mNetd.tetherOffloadGetStats()).thenReturn(tetherStatsList); - mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); waitForIdle(); } @@ -201,7 +250,7 @@ public class BpfCoordinatorTest { clearInvocations(mNetd); // Verify the polling update thread stopped. - mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); waitForIdle(); verify(mNetd, never()).tetherOffloadGetStats(); } @@ -226,21 +275,333 @@ public class BpfCoordinatorTest { when(mNetd.tetherOffloadGetStats()).thenReturn( new TetherStatsParcel[] {buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0)}); mTetherStatsProvider.onSetAlert(100); - mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); waitForIdle(); mTetherStatsProviderCb.assertNoCallback(); // Verify that notifyAlertReached fired when quota is reached. when(mNetd.tetherOffloadGetStats()).thenReturn( new TetherStatsParcel[] {buildTestTetherStatsParcel(mobileIfIndex, 50, 0, 50, 0)}); - mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); waitForIdle(); mTetherStatsProviderCb.expectNotifyAlertReached(); // Verify that set quota with UNLIMITED won't trigger any callback. mTetherStatsProvider.onSetAlert(QUOTA_UNLIMITED); - mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); waitForIdle(); mTetherStatsProviderCb.assertNoCallback(); } + + // The custom ArgumentMatcher simply comes from IpServerTest. + // TODO: move both of them into a common utility class for reusing the code. + private static class TetherOffloadRuleParcelMatcher implements + ArgumentMatcher<TetherOffloadRuleParcel> { + public final int upstreamIfindex; + public final int downstreamIfindex; + public final Inet6Address address; + public final MacAddress srcMac; + public final MacAddress dstMac; + + TetherOffloadRuleParcelMatcher(@NonNull Ipv6ForwardingRule rule) { + upstreamIfindex = rule.upstreamIfindex; + downstreamIfindex = rule.downstreamIfindex; + address = rule.address; + srcMac = rule.srcMac; + dstMac = rule.dstMac; + } + + public boolean matches(@NonNull TetherOffloadRuleParcel parcel) { + return upstreamIfindex == parcel.inputInterfaceIndex + && (downstreamIfindex == parcel.outputInterfaceIndex) + && Arrays.equals(address.getAddress(), parcel.destination) + && (128 == parcel.prefixLength) + && Arrays.equals(srcMac.toByteArray(), parcel.srcL2Address) + && Arrays.equals(dstMac.toByteArray(), parcel.dstL2Address); + } + + public String toString() { + return String.format("TetherOffloadRuleParcelMatcher(%d, %d, %s, %s, %s", + upstreamIfindex, downstreamIfindex, address.getHostAddress(), srcMac, dstMac); + } + } + + @NonNull + private TetherOffloadRuleParcel matches(@NonNull Ipv6ForwardingRule rule) { + return argThat(new TetherOffloadRuleParcelMatcher(rule)); + } + + @NonNull + private static Ipv6ForwardingRule buildTestForwardingRule( + int upstreamIfindex, @NonNull InetAddress address, @NonNull MacAddress dstMac) { + return new Ipv6ForwardingRule(upstreamIfindex, DOWNSTREAM_IFINDEX, (Inet6Address) address, + DOWNSTREAM_MAC, dstMac); + } + + @Test + public void testSetDataLimit() throws Exception { + setupFunctioningNetdInterface(); + + final BpfCoordinator coordinator = makeBpfCoordinator(); + + final String mobileIface = "rmnet_data0"; + final Integer mobileIfIndex = 100; + coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + + // [1] Default limit. + // Set the unlimited quota as default if the service has never applied a data limit for a + // given upstream. Note that the data limit only be applied on an upstream which has rules. + final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); + final InOrder inOrder = inOrder(mNetd); + coordinator.tetherOffloadRuleAdd(mIpServer, rule); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(rule)); + inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, QUOTA_UNLIMITED); + inOrder.verifyNoMoreInteractions(); + + // [2] Specific limit. + // Applying the data limit boundary {min, 1gb, max, infinity} on current upstream. + for (final long quota : new long[] {0, 1048576000, Long.MAX_VALUE, QUOTA_UNLIMITED}) { + mTetherStatsProvider.onSetLimit(mobileIface, quota); + waitForIdle(); + inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, quota); + inOrder.verifyNoMoreInteractions(); + } + + // [3] Invalid limit. + // The valid range of quota is 0..max_int64 or -1 (unlimited). + final long invalidLimit = Long.MIN_VALUE; + try { + mTetherStatsProvider.onSetLimit(mobileIface, invalidLimit); + waitForIdle(); + fail("No exception thrown for invalid limit " + invalidLimit + "."); + } catch (IllegalArgumentException expected) { + assertEquals(expected.getMessage(), "invalid quota value " + invalidLimit); + } + } + + // TODO: Test the case in which the rules are changed from different IpServer objects. + @Test + public void testSetDataLimitOnRuleChange() throws Exception { + setupFunctioningNetdInterface(); + + final BpfCoordinator coordinator = makeBpfCoordinator(); + + final String mobileIface = "rmnet_data0"; + final Integer mobileIfIndex = 100; + coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + + // Applying a data limit to the current upstream does not take any immediate action. + // The data limit could be only set on an upstream which has rules. + final long limit = 12345; + final InOrder inOrder = inOrder(mNetd); + mTetherStatsProvider.onSetLimit(mobileIface, limit); + waitForIdle(); + inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); + + // Adding the first rule on current upstream immediately sends the quota to netd. + final Ipv6ForwardingRule ruleA = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); + coordinator.tetherOffloadRuleAdd(mIpServer, ruleA); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ruleA)); + inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, limit); + inOrder.verifyNoMoreInteractions(); + + // Adding the second rule on current upstream does not send the quota to netd. + final Ipv6ForwardingRule ruleB = buildTestForwardingRule(mobileIfIndex, NEIGH_B, MAC_B); + coordinator.tetherOffloadRuleAdd(mIpServer, ruleB); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ruleB)); + inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); + + // Removing the second rule on current upstream does not send the quota to netd. + coordinator.tetherOffloadRuleRemove(mIpServer, ruleB); + inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ruleB)); + inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); + + // Removing the last rule on current upstream immediately sends the cleanup stuff to netd. + when(mNetd.tetherOffloadGetAndClearStats(mobileIfIndex)) + .thenReturn(buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0)); + coordinator.tetherOffloadRuleRemove(mIpServer, ruleA); + inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ruleA)); + inOrder.verify(mNetd).tetherOffloadGetAndClearStats(mobileIfIndex); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testTetherOffloadRuleUpdateAndClear() throws Exception { + setupFunctioningNetdInterface(); + + final BpfCoordinator coordinator = makeBpfCoordinator(); + + final String ethIface = "eth1"; + final String mobileIface = "rmnet_data0"; + final Integer ethIfIndex = 100; + final Integer mobileIfIndex = 101; + coordinator.addUpstreamNameToLookupTable(ethIfIndex, ethIface); + coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + + final InOrder inOrder = inOrder(mNetd); + + // Before the rule test, here are the additional actions while the rules are changed. + // - After adding the first rule on a given upstream, the coordinator adds a data limit. + // If the service has never applied the data limit, set an unlimited quota as default. + // - After removing the last rule on a given upstream, the coordinator gets the last stats. + // Then, it clears the stats and the limit entry from BPF maps. + // See tetherOffloadRule{Add, Remove, Clear, Clean}. + + // [1] Adding rules on the upstream Ethernet. + // Note that the default data limit is applied after the first rule is added. + final Ipv6ForwardingRule ethernetRuleA = buildTestForwardingRule( + ethIfIndex, NEIGH_A, MAC_A); + final Ipv6ForwardingRule ethernetRuleB = buildTestForwardingRule( + ethIfIndex, NEIGH_B, MAC_B); + + coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleA); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ethernetRuleA)); + inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(ethIfIndex, QUOTA_UNLIMITED); + + coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleB); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ethernetRuleB)); + + // [2] Update the existing rules from Ethernet to cellular. + final Ipv6ForwardingRule mobileRuleA = buildTestForwardingRule( + mobileIfIndex, NEIGH_A, MAC_A); + final Ipv6ForwardingRule mobileRuleB = buildTestForwardingRule( + mobileIfIndex, NEIGH_B, MAC_B); + when(mNetd.tetherOffloadGetAndClearStats(ethIfIndex)) + .thenReturn(buildTestTetherStatsParcel(ethIfIndex, 10, 20, 30, 40)); + + // Update the existing rules for upstream changes. The rules are removed and re-added one + // by one for updating upstream interface index by #tetherOffloadRuleUpdate. + coordinator.tetherOffloadRuleUpdate(mIpServer, mobileIfIndex); + inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ethernetRuleA)); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(mobileRuleA)); + inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, QUOTA_UNLIMITED); + inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ethernetRuleB)); + inOrder.verify(mNetd).tetherOffloadGetAndClearStats(ethIfIndex); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(mobileRuleB)); + + // [3] Clear all rules for a given IpServer. + when(mNetd.tetherOffloadGetAndClearStats(mobileIfIndex)) + .thenReturn(buildTestTetherStatsParcel(mobileIfIndex, 50, 60, 70, 80)); + coordinator.tetherOffloadRuleClear(mIpServer); + inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(mobileRuleA)); + inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(mobileRuleB)); + inOrder.verify(mNetd).tetherOffloadGetAndClearStats(mobileIfIndex); + + // [4] Force pushing stats update to verify that the last diff of stats is reported on all + // upstreams. + mTetherStatsProvider.pushTetherStats(); + mTetherStatsProviderCb.expectNotifyStatsUpdated( + new NetworkStats(0L, 2) + .addEntry(buildTestEntry(STATS_PER_IFACE, ethIface, 10, 20, 30, 40)) + .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 50, 60, 70, 80)), + new NetworkStats(0L, 2) + .addEntry(buildTestEntry(STATS_PER_UID, ethIface, 10, 20, 30, 40)) + .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 50, 60, 70, 80))); + } + + @Test + public void testTetheringConfigDisable() throws Exception { + setupFunctioningNetdInterface(); + when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false); + + final BpfCoordinator coordinator = makeBpfCoordinator(); + coordinator.startPolling(); + + // The tether stats polling task should not be scheduled. + mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); + waitForIdle(); + verify(mNetd, never()).tetherOffloadGetStats(); + + // The interface name lookup table can't be added. + final String iface = "rmnet_data0"; + final Integer ifIndex = 100; + coordinator.addUpstreamNameToLookupTable(ifIndex, iface); + assertEquals(0, coordinator.getInterfaceNamesForTesting().size()); + + // The rule can't be added. + final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1"); + final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a"); + final Ipv6ForwardingRule rule = buildTestForwardingRule(ifIndex, neigh, mac); + coordinator.tetherOffloadRuleAdd(mIpServer, rule); + verify(mNetd, never()).tetherOffloadRuleAdd(any()); + LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = + coordinator.getForwardingRulesForTesting().get(mIpServer); + assertNull(rules); + + // The rule can't be removed. This is not a realistic case because adding rule is not + // allowed. That implies no rule could be removed, cleared or updated. Verify these + // cases just in case. + rules = new LinkedHashMap<Inet6Address, Ipv6ForwardingRule>(); + rules.put(rule.address, rule); + coordinator.getForwardingRulesForTesting().put(mIpServer, rules); + coordinator.tetherOffloadRuleRemove(mIpServer, rule); + verify(mNetd, never()).tetherOffloadRuleRemove(any()); + rules = coordinator.getForwardingRulesForTesting().get(mIpServer); + assertNotNull(rules); + assertEquals(1, rules.size()); + + // The rule can't be cleared. + coordinator.tetherOffloadRuleClear(mIpServer); + verify(mNetd, never()).tetherOffloadRuleRemove(any()); + rules = coordinator.getForwardingRulesForTesting().get(mIpServer); + assertNotNull(rules); + assertEquals(1, rules.size()); + + // The rule can't be updated. + coordinator.tetherOffloadRuleUpdate(mIpServer, rule.upstreamIfindex + 1 /* new */); + verify(mNetd, never()).tetherOffloadRuleRemove(any()); + verify(mNetd, never()).tetherOffloadRuleAdd(any()); + rules = coordinator.getForwardingRulesForTesting().get(mIpServer); + assertNotNull(rules); + assertEquals(1, rules.size()); + } + + @Test + public void testTetheringConfigSetPollingInterval() throws Exception { + setupFunctioningNetdInterface(); + + final BpfCoordinator coordinator = makeBpfCoordinator(); + + // [1] The default polling interval. + coordinator.startPolling(); + assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval()); + coordinator.stopPolling(); + + // [2] Expect the invalid polling interval isn't applied. The valid range of interval is + // DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long. + for (final int interval + : new int[] {0, 100, DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS - 1}) { + when(mTetherConfig.getOffloadPollInterval()).thenReturn(interval); + coordinator.startPolling(); + assertEquals(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, coordinator.getPollingInterval()); + coordinator.stopPolling(); + } + + // [3] Set a specific polling interval which is larger than default value. + // Use a large polling interval to avoid flaky test because the time forwarding + // approximation is used to verify the scheduled time of the polling thread. + final int pollingInterval = 100_000; + when(mTetherConfig.getOffloadPollInterval()).thenReturn(pollingInterval); + coordinator.startPolling(); + + // Expect the specific polling interval to be applied. + assertEquals(pollingInterval, coordinator.getPollingInterval()); + + // Start on a new polling time slot. + mTestLooper.moveTimeForward(pollingInterval); + waitForIdle(); + clearInvocations(mNetd); + + // Move time forward to 90% polling interval time. Expect that the polling thread has not + // scheduled yet. + mTestLooper.moveTimeForward((long) (pollingInterval * 0.9)); + waitForIdle(); + verify(mNetd, never()).tetherOffloadGetStats(); + + // Move time forward to the remaining 10% polling interval time. Expect that the polling + // thread has scheduled. + mTestLooper.moveTimeForward((long) (pollingInterval * 0.1)); + waitForIdle(); + verify(mNetd).tetherOffloadGetStats(); + } } diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java index 72fa916b9e42..354e75356e9f 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java @@ -16,8 +16,16 @@ package com.android.networkstack.tethering; +import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE; +import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK; +import static android.net.TetheringConstants.EXTRA_RUN_PROVISION; +import static android.net.TetheringConstants.EXTRA_TETHER_PROVISIONING_RESPONSE; +import static android.net.TetheringConstants.EXTRA_TETHER_SILENT_PROVISIONING_ACTION; +import static android.net.TetheringConstants.EXTRA_TETHER_SUBID; +import static android.net.TetheringConstants.EXTRA_TETHER_UI_PROVISIONING_APP_NAME; import static android.net.TetheringManager.TETHERING_BLUETOOTH; import static android.net.TetheringManager.TETHERING_ETHERNET; +import static android.net.TetheringManager.TETHERING_INVALID; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHERING_WIFI_P2P; @@ -44,6 +52,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.net.util.SharedLog; import android.os.Bundle; @@ -53,6 +62,7 @@ import android.os.ResultReceiver; import android.os.SystemProperties; import android.os.test.TestLooper; import android.provider.DeviceConfig; +import android.provider.Settings; import android.telephony.CarrierConfigManager; import androidx.test.filters.SmallTest; @@ -76,6 +86,7 @@ public final class EntitlementManagerTest { private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; + private static final String PROVISIONING_APP_RESPONSE = "app_response"; @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private Context mContext; @@ -122,15 +133,51 @@ public final class EntitlementManagerTest { } @Override - protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { + protected Intent runUiTetherProvisioning(int type, + final TetheringConfiguration config, final ResultReceiver receiver) { + Intent intent = super.runUiTetherProvisioning(type, config, receiver); + assertUiTetherProvisioningIntent(type, config, receiver, intent); uiProvisionCount++; receiver.send(fakeEntitlementResult, null); + return intent; + } + + private void assertUiTetherProvisioningIntent(int type, final TetheringConfiguration config, + final ResultReceiver receiver, final Intent intent) { + assertEquals(Settings.ACTION_TETHER_PROVISIONING_UI, intent.getAction()); + assertEquals(type, intent.getIntExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_INVALID)); + final String[] appName = intent.getStringArrayExtra( + EXTRA_TETHER_UI_PROVISIONING_APP_NAME); + assertEquals(PROVISIONING_APP_NAME.length, appName.length); + for (int i = 0; i < PROVISIONING_APP_NAME.length; i++) { + assertEquals(PROVISIONING_APP_NAME[i], appName[i]); + } + assertEquals(receiver, intent.getParcelableExtra(EXTRA_PROVISION_CALLBACK)); + assertEquals(config.activeDataSubId, + intent.getIntExtra(EXTRA_TETHER_SUBID, INVALID_SUBSCRIPTION_ID)); } @Override - protected void runSilentTetherProvisioning(int type, int subId) { + protected Intent runSilentTetherProvisioning(int type, + final TetheringConfiguration config) { + Intent intent = super.runSilentTetherProvisioning(type, config); + assertSilentTetherProvisioning(type, config, intent); silentProvisionCount++; addDownstreamMapping(type, fakeEntitlementResult); + return intent; + } + + private void assertSilentTetherProvisioning(int type, final TetheringConfiguration config, + final Intent intent) { + assertEquals(type, intent.getIntExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_INVALID)); + assertEquals(true, intent.getBooleanExtra(EXTRA_RUN_PROVISION, false)); + assertEquals(PROVISIONING_NO_UI_APP_NAME, + intent.getStringExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION)); + assertEquals(PROVISIONING_APP_RESPONSE, + intent.getStringExtra(EXTRA_TETHER_PROVISIONING_RESPONSE)); + assertTrue(intent.hasExtra(EXTRA_PROVISION_CALLBACK)); + assertEquals(config.activeDataSubId, + intent.getIntExtra(EXTRA_TETHER_SUBID, INVALID_SUBSCRIPTION_ID)); } } @@ -187,6 +234,8 @@ public final class EntitlementManagerTest { .thenReturn(PROVISIONING_APP_NAME); when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) .thenReturn(PROVISIONING_NO_UI_APP_NAME); + when(mResources.getString(R.string.config_mobile_hotspot_provision_response)).thenReturn( + PROVISIONING_APP_RESPONSE); // Act like the CarrierConfigManager is present and ready unless told otherwise. when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) .thenReturn(mCarrierConfigManager); diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java index 1999ad786ed4..a9ac4e2851f3 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java @@ -61,6 +61,8 @@ public class TetheringConfigurationTest { private final SharedLog mLog = new SharedLog("TetheringConfigurationTest"); private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; + private static final String PROVISIONING_APP_RESPONSE = "app_response"; @Mock private Context mContext; @Mock private TelephonyManager mTelephonyManager; @Mock private Resources mResources; @@ -292,7 +294,7 @@ public class TetheringConfigurationTest { initializeBpfOffloadConfiguration(true, null /* unset */); final TetheringConfiguration enableByRes = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertTrue(enableByRes.enableBpfOffload); + assertTrue(enableByRes.isBpfOffloadEnabled()); } @Test @@ -301,7 +303,7 @@ public class TetheringConfigurationTest { initializeBpfOffloadConfiguration(res, "true"); final TetheringConfiguration enableByDevConOverride = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertTrue(enableByDevConOverride.enableBpfOffload); + assertTrue(enableByDevConOverride.isBpfOffloadEnabled()); } } @@ -310,7 +312,7 @@ public class TetheringConfigurationTest { initializeBpfOffloadConfiguration(false, null /* unset */); final TetheringConfiguration disableByRes = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertFalse(disableByRes.enableBpfOffload); + assertFalse(disableByRes.isBpfOffloadEnabled()); } @Test @@ -319,7 +321,7 @@ public class TetheringConfigurationTest { initializeBpfOffloadConfiguration(res, "false"); final TetheringConfiguration disableByDevConOverride = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertFalse(disableByDevConOverride.enableBpfOffload); + assertFalse(disableByDevConOverride.isBpfOffloadEnabled()); } } @@ -388,6 +390,8 @@ public class TetheringConfigurationTest { new MockTetheringConfiguration(mMockContext, mLog, anyValidSubId); assertEquals(mockCfg.provisioningApp[0], PROVISIONING_APP_NAME[0]); assertEquals(mockCfg.provisioningApp[1], PROVISIONING_APP_NAME[1]); + assertEquals(mockCfg.provisioningAppNoUi, PROVISIONING_NO_UI_APP_NAME); + assertEquals(mockCfg.provisioningResponse, PROVISIONING_APP_RESPONSE); } private void setUpResourceForSubId() { @@ -403,6 +407,10 @@ public class TetheringConfigurationTest { new int[0]); when(mResourcesForSubId.getStringArray( R.array.config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME); + when(mResourcesForSubId.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) + .thenReturn(PROVISIONING_NO_UI_APP_NAME); + when(mResourcesForSubId.getString( + R.string.config_mobile_hotspot_provision_response)).thenReturn( + PROVISIONING_APP_RESPONSE); } - } diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index 8146a58dddcb..526199226a6e 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -16,6 +16,7 @@ package com.android.networkstack.tethering; +import static android.content.pm.PackageManager.GET_ACTIVITIES; import static android.hardware.usb.UsbManager.USB_CONFIGURED; import static android.hardware.usb.UsbManager.USB_CONNECTED; import static android.hardware.usb.UsbManager.USB_FUNCTION_NCM; @@ -81,6 +82,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; @@ -204,6 +206,7 @@ public class TetheringTest { @Mock private EthernetManager mEm; @Mock private TetheringNotificationUpdater mNotificationUpdater; @Mock private BpfCoordinator mBpfCoordinator; + @Mock private PackageManager mPackageManager; private final MockIpServerDependencies mIpServerDependencies = spy(new MockIpServerDependencies()); @@ -264,6 +267,11 @@ public class TetheringTest { } @Override + public PackageManager getPackageManager() { + return mPackageManager; + } + + @Override public String getSystemServiceName(Class<?> serviceClass) { if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE; return super.getSystemServiceName(serviceClass); @@ -338,8 +346,8 @@ public class TetheringTest { } @Override - public BpfCoordinator getBpfCoordinator(Handler handler, INetd netd, - SharedLog log, BpfCoordinator.Dependencies deps) { + public BpfCoordinator getBpfCoordinator( + BpfCoordinator.Dependencies deps) { return mBpfCoordinator; } @@ -425,6 +433,11 @@ public class TetheringTest { public TetheringNotificationUpdater getNotificationUpdater(Context ctx, Looper looper) { return mNotificationUpdater; } + + @Override + public boolean isTetheringDenied() { + return false; + } } private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4, @@ -1951,6 +1964,23 @@ public class TetheringTest { assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastTetherError(TEST_ETH_IFNAME)); } + @Test + public void testProvisioningNeededButUnavailable() throws Exception { + assertTrue(mTethering.isTetheringSupported()); + verify(mPackageManager, never()).getPackageInfo(PROVISIONING_APP_NAME[0], GET_ACTIVITIES); + + setupForRequiredProvisioning(); + assertTrue(mTethering.isTetheringSupported()); + verify(mPackageManager).getPackageInfo(PROVISIONING_APP_NAME[0], GET_ACTIVITIES); + reset(mPackageManager); + + doThrow(PackageManager.NameNotFoundException.class).when(mPackageManager).getPackageInfo( + PROVISIONING_APP_NAME[0], GET_ACTIVITIES); + setupForRequiredProvisioning(); + assertFalse(mTethering.isTetheringSupported()); + verify(mPackageManager).getPackageInfo(PROVISIONING_APP_NAME[0], GET_ACTIVITIES); + } + // TODO: Test that a request for hotspot mode doesn't interfere with an // already operating tethering mode interface. } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 0f98992d1ea0..fea2e7b841e0 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -21,6 +21,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -83,7 +84,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect final long identity = Binder.clearCallingIdentity(); try { mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, mSystemSupport.getPendingIntentActivity( - mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0)); + mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), + PendingIntent.FLAG_IMMUTABLE)); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java index 26e85beba58b..103151dcdda5 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -51,7 +51,7 @@ import java.util.function.Consumer; */ public class AppPredictionPerUserService extends AbstractPerUserSystemService<AppPredictionPerUserService, AppPredictionManagerService> - implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks { + implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks { private static final String TAG = AppPredictionPerUserService.class.getSimpleName(); private static final String PREDICT_USING_PEOPLE_SERVICE_PREFIX = @@ -114,11 +114,8 @@ public class AppPredictionPerUserService extends public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context, @NonNull AppPredictionSessionId sessionId) { if (!mSessionInfos.containsKey(sessionId)) { - // TODO(b/157500121): remove below forceUsingPeopleService logic after testing - // PeopleService for 2 weeks on Droidfood. - final boolean forceUsingPeopleService = context.getUiSurface().equals("share"); mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context, - forceUsingPeopleService || DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, + DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false), this::removeAppPredictionSessionInfo)); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java index 0ec8654f2a20..19248ca54611 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java @@ -45,6 +45,8 @@ final class AutofillInlineSessionController { private final Object mLock; @NonNull private final Handler mHandler; + @NonNull + private final InlineFillUi.InlineUiEventCallback mUiCallback; @Nullable @GuardedBy("mLock") @@ -54,12 +56,14 @@ final class AutofillInlineSessionController { private InlineFillUi mInlineFillUi; AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal, - int userId, ComponentName componentName, Handler handler, Object lock) { + int userId, ComponentName componentName, Handler handler, Object lock, + InlineFillUi.InlineUiEventCallback callback) { mInputMethodManagerInternal = inputMethodManagerInternal; mUserId = userId; mComponentName = componentName; mHandler = handler; mLock = lock; + mUiCallback = callback; } @@ -77,16 +81,33 @@ final class AutofillInlineSessionController { if (mSession != null) { // Destroy the existing session. mSession.destroySessionLocked(); - mInlineFillUi = null; } + mInlineFillUi = null; // TODO(b/151123764): consider reusing the same AutofillInlineSession object for the // same field. mSession = new AutofillInlineSuggestionsRequestSession(mInputMethodManagerInternal, mUserId, - mComponentName, mHandler, mLock, autofillId, requestConsumer, uiExtras); + mComponentName, mHandler, mLock, autofillId, requestConsumer, uiExtras, + mUiCallback); mSession.onCreateInlineSuggestionsRequestLocked(); } /** + * Destroys the current session. May send an empty response to IME to clear the suggestions if + * the focus didn't change to a different field. + * + * @param autofillId the currently focused view from the autofill session + */ + @GuardedBy("mLock") + void destroyLocked(@NonNull AutofillId autofillId) { + if (mSession != null) { + mSession.onInlineSuggestionsResponseLocked(InlineFillUi.emptyUi(autofillId)); + mSession.destroySessionLocked(); + mSession = null; + } + mInlineFillUi = null; + } + + /** * Returns the {@link InlineSuggestionsRequest} provided by IME for the last request. * * <p> The caller is responsible for making sure Autofill hears back from IME before calling diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java index 48895ad42e99..68eeb0a3ca2e 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java @@ -29,6 +29,7 @@ import android.os.Handler; import android.os.RemoteException; import android.util.Slog; import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestion; import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InlineSuggestionsResponse; @@ -40,6 +41,7 @@ import com.android.server.autofill.ui.InlineFillUi; import com.android.server.inputmethod.InputMethodManagerInternal; import java.lang.ref.WeakReference; +import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -65,6 +67,8 @@ final class AutofillInlineSuggestionsRequestSession { private final Handler mHandler; @NonNull private final Bundle mUiExtras; + @NonNull + private final InlineFillUi.InlineUiEventCallback mUiCallback; @GuardedBy("mLock") @NonNull @@ -97,18 +101,22 @@ final class AutofillInlineSuggestionsRequestSession { @GuardedBy("mLock") private boolean mDestroyed = false; + @GuardedBy("mLock") + private boolean mPreviousHasNonPinSuggestionShow; AutofillInlineSuggestionsRequestSession( @NonNull InputMethodManagerInternal inputMethodManagerInternal, int userId, @NonNull ComponentName componentName, @NonNull Handler handler, @NonNull Object lock, @NonNull AutofillId autofillId, - @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) { + @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras, + @NonNull InlineFillUi.InlineUiEventCallback callback) { mInputMethodManagerInternal = inputMethodManagerInternal; mUserId = userId; mComponentName = componentName; mHandler = handler; mLock = lock; mUiExtras = uiExtras; + mUiCallback = callback; mAutofillId = autofillId; mImeRequestConsumer = requestConsumer; @@ -217,6 +225,7 @@ final class AutofillInlineSuggestionsRequestSession { // No-op if both the previous response and current response are empty. return; } + maybeNotifyFillUiEventLocked(response.getInlineSuggestions()); updateResponseToImeUncheckLocked(response); mPreviousResponseIsNotEmpty = !isEmptyResponse; } @@ -238,6 +247,38 @@ final class AutofillInlineSuggestionsRequestSession { } } + @GuardedBy("mLock") + private void maybeNotifyFillUiEventLocked(@NonNull List<InlineSuggestion> suggestions) { + if (mDestroyed) { + return; + } + boolean hasSuggestionToShow = false; + for (int i = 0; i < suggestions.size(); i++) { + InlineSuggestion suggestion = suggestions.get(i); + // It is possible we don't have any match result but we still have pinned + // suggestions. Only notify we have non-pinned suggestions to show + if (!suggestion.getInfo().isPinned()) { + hasSuggestionToShow = true; + break; + } + } + if (sDebug) { + Slog.d(TAG, "maybeNotifyFillUiEventLoked(): hasSuggestionToShow=" + hasSuggestionToShow + + ", mPreviousHasNonPinSuggestionShow=" + mPreviousHasNonPinSuggestionShow); + } + // Use mPreviousHasNonPinSuggestionShow to save previous status, if the display status + // change, we can notify the event. + if (hasSuggestionToShow && !mPreviousHasNonPinSuggestionShow) { + // From no suggestion to has suggestions to show + mUiCallback.notifyInlineUiShown(mAutofillId); + } else if (!hasSuggestionToShow && mPreviousHasNonPinSuggestionShow) { + // From has suggestions to no suggestions to show + mUiCallback.notifyInlineUiHidden(mAutofillId); + } + // Update the latest status + mPreviousHasNonPinSuggestionShow = hasSuggestionToShow; + } + /** * Handles the {@code request} and {@code callback} received from the IME. * diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 96b593d9682f..57ffe0498a88 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -815,6 +815,19 @@ final class AutofillManagerServiceImpl } } + void logAugmentedAutofillAuthenticationSelected(int sessionId, @Nullable String selectedDataset, + @Nullable Bundle clientState) { + synchronized (mLock) { + if (mAugmentedAutofillEventHistory == null + || mAugmentedAutofillEventHistory.getSessionId() != sessionId) { + return; + } + mAugmentedAutofillEventHistory.addEvent( + new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset, + clientState, null, null, null, null, null, null, null, null)); + } + } + void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, @Nullable Bundle clientState) { synchronized (mLock) { @@ -1199,6 +1212,14 @@ final class AutofillManagerServiceImpl } @Override + public void logAugmentedAutofillAuthenticationSelected(int sessionId, + String suggestionId, Bundle clientState) { + AutofillManagerServiceImpl.this + .logAugmentedAutofillAuthenticationSelected( + sessionId, suggestionId, clientState); + } + + @Override public void onServiceDied(@NonNull RemoteAugmentedAutofillService service) { Slog.w(TAG, "remote augmented autofill service died"); final RemoteAugmentedAutofillService remoteService = diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index a7d0061cc043..11f901538868 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -167,14 +167,16 @@ final class RemoteAugmentedAutofillService new IFillCallback.Stub() { @Override public void onSuccess(@Nullable List<Dataset> inlineSuggestionsData, - @Nullable Bundle clientState) { + @Nullable Bundle clientState, boolean showingFillWindow) { mCallbacks.resetLastResponse(); maybeRequestShowInlineSuggestions(sessionId, inlineSuggestionsRequest, inlineSuggestionsData, clientState, focusedId, focusedValue, inlineSuggestionsCallback, client, onErrorCallback, remoteRenderService); - requestAutofill.complete(null); + if (!showingFillWindow) { + requestAutofill.complete(null); + } } @Override @@ -263,7 +265,28 @@ final class RemoteAugmentedAutofillService request, inlineSuggestionsData, focusedId, filterText, new InlineFillUi.InlineSuggestionUiCallback() { @Override - public void autofill(Dataset dataset) { + public void autofill(Dataset dataset, int datasetIndex) { + if (dataset.getAuthentication() != null) { + mCallbacks.logAugmentedAutofillAuthenticationSelected(sessionId, + dataset.getId(), clientState); + final IntentSender action = dataset.getAuthentication(); + final int authenticationId = + AutofillManager.makeAuthenticationId( + Session.AUGMENTED_AUTOFILL_REQUEST_ID, + datasetIndex); + final Intent fillInIntent = new Intent(); + fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, + clientState); + try { + client.authenticate(sessionId, authenticationId, action, + fillInIntent, false); + } catch (RemoteException e) { + Slog.w(TAG, "Error starting auth flow"); + inlineSuggestionsCallback.apply( + InlineFillUi.emptyUi(focusedId)); + } + return; + } mCallbacks.logAugmentedAutofillSelected(sessionId, dataset.getId(), clientState); try { @@ -319,5 +342,8 @@ final class RemoteAugmentedAutofillService void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, @Nullable Bundle clientState); + + void logAugmentedAutofillAuthenticationSelected(int sessionId, + @Nullable String suggestionId, @Nullable Bundle clientState); } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index a9a0ab69f633..3114a6a02e31 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -144,7 +144,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private final MetricsLogger mMetricsLogger = new MetricsLogger(); - private static AtomicInteger sIdCounter = new AtomicInteger(); + static final int AUGMENTED_AUTOFILL_REQUEST_ID = 1; + + private static AtomicInteger sIdCounter = new AtomicInteger(2); /** * ID of the session. @@ -313,18 +315,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private final AssistDataReceiverImpl mAssistReceiver = new AssistDataReceiverImpl(); void onSwitchInputMethodLocked() { + // One caveat is that for the case where the focus is on a field for which regular autofill + // returns null, and augmented autofill is triggered, and then the user switches the input + // method. Tapping on the field again will not trigger a new augmented autofill request. + // This may be fixed by adding more checks such as whether mCurrentViewId is null. if (mExpiredResponse) { return; } - - if (shouldExpireResponseOnInputMethodSwitch()) { + if (shouldResetSessionStateOnInputMethodSwitch()) { // Set the old response expired, so the next action (ACTION_VIEW_ENTERED) can trigger // a new fill request. mExpiredResponse = true; + // Clear the augmented autofillable ids so augmented autofill will trigger again. + mAugmentedAutofillableIds = null; + // In case the field is augmented autofill only, we clear the current view id, so that + // we won't skip view entered due to same view entered, for the augmented autofill. + if (mForAugmentedAutofillOnly) { + mCurrentViewId = null; + } } } - private boolean shouldExpireResponseOnInputMethodSwitch() { + private boolean shouldResetSessionStateOnInputMethodSwitch() { // One of below cases will need a new fill request to update the inline spec for the new // input method. // 1. The autofill provider supports inline suggestion and the render service is available. @@ -726,7 +738,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState viewState.setState(newState); int requestId; - + // TODO(b/158623971): Update this to prevent possible overflow do { requestId = sIdCounter.getAndIncrement(); } while (requestId == INVALID_REQUEST_ID); @@ -831,7 +843,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState setClientLocked(client); mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal, - userId, componentName, handler, mLock); + userId, componentName, handler, mLock, + new InlineFillUi.InlineUiEventCallback() { + @Override + public void notifyInlineUiShown(AutofillId autofillId) { + notifyFillUiShown(autofillId); + } + + @Override + public void notifyInlineUiHidden(AutofillId autofillId) { + notifyFillUiHidden(autofillId); + } + }); mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); @@ -1317,6 +1340,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState this, intentSender, intent)); } + private void notifyFillUiHidden(@NonNull AutofillId autofillId) { + synchronized (mLock) { + try { + mClient.notifyFillUiHidden(this.id, autofillId); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending fill UI hidden notification", e); + } + } + } + + private void notifyFillUiShown(@NonNull AutofillId autofillId) { + synchronized (mLock) { + try { + mClient.notifyFillUiShown(this.id, autofillId); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending fill UI shown notification", e); + } + } + } + private void doStartIntentSender(IntentSender intentSender, Intent intent) { try { synchronized (mLock) { @@ -1334,6 +1377,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + id + " destroyed"); return; } + final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId); + if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) { + setAuthenticationResultForAugmentedAutofillLocked(data, authenticationId); + return; + } if (mResponses == null) { // Typically happens when app explicitly called cancel() while the service was showing // the auth UI. @@ -1341,7 +1389,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState removeSelf(); return; } - final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId); final FillResponse authenticatedResponse = mResponses.get(requestId); if (authenticatedResponse == null || data == null) { Slog.w(TAG, "no authenticated response"); @@ -1401,6 +1448,58 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") + void setAuthenticationResultForAugmentedAutofillLocked(Bundle data, int authId) { + final Dataset dataset = (data == null) ? null : + data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT); + if (sDebug) { + Slog.d(TAG, "Auth result for augmented autofill: sessionId=" + id + + ", authId=" + authId + ", dataset=" + dataset); + } + if (dataset == null + || dataset.getFieldIds().size() != 1 + || dataset.getFieldIds().get(0) == null + || dataset.getFieldValues().size() != 1 + || dataset.getFieldValues().get(0) == null) { + if (sDebug) { + Slog.d(TAG, "Rejecting empty/invalid auth result"); + } + mService.resetLastAugmentedAutofillResponse(); + removeSelfLocked(); + return; + } + final List<AutofillId> fieldIds = dataset.getFieldIds(); + final List<AutofillValue> autofillValues = dataset.getFieldValues(); + final AutofillId fieldId = fieldIds.get(0); + final AutofillValue value = autofillValues.get(0); + + // Update state to ensure that after filling the field here we don't end up firing another + // autofill request that will end up showing the same suggestions to the user again. When + // the auth activity came up, the field for which the suggestions were shown lost focus and + // mCurrentViewId was cleared. We need to set mCurrentViewId back to the id of the field + // that we are filling. + fieldId.setSessionId(id); + mCurrentViewId = fieldId; + + // Notify the Augmented Autofill provider of the dataset that was selected. + final Bundle clientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE); + mService.logAugmentedAutofillSelected(id, dataset.getId(), clientState); + + // Fill the value into the field. + if (sDebug) { + Slog.d(TAG, "Filling after auth: fieldId=" + fieldId + ", value=" + value); + } + try { + mClient.autofill(id, fieldIds, autofillValues, true); + } catch (RemoteException e) { + Slog.w(TAG, "Error filling after auth: fieldId=" + fieldId + ", value=" + value + + ", error=" + e); + } + + // Clear the suggestions since the user already accepted one of them. + mInlineSessionController.setInlineFillUiLocked(InlineFillUi.emptyUi(fieldId)); + } + + @GuardedBy("mLock") void setHasCallbackLocked(boolean hasIt) { if (mDestroyed) { Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: " @@ -2496,6 +2595,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + actionAsString(action) + ", flags=" + flags); } ViewState viewState = mViewStates.get(id); + if (sVerbose) { + Slog.v(TAG, "updateLocked(" + this.id + "): mCurrentViewId=" + mCurrentViewId + + ", mExpiredResponse=" + mExpiredResponse + ", viewState=" + viewState); + } if (viewState == null) { if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED @@ -2588,15 +2691,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState id)) { // Regular autofill handled the view and returned null response, but it // triggered augmented autofill - if (!isSameViewEntered || mExpiredResponse) { + if (!isSameViewEntered) { if (sDebug) Slog.d(TAG, "trigger augmented autofill."); triggerAugmentedAutofillLocked(flags); } else { if (sDebug) Slog.d(TAG, "skip augmented autofill for same view."); } return; - } else if (mForAugmentedAutofillOnly && isSameViewEntered - && !mExpiredResponse) { + } else if (mForAugmentedAutofillOnly && isSameViewEntered) { // Regular autofill is disabled. if (sDebug) Slog.d(TAG, "skip augmented autofill for same view."); return; @@ -2698,11 +2800,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // TODO(b/156099633): remove this once framework gets out of business of resending // inline suggestions when IME visibility changes. mInlineSessionController.hideInlineSuggestionsUiLocked(viewState.id); - try { - mClient.notifyFillUiHidden(this.id, viewState.id); - } catch (RemoteException e) { - Slog.e(TAG, "Error requesting to hide fill UI", e); - } viewState.resetState(ViewState.STATE_CHANGED); return; } else if ((viewState.id.equals(this.mCurrentViewId)) @@ -2729,11 +2826,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // TODO: we should be able to replace this with controller#filterInlineFillUiLocked // to accomplish filtering for augmented autofill. mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); - try { - mClient.notifyFillUiHidden(this.id, mCurrentViewId); - } catch (RemoteException e) { - Slog.e(TAG, "Error sending fill UI hidden notification", e); - } } } @@ -2819,11 +2911,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (requestShowInlineSuggestionsLocked(response, filterText)) { final ViewState currentView = mViewStates.get(mCurrentViewId); currentView.setState(ViewState.STATE_INLINE_SHOWN); - try { - mClient.notifyFillUiShown(this.id, mCurrentViewId); - } catch (RemoteException e) { - Slog.e(TAG, "Error sending fill UI shown notification", e); - } //TODO(b/137800469): Fix it to log showed only when IME asks for inflation, // rather than here where framework sends back the response. mService.logDatasetShown(id, mClientState); @@ -2894,11 +2981,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { mInlineSessionController.hideInlineSuggestionsUiLocked( focusedId); - try { - mClient.notifyFillUiHidden(this.id, focusedId); - } catch (RemoteException e) { - Slog.e(TAG, "Error sending fill UI hidden notification", e); - } } }, remoteRenderService); return mInlineSessionController.setInlineFillUiLocked(inlineFillUi); @@ -3208,16 +3290,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState }; // When the inline suggestion render service is available and the view is focused, there - // are 2 cases when augmented autofill should ask IME for inline suggestion request, + // are 3 cases when augmented autofill should ask IME for inline suggestion request, // because standard autofill flow didn't: // 1. the field is augmented autofill only (when standard autofill provider is None or // when it returns null response) // 2. standard autofill provider doesn't support inline suggestion + // 3. we re-entered the autofill session and standard autofill was not re-triggered, this is + // recognized by seeing mExpiredResponse == true final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); if (remoteRenderService != null && (mForAugmentedAutofillOnly - || !isInlineSuggestionsEnabledByAutofillProviderLocked()) + || !isInlineSuggestionsEnabledByAutofillProviderLocked() + || mExpiredResponse) && isViewFocusedLocked(flags)) { if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback( @@ -3410,11 +3495,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (mCurrentViewId != null) { mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); - try { - mClient.notifyFillUiHidden(this.id, mCurrentViewId); - } catch (RemoteException e) { - Slog.e(TAG, "Error sending fill UI hidden notification", e); - } } autoFillApp(dataset); return; @@ -3702,6 +3782,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState unlinkClientVultureLocked(); mUi.destroyAll(mPendingSaveUi, this, true); mUi.clearCallback(this); + if (mCurrentViewId != null) { + mInlineSessionController.destroyLocked(mCurrentViewId); + } mDestroyed = true; // Log metrics diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java index a3d0fb955da4..627c0733b078 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java @@ -290,11 +290,26 @@ public final class InlineFillUi { /** * Callback to autofill a dataset to the client app. */ - void autofill(@NonNull Dataset dataset); + void autofill(@NonNull Dataset dataset, int datasetIndex); /** * Callback to start Intent in client app. */ void startIntentSender(@NonNull IntentSender intentSender, @NonNull Intent intent); } + + /** + * Callback for inline suggestion Ui related events. + */ + public interface InlineUiEventCallback { + /** + * Callback to notify inline ui is shown. + */ + void notifyInlineUiShown(@NonNull AutofillId autofillId); + + /** + * Callback to notify inline ui is hidden. + */ + void notifyInlineUiHidden(@NonNull AutofillId autofillId); + } } diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java index c8485b7c2b38..462ffd668e2e 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java @@ -109,7 +109,7 @@ final class InlineSuggestionFactory { return createInlineSuggestionsInternal(/* isAugmented= */ true, request, datasets, autofillId, onErrorCallback, (dataset, datasetIndex) -> - inlineSuggestionUiCallback.autofill(dataset), + inlineSuggestionUiCallback.autofill(dataset, datasetIndex), (intentSender) -> inlineSuggestionUiCallback.startIntentSender(intentSender, new Intent()), remoteRenderService); diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngineThread.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngineThread.java index 7075608674a1..71f1dbf35008 100644 --- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngineThread.java +++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngineThread.java @@ -1,10 +1,10 @@ package com.android.server.backup.restore; import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.AutoCloseInputStream; import libcore.io.IoUtils; -import java.io.FileInputStream; import java.io.InputStream; class FullRestoreEngineThread implements Runnable { @@ -19,7 +19,7 @@ class FullRestoreEngineThread implements Runnable { // We *do* want this FileInputStream to own the underlying fd, so that // when we are finished with it, it closes this end of the pipe in a way // that signals its other end. - mEngineStream = new FileInputStream(engineSocket.getFileDescriptor(), true); + mEngineStream = new AutoCloseInputStream(engineSocket); // Tell it to be sure to leave the agent instance up after finishing mMustKillAgent = false; } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 6ccdf245b271..961dc159009c 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -22,10 +22,14 @@ import static com.android.internal.util.FunctionalUtils.uncheckExceptions; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkState; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; +import static java.util.concurrent.TimeUnit.MINUTES; + import android.annotation.CheckResult; import android.annotation.Nullable; +import android.app.AppOpsManager; import android.app.PendingIntent; import android.companion.Association; import android.companion.AssociationRequest; @@ -36,6 +40,7 @@ import android.companion.IFindDeviceCallback; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; @@ -55,6 +60,7 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.UserHandle; +import android.os.UserManagerInternal; import android.provider.Settings; import android.provider.SettingsStringUtil.ComponentNameSet; import android.text.BidiFormatter; @@ -71,6 +77,7 @@ import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.PerUser; import com.android.internal.infra.ServiceConnector; import com.android.internal.notification.NotificationAccessConfirmationActivityContract; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.function.pooled.PooledLambda; @@ -112,6 +119,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private static final boolean DEBUG = false; private static final String LOG_TAG = "CompanionDeviceManagerService"; + private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; + private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; + private static final String XML_TAG_ASSOCIATIONS = "associations"; private static final String XML_TAG_ASSOCIATION = "association"; private static final String XML_ATTR_PACKAGE = "package"; @@ -150,7 +160,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } }; - registerPackageMonitor(); } @@ -195,6 +204,36 @@ public class CompanionDeviceManagerService extends SystemService implements Bind if (atmInternal != null) { atmInternal.setCompanionAppPackages(userHandle, companionAppPackages); } + + BackgroundThread.getHandler().sendMessageDelayed( + obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this), + MINUTES.toMillis(10)); + } + + void maybeGrantAutoRevokeExemptions() { + PackageManager pm = getContext().getPackageManager(); + for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { + SharedPreferences pref = getContext().getSharedPreferences( + new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), + Context.MODE_PRIVATE); + if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { + continue; + } + + try { + Set<Association> associations = readAllAssociations(userId); + for (Association a : associations) { + try { + int uid = pm.getPackageUidAsUser(a.companionAppPackage, userId); + exemptFromAutoRevoke(a.companionAppPackage, uid); + } catch (PackageManager.NameNotFoundException e) { + Log.w(LOG_TAG, "Unknown companion package: " + a.companionAppPackage, e); + } + } + } finally { + pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); + } + } } @Override @@ -469,6 +508,21 @@ public class CompanionDeviceManagerService extends SystemService implements Bind packageInfo.applicationInfo.uid, NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND); } + + exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid); + } + + private void exemptFromAutoRevoke(String packageName, int uid) { + try { + mAppOpsManager.setMode( + AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, + uid, + packageName, + AppOpsManager.MODE_IGNORED); + } catch (RemoteException e) { + Log.w(LOG_TAG, + "Error while granting auto revoke exemption for " + packageName, e); + } } private static <T> boolean containsEither(T[] array, T a, T b) { diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 13e5ab451cfc..4a4b7dd7cc29 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -26,6 +26,15 @@ import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECU import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE; import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ACCEPT_DATA_SHARE_REQUEST; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_EMPTY_DATA; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_IOEXCEPTION; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_SERVICE_PIPE_FAIL; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_WRITE_FINISHED; +import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__REJECT_DATA_SHARE_REQUEST; import static com.android.internal.util.SyncResultReceiver.bundleFor; import android.annotation.NonNull; @@ -95,6 +104,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; /** * A service used to observe the contents of the screen. @@ -114,6 +124,14 @@ public final class ContentCaptureManagerService extends private static final int MAX_CONCURRENT_FILE_SHARING_REQUESTS = 10; private static final int DATA_SHARE_BYTE_BUFFER_LENGTH = 1_024; + // Needed to pass checkstyle_hook as names are too long for one line. + private static final int EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST = + CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST; + private static final int EVENT__DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = + CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED; + private static final int EVENT__DATA_SHARE_WRITE_FINISHED = + CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_WRITE_FINISHED; + private final LocalService mLocalService = new LocalService(); @Nullable @@ -657,6 +675,10 @@ public final class ContentCaptureManagerService extends if (mPackagesWithShareRequests.size() >= MAX_CONCURRENT_FILE_SHARING_REQUESTS || mPackagesWithShareRequests.contains(request.getPackageName())) { try { + String serviceName = mServiceNameResolver.getServiceName(userId); + ContentCaptureMetricsLogger.writeServiceEvent( + EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST, + serviceName, request.getPackageName()); clientAdapter.error( ContentCaptureManager.DATA_SHARE_ERROR_CONCURRENT_REQUEST); } catch (RemoteException e) { @@ -920,6 +942,7 @@ public final class ContentCaptureManagerService extends @NonNull private final DataShareRequest mDataShareRequest; @NonNull private final IDataShareWriteAdapter mClientAdapter; @NonNull private final ContentCaptureManagerService mParentService; + @NonNull private final AtomicBoolean mLoggedWriteFinish = new AtomicBoolean(false); DataShareCallbackDelegate(@NonNull DataShareRequest dataShareRequest, @NonNull IDataShareWriteAdapter clientAdapter, @@ -932,9 +955,12 @@ public final class ContentCaptureManagerService extends @Override public void accept(@NonNull IDataShareReadAdapter serviceAdapter) throws RemoteException { Slog.i(TAG, "Data share request accepted by Content Capture service"); + logServiceEvent(CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ACCEPT_DATA_SHARE_REQUEST); Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe(); if (clientPipe == null) { + logServiceEvent( + CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL); mClientAdapter.error(ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN); serviceAdapter.error(ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN); return; @@ -945,6 +971,8 @@ public final class ContentCaptureManagerService extends Pair<ParcelFileDescriptor, ParcelFileDescriptor> servicePipe = createPipe(); if (servicePipe == null) { + logServiceEvent( + CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_SERVICE_PIPE_FAIL); bestEffortCloseFileDescriptors(sourceIn, sinkIn); mClientAdapter.error(ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN); @@ -987,6 +1015,8 @@ public final class ContentCaptureManagerService extends } } catch (IOException e) { Slog.e(TAG, "Failed to pipe client and service streams", e); + logServiceEvent( + CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_IOEXCEPTION); sendErrorSignal(mClientAdapter, serviceAdapter, ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN); @@ -996,6 +1026,10 @@ public final class ContentCaptureManagerService extends .remove(mDataShareRequest.getPackageName()); } if (receivedData) { + if (!mLoggedWriteFinish.get()) { + logServiceEvent(EVENT__DATA_SHARE_WRITE_FINISHED); + mLoggedWriteFinish.set(true); + } try { mClientAdapter.finish(); } catch (RemoteException e) { @@ -1008,6 +1042,8 @@ public final class ContentCaptureManagerService extends } } else { // Client or service may have crashed before sending. + logServiceEvent( + CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_EMPTY_DATA); sendErrorSignal(mClientAdapter, serviceAdapter, ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN); } @@ -1027,6 +1063,7 @@ public final class ContentCaptureManagerService extends @Override public void reject() throws RemoteException { Slog.i(TAG, "Data share request rejected by Content Capture service"); + logServiceEvent(CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__REJECT_DATA_SHARE_REQUEST); mClientAdapter.rejected(); } @@ -1048,11 +1085,16 @@ public final class ContentCaptureManagerService extends && !sourceOut.getFileDescriptor().valid(); if (finishedSuccessfully) { + if (!mLoggedWriteFinish.get()) { + logServiceEvent(EVENT__DATA_SHARE_WRITE_FINISHED); + mLoggedWriteFinish.set(true); + } Slog.i(TAG, "Content capture data sharing session terminated " + "successfully for package '" + mDataShareRequest.getPackageName() + "'"); } else { + logServiceEvent(EVENT__DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED); Slog.i(TAG, "Reached the timeout of Content Capture data sharing session " + "for package '" + mDataShareRequest.getPackageName() @@ -1123,5 +1165,12 @@ public final class ContentCaptureManagerService extends Slog.e(TAG, "Failed to call error() the service operation", e); } } + + private void logServiceEvent(int eventType) { + int userId = UserHandle.getCallingUserId(); + String serviceName = mParentService.mServiceNameResolver.getServiceName(userId); + ContentCaptureMetricsLogger.writeServiceEvent(eventType, serviceName, + mDataShareRequest.getPackageName()); + } } } diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java index 0b9bf3962562..08e6a0550f69 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java @@ -156,6 +156,9 @@ final class RemoteContentCaptureService public void onDataShareRequest(@NonNull DataShareRequest request, @NonNull IDataShareCallback.Stub dataShareCallback) { scheduleAsyncRequest((s) -> s.onDataShared(request, dataShareCallback)); + writeServiceEvent( + FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_DATA_SHARE_REQUEST, + mComponentName, request.getPackageName()); } /** diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 0ab571854c72..36ba610085e1 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1698,6 +1698,12 @@ public class ConnectivityService extends IConnectivityManager.Stub return newNc; } + // Allow VPNs to see ownership of their own VPN networks - not location sensitive. + if (nc.hasTransport(TRANSPORT_VPN)) { + // Owner UIDs already checked above. No need to re-check. + return newNc; + } + Binder.withCleanCallingIdentity( () -> { if (!mLocationPermissionChecker.checkLocationPermission( @@ -6426,7 +6432,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties); final String iface = nai.linkProperties.getInterfaceName(); // For VPN uid interface filtering, old ranges need to be removed before new ranges can - // be added, due to the range being expanded and stored as invidiual UIDs. For example + // be added, due to the range being expanded and stored as individual UIDs. For example // the UIDs might be updated from [0, 99999] to ([0, 10012], [10014, 99999]) which means // prevRanges = [0, 99999] while newRanges = [0, 10012], [10014, 99999]. If prevRanges // were added first and then newRanges got removed later, there would be only one uid diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 1ce3dfe9f3a0..d479f23c5d49 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -52,7 +52,6 @@ import static com.android.internal.util.XmlUtils.readStringAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; -import static com.android.server.storage.StorageUserConnection.REMOTE_TIMEOUT_SECONDS; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -224,6 +223,9 @@ class StorageManagerService extends IStorageManager.Stub private static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY = "persist.sys.vold_app_data_isolation_enabled"; + // How long we wait to reset storage, if we failed to call onMount on the + // external storage service. + public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10; /** * If {@code 1}, enables the isolated storage feature. If {@code -1}, * disables the isolated storage feature. If {@code 0}, uses the default @@ -2202,7 +2204,7 @@ class StorageManagerService extends IStorageManager.Stub } catch (ExternalStorageServiceException e) { Slog.e(TAG, "Failed to mount volume " + vol, e); - int nextResetSeconds = REMOTE_TIMEOUT_SECONDS * 2; + int nextResetSeconds = FAILED_MOUNT_RESET_TIMEOUT_SECONDS; Slog.i(TAG, "Scheduling reset in " + nextResetSeconds + "s"); mHandler.removeMessages(H_RESET); mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET), @@ -3535,6 +3537,13 @@ class StorageManagerService extends IStorageManager.Stub // point final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM); + // When the caller is the app actually hosting external storage, we + // should never attempt to augment the actual storage volume state, + // otherwise we risk confusing it with race conditions as users go + // through various unlocked states + final boolean callerIsMediaStore = UserHandle.isSameApp(Binder.getCallingUid(), + mMediaStoreAuthorityAppId); + final boolean userIsDemo; final boolean userKeyUnlocked; final boolean storagePermission; @@ -3554,6 +3563,7 @@ class StorageManagerService extends IStorageManager.Stub final ArraySet<String> resUuids = new ArraySet<>(); synchronized (mLock) { for (int i = 0; i < mVolumes.size(); i++) { + final String volId = mVolumes.keyAt(i); final VolumeInfo vol = mVolumes.valueAt(i); switch (vol.getType()) { case VolumeInfo.TYPE_PUBLIC: @@ -3578,11 +3588,19 @@ class StorageManagerService extends IStorageManager.Stub if (!match) continue; boolean reportUnmounted = false; - if (!systemUserUnlocked) { + if (callerIsMediaStore) { + // When the caller is the app actually hosting external storage, we + // should never attempt to augment the actual storage volume state, + // otherwise we risk confusing it with race conditions as users go + // through various unlocked states + } else if (!systemUserUnlocked) { reportUnmounted = true; + Slog.w(TAG, "Reporting " + volId + " unmounted due to system locked"); } else if ((vol.getType() == VolumeInfo.TYPE_EMULATED) && !userKeyUnlocked) { reportUnmounted = true; + Slog.w(TAG, "Reporting " + volId + "unmounted due to " + userId + " locked"); } else if (!storagePermission && !realState) { + Slog.w(TAG, "Reporting " + volId + "unmounted due to missing permissions"); reportUnmounted = true; } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 9080bdb44eaf..23bf955ba9a6 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -310,27 +310,30 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private List<Map<Integer, PreciseDataConnectionState>> mPreciseDataConnectionStates = new ArrayList<Map<Integer, PreciseDataConnectionState>>(); - // Nothing here yet, but putting it here in case we want to add more in the future. - static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0; + static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = + PhoneStateListener.LISTEN_REGISTRATION_FAILURE + | PhoneStateListener.LISTEN_BARRING_INFO; static final int ENFORCE_FINE_LOCATION_PERMISSION_MASK = PhoneStateListener.LISTEN_CELL_LOCATION - | PhoneStateListener.LISTEN_CELL_INFO; + | PhoneStateListener.LISTEN_CELL_INFO + | PhoneStateListener.LISTEN_REGISTRATION_FAILURE + | PhoneStateListener.LISTEN_BARRING_INFO; static final int ENFORCE_PHONE_STATE_PERMISSION_MASK = - PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR - | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR - | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST - | PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED; + PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR + | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR + | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST + | PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED; static final int ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK = - PhoneStateListener.LISTEN_PRECISE_CALL_STATE - | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE - | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES - | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED - | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES - | PhoneStateListener.LISTEN_REGISTRATION_FAILURE - | PhoneStateListener.LISTEN_BARRING_INFO; + PhoneStateListener.LISTEN_PRECISE_CALL_STATE + | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE + | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES + | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED + | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES + | PhoneStateListener.LISTEN_REGISTRATION_FAILURE + | PhoneStateListener.LISTEN_BARRING_INFO; static final int READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK = PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL @@ -338,9 +341,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { static final int READ_PRIVILEGED_PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT - | PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED - | PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED - | PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE; + | PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED + | PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED + | PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE; private static final int MSG_USER_SWITCHED = 1; private static final int MSG_UPDATE_DEFAULT_SUB = 2; @@ -1606,7 +1609,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if (r.matchPhoneStateListenerEvent( PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) - && idMatch(r.subId, subId, phoneId)) { + && idMatchWithoutDefaultPhoneCheck(r.subId, subId)) { try { r.callback.onDisplayInfoChanged(telephonyDisplayInfo); } catch (RemoteException ex) { @@ -2726,6 +2729,24 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { Rlog.e(TAG, s); } + /** + * If the registrant specified a subId, then we should only notify it if subIds match. + * If the registrant registered with DEFAULT subId, we should notify only when the related subId + * is default subId (which could be INVALID if there's no default subId). + * + * This should be the correct way to check record ID match. in idMatch the record's phoneId is + * speculated based on subId passed by the registrant so it's not a good reference. + * But to avoid triggering potential regression only replace idMatch with it when an issue with + * idMatch is reported. Eventually this should replace all instances of idMatch. + */ + private boolean idMatchWithoutDefaultPhoneCheck(int subIdInRecord, int subIdToNotify) { + if (subIdInRecord == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { + return (subIdToNotify == mDefaultSubId); + } else { + return (subIdInRecord == subIdToNotify); + } + } + boolean idMatch(int rSubId, int subId, int phoneId) { if(subId < 0) { diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 35936babf3cb..be080e5cce62 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -233,7 +233,7 @@ final class UiModeManagerService extends SystemService { public void onTwilightStateChanged(@Nullable TwilightState state) { synchronized (mLock) { if (mNightMode == UiModeManager.MODE_NIGHT_AUTO && mSystemReady) { - if (mCar) { + if (shouldApplyAutomaticChangesImmediately()) { updateLocked(0, 0); } else { registerScreenOffEventLocked(); @@ -1155,7 +1155,6 @@ final class UiModeManagerService extends SystemService { void updateLocked(int enableFlags, int disableFlags) { String action = null; String oldAction = null; - boolean originalComputedNightMode = mComputedNightMode; if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) { adjustStatusBarCarModeLocked(); oldAction = UiModeManager.ACTION_EXIT_CAR_MODE; @@ -1236,16 +1235,11 @@ final class UiModeManagerService extends SystemService { sendConfigurationAndStartDreamOrDockAppLocked(category); } - // reset overrides if mComputedNightMode changes - if (originalComputedNightMode != mComputedNightMode) { - resetNightModeOverrideLocked(); - } - // keep screen on when charging and in car mode boolean keepScreenOn = mCharging && ((mCarModeEnabled && mCarModeKeepsScreenOn && - (mCarModeEnableFlags & UiModeManager.ENABLE_CAR_MODE_ALLOW_SLEEP) == 0) || - (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn)); + (mCarModeEnableFlags & UiModeManager.ENABLE_CAR_MODE_ALLOW_SLEEP) == 0) || + (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn)); if (keepScreenOn != mWakeLock.isHeld()) { if (keepScreenOn) { mWakeLock.acquire(); @@ -1403,6 +1397,7 @@ final class UiModeManagerService extends SystemService { mComputedNightMode = false; return; } + resetNightModeOverrideLocked(); } private boolean resetNightModeOverrideLocked() { diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index 27ea4716e12d..df3b6880fdfb 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -337,6 +337,7 @@ public class AdbDebuggingManager { class PortListenerImpl implements AdbConnectionPortListener { public void onPortReceived(int port) { + if (DEBUG) Slog.d(TAG, "Received tls port=" + port); Message msg = mHandler.obtainMessage(port > 0 ? AdbDebuggingHandler.MSG_SERVER_CONNECTED : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED); @@ -392,6 +393,7 @@ public class AdbDebuggingManager { mOutputStream = mSocket.getOutputStream(); mInputStream = mSocket.getInputStream(); + mHandler.sendEmptyMessage(AdbDebuggingHandler.MSG_ADBD_SOCKET_CONNECTED); } catch (IOException ioe) { Slog.e(TAG, "Caught an exception opening the socket: " + ioe); closeSocketLocked(); @@ -504,6 +506,7 @@ public class AdbDebuggingManager { } catch (IOException ex) { Slog.e(TAG, "Failed closing socket: " + ex); } + mHandler.sendEmptyMessage(AdbDebuggingHandler.MSG_ADBD_SOCKET_DISCONNECTED); } /** Call to stop listening on the socket and exit the thread. */ @@ -729,6 +732,10 @@ public class AdbDebuggingManager { static final int MSG_SERVER_CONNECTED = 24; // Notifies us the TLS server is disconnected static final int MSG_SERVER_DISCONNECTED = 25; + // Notification when adbd socket successfully connects. + static final int MSG_ADBD_SOCKET_CONNECTED = 26; + // Notification when adbd socket is disconnected. + static final int MSG_ADBD_SOCKET_DISCONNECTED = 27; // === Messages we can send to adbd =========== static final String MSG_DISCONNECT_DEVICE = "DD"; @@ -1170,6 +1177,28 @@ public class AdbDebuggingManager { } break; } + case MSG_ADBD_SOCKET_CONNECTED: { + if (DEBUG) Slog.d(TAG, "adbd socket connected"); + if (mAdbWifiEnabled) { + // In scenarios where adbd is restarted, the tls port may change. + mConnectionPortPoller = + new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); + mConnectionPortPoller.start(); + } + break; + } + case MSG_ADBD_SOCKET_DISCONNECTED: { + if (DEBUG) Slog.d(TAG, "adbd socket disconnected"); + if (mConnectionPortPoller != null) { + mConnectionPortPoller.cancelAndWait(); + mConnectionPortPoller = null; + } + if (mAdbWifiEnabled) { + // In scenarios where adbd is restarted, the tls port may change. + onAdbdWifiServerDisconnected(-1); + } + break; + } } } diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index e1f9a7a150d8..ef81d7159c42 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -241,12 +241,7 @@ public class AdbService extends IAdbManager.Stub { private AdbService(Context context) { mContext = context; mContentResolver = context.getContentResolver(); - - boolean secureAdbEnabled = AdbProperties.secure().orElse(false); - boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt")); - if (secureAdbEnabled && !dataEncrypted) { - mDebuggingManager = new AdbDebuggingManager(context); - } + mDebuggingManager = new AdbDebuggingManager(context); initAdbState(); LocalServices.addService(AdbManagerInternal.class, new AdbManagerInternalImpl()); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 026f147e955c..3e6a17325f5c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -24,8 +24,8 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.REMOVE_TASKS; import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS; +import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS; -import static android.app.ActivityManager.INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL; import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.PROCESS_STATE_TOP; @@ -1671,6 +1671,12 @@ public class ActivityManagerService extends IActivityManager.Stub */ @Nullable ContentCaptureManagerInternal mContentCaptureService; + /** + * Set of {@link ProcessRecord} that have either {@link ProcessRecord#hasTopUi()} or + * {@link ProcessRecord#runningRemoteAnimation} set to {@code true}. + */ + final ArraySet<ProcessRecord> mTopUiOrRunningRemoteAnimApps = new ArraySet<>(); + final class UiHandler extends Handler { public UiHandler() { super(com.android.server.UiThread.get().getLooper(), null, true); @@ -14702,6 +14708,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessesToGc.remove(app); mPendingPssProcesses.remove(app); + mTopUiOrRunningRemoteAnimApps.remove(app); ProcessList.abortNextPssTime(app.procStateMemTracker); // Dismiss any open dialogs. @@ -15781,9 +15788,10 @@ public class ActivityManagerService extends IActivityManager.Stub } if (receivers != null && broadcastWhitelist != null) { for (int i = receivers.size() - 1; i >= 0; i--) { - final int uid = receivers.get(i).activityInfo.applicationInfo.uid; - if (uid >= Process.FIRST_APPLICATION_UID - && Arrays.binarySearch(broadcastWhitelist, UserHandle.getAppId(uid)) < 0) { + final int receiverAppId = UserHandle.getAppId( + receivers.get(i).activityInfo.applicationInfo.uid); + if (receiverAppId >= Process.FIRST_APPLICATION_UID + && Arrays.binarySearch(broadcastWhitelist, receiverAppId) < 0) { receivers.remove(i); } } @@ -16429,9 +16437,9 @@ public class ActivityManagerService extends IActivityManager.Stub // if a uid whitelist was provided, remove anything in the application space that wasn't // in it. for (int i = registeredReceivers.size() - 1; i >= 0; i--) { - final int uid = registeredReceivers.get(i).owningUid; - if (uid >= Process.FIRST_APPLICATION_UID - && Arrays.binarySearch(broadcastWhitelist, UserHandle.getAppId(uid)) < 0) { + final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid); + if (owningAppId >= Process.FIRST_APPLICATION_UID + && Arrays.binarySearch(broadcastWhitelist, owningAppId) < 0) { registeredReceivers.remove(i); } } @@ -16904,8 +16912,9 @@ public class ActivityManagerService extends IActivityManager.Stub "disable hidden API checks"); } + // TODO(b/158750470): remove final boolean mountExtStorageFull = isCallerShell() - && (flags & INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL) != 0; + && (flags & INSTR_FLAG_DISABLE_ISOLATED_STORAGE) != 0; final long origId = Binder.clearCallingIdentity(); // Instrumentation can kill and relaunch even persistent processes @@ -16927,6 +16936,13 @@ public class ActivityManagerService extends IActivityManager.Stub if (!mActiveInstrumentation.contains(activeInstr)) { mActiveInstrumentation.add(activeInstr); } + + if ((flags & INSTR_FLAG_DISABLE_ISOLATED_STORAGE) != 0) { + // Allow OP_NO_ISOLATED_STORAGE app op for the package running instrumentation with + // --no-isolated-storage flag. + mAppOpsService.setMode(AppOpsManager.OP_NO_ISOLATED_STORAGE, ai.uid, + ii.packageName, AppOpsManager.MODE_ALLOWED); + } Binder.restoreCallingIdentity(origId); } @@ -17017,6 +17033,9 @@ public class ActivityManagerService extends IActivityManager.Stub // Can't call out of the system process with a lock held, so post a message. if (instr.mUiAutomationConnection != null) { + // Go back to the default mode of denying OP_NO_ISOLATED_STORAGE app op. + mAppOpsService.setMode(AppOpsManager.OP_NO_ISOLATED_STORAGE, app.uid, + app.info.packageName, AppOpsManager.MODE_ERRORED); mAppOpsService.setAppOpsServiceDelegate(null); getPermissionManagerInternalLocked().setCheckPermissionDelegate(null); mHandler.obtainMessage(SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG, @@ -18490,6 +18509,22 @@ public class ActivityManagerService extends IActivityManager.Stub return proc; } + /** + * @return {@code true} if {@link #mTopUiOrRunningRemoteAnimApps} set contains {@code app} or when there are no apps + * in this list, an false otherwise. + */ + boolean containsTopUiOrRunningRemoteAnimOrEmptyLocked(ProcessRecord app) { + return mTopUiOrRunningRemoteAnimApps.isEmpty() || mTopUiOrRunningRemoteAnimApps.contains(app); + } + + void addTopUiOrRunningRemoteAnim(ProcessRecord app) { + mTopUiOrRunningRemoteAnimApps.add(app); + } + + void removeTopUiOrRunningRemoteAnim(ProcessRecord app) { + mTopUiOrRunningRemoteAnimApps.remove(app); + } + @Override public boolean dumpHeap(String process, int userId, boolean managed, boolean mallocInfo, boolean runGc, String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index da5f48962130..58b0a157e2c2 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -1151,8 +1151,17 @@ public final class OomAdjuster { // is currently showing UI. app.systemNoUi = true; if (app == topApp) { + // If specific system app has set ProcessRecord.mHasTopUi or is running a remote + // animation (ProcessRecord.runningRemoteAnimation), this will prevent topApp + // to use SCHED_GROUP_TOP_APP to ensure process with mHasTopUi will have exclusive + // access to configured cores. + if (mService.containsTopUiOrRunningRemoteAnimOrEmptyLocked(app)) { + app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP); + } else { + app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT); + } app.systemNoUi = false; - app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP); + app.adjType = "pers-top-activity"; } else if (app.hasTopUi()) { // sched group/proc state adjustment is below @@ -1193,10 +1202,20 @@ public final class OomAdjuster { boolean foregroundActivities = false; if (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP && app == topApp) { - // The last app on the list is the foreground app. - adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = ProcessList.SCHED_GROUP_TOP_APP; - app.adjType = "top-activity"; + + // If specific system app has set ProcessRecord.mHasTopUi or is running a remote + // animation (ProcessRecord.runningRemoteAnimation), this will prevent topApp + // to use SCHED_GROUP_TOP_APP to ensure process with mHasTopUi will have exclusive + // access to configured cores. + if (mService.containsTopUiOrRunningRemoteAnimOrEmptyLocked(app)) { + adj = ProcessList.FOREGROUND_APP_ADJ; + schedGroup = ProcessList.SCHED_GROUP_TOP_APP; + app.adjType = "top-activity"; + } else { + adj = ProcessList.FOREGROUND_APP_ADJ; + schedGroup = ProcessList.SCHED_GROUP_DEFAULT; + app.adjType = "top-activity-behind-topui"; + } foregroundActivities = true; procState = PROCESS_STATE_CUR_TOP; if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index c5152c081e70..4c75ab21d6f2 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1268,6 +1268,7 @@ class ProcessRecord implements WindowProcessListener { void setHasTopUi(boolean hasTopUi) { mHasTopUi = hasTopUi; mWindowProcessController.setHasTopUi(hasTopUi); + updateTopUiOrRunningRemoteAnim(); } boolean hasTopUi() { @@ -1518,10 +1519,19 @@ class ProcessRecord implements WindowProcessListener { Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation + " for pid=" + pid); } + updateTopUiOrRunningRemoteAnim(); mService.updateOomAdjLocked(this, true, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); } } + void updateTopUiOrRunningRemoteAnim() { + if (runningRemoteAnimation || hasTopUi()) { + mService.addTopUiOrRunningRemoteAnim(this); + } else { + mService.removeTopUiOrRunningRemoteAnim(this); + } + } + public long getInputDispatchingTimeout() { return mWindowProcessController.getInputDispatchingTimeout(); } diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java index a0349493edbc..a168af5ad842 100644 --- a/services/core/java/com/android/server/am/ProcessStatsService.java +++ b/services/core/java/com/android/server/am/ProcessStatsService.java @@ -1269,12 +1269,12 @@ public final class ProcessStatsService extends IProcessStats.Stub { * Dump proto for the statsd, mainly for testing. */ private void dumpProtoForStatsd(FileDescriptor fd) { - final ProtoOutputStream proto = new ProtoOutputStream(fd); + final ProtoOutputStream[] protos = {new ProtoOutputStream(fd)}; ProcessStats procStats = new ProcessStats(false); getCommittedStatsMerged(0, 0, true, null, procStats); - procStats.dumpAggregatedProtoForStatsd(proto); + procStats.dumpAggregatedProtoForStatsd(protos, 999999 /* max bytes per shard */); - proto.flush(); + protos[0].flush(); } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 6d45abaf0234..36272278e0e4 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -5322,6 +5322,15 @@ public class AudioService extends IAudioService.Stub } private void setVolumeIndexInt(int index, int device, int flags) { + // Reflect mute state of corresponding stream by forcing index to 0 if muted + // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted. + // This allows RX path muting by the audio HAL only when explicitly muted but not when + // index is just set to 0 to repect BT requirements + if (mStreamStates[mPublicStreamType].isFullyMuted()) { + index = 0; + } else if (mPublicStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0) { + index = 1; + } // Set the volume index AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); } diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 8ed864c71625..d7e9499cf8db 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -102,6 +102,13 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { checkCompatChangeReadAndLogPermission(); + return isChangeEnabledInternal(changeId, appInfo); + } + + /** + * Internal version of the above method. Does not perform costly permission check. + */ + public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) { if (mCompatConfig.isChangeEnabled(changeId, appInfo)) { reportChange(changeId, appInfo.uid, ChangeReporter.STATE_ENABLED); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index e654af706fca..1f85d1046523 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1106,7 +1106,8 @@ public class Vpn { NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig(); networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown; - mNetworkCapabilities.setOwnerUid(Binder.getCallingUid()); + mNetworkCapabilities.setOwnerUid(mOwnerUID); + mNetworkCapabilities.setAdministratorUids(new int[] {mOwnerUID}); mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle, mConfig.allowedApplications, mConfig.disallowedApplications)); long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 18adc0ba27ee..d4377e4870a5 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -116,12 +116,20 @@ final class DisplayDeviceInfo { /** * Flag: This flag identifies secondary displays that should show system decorations, such as * status bar, navigation bar, home activity or IME. + * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p> * @hide */ // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 12; /** + * Flag: The display is trusted to show system decorations and receive inputs without users' + * touch. + * @see #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + */ + public static final int FLAG_TRUSTED = 1 << 13; + + /** * Touch attachment: Display does not receive touch. */ public static final int TOUCH_NONE = 0; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index dee6cd02917f..1058000e0b68 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -16,6 +16,7 @@ package com.android.server.display; +import static android.Manifest.permission.ADD_TRUSTED_DISPLAY; import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT; import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; @@ -25,6 +26,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_C import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL; import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL; import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL; @@ -2189,16 +2191,25 @@ public final class DisplayManagerService extends SystemService { } } + if (callingUid == Process.SYSTEM_UID + || checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) { + flags |= VIRTUAL_DISPLAY_FLAG_TRUSTED; + } else { + flags &= ~VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; + } + // Sometimes users can have sensitive information in system decoration windows. An app // could create a virtual display with system decorations support and read the user info // from the surface. // We should only allow adding flag VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS - // to virtual displays that are owned by the system. - if (callingUid != Process.SYSTEM_UID - && (flags & VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) { - if (!checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "createVirtualDisplay()")) { + // to trusted virtual displays. + final int trustedDisplayWithSysDecorFlag = + (VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + | VIRTUAL_DISPLAY_FLAG_TRUSTED); + if ((flags & trustedDisplayWithSysDecorFlag) + == VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + && !checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "createVirtualDisplay()")) { throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission"); - } } final long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 2a65b33461cf..2c08420af42d 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -577,6 +577,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.name = getContext().getResources().getString( com.android.internal.R.string.display_manager_hdmi_display_name); } + // The display is trusted since it is created by system. + mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED; } return mInfo; } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 0261f388f7cb..8556f084a072 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -269,6 +269,9 @@ final class LogicalDisplay { if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) { mBaseDisplayInfo.flags |= Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; } + if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_TRUSTED) != 0) { + mBaseDisplayInfo.flags |= Display.FLAG_TRUSTED; + } Rect maskingInsets = getMaskingInsets(deviceInfo); int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right; int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom; diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index 8fb384070e25..69943e3904ed 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -16,6 +16,8 @@ package com.android.server.display; +import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED; + import android.annotation.Nullable; import android.content.Context; import android.database.ContentObserver; @@ -356,6 +358,8 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mInfo.type = Display.TYPE_OVERLAY; mInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL; mInfo.state = mState; + // The display is trusted since it is created by system. + mInfo.flags |= FLAG_TRUSTED; } return mInfo; } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index ccd88483593a..210d2979c807 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -25,6 +25,9 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTAT import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; + +import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED; import android.content.Context; import android.hardware.display.IVirtualDisplayCallback; @@ -412,6 +415,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { if ((mFlags & VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) { mInfo.flags |= DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; } + if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) { + mInfo.flags |= FLAG_TRUSTED; + } mInfo.type = Display.TYPE_VIRTUAL; mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ? diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index 5584dcf69f50..57323170b327 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -651,6 +651,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { mInfo.address = mAddress; mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight); + // The display is trusted since it is created by system. + mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED; } return mInfo; } diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 769956d797b0..e3eeb6c41d9f 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -47,6 +47,10 @@ import android.service.dreams.IDreamManager; import android.util.Slog; import android.view.Display; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; @@ -77,6 +81,8 @@ public final class DreamManagerService extends SystemService { private final PowerManagerInternal mPowerManagerInternal; private final PowerManager.WakeLock mDozeWakeLock; private final ActivityTaskManagerInternal mAtmInternal; + private final UiEventLogger mUiEventLogger; + private final ComponentName mAmbientDisplayComponent; private Binder mCurrentDreamToken; private ComponentName mCurrentDreamName; @@ -91,6 +97,26 @@ public final class DreamManagerService extends SystemService { private AmbientDisplayConfiguration mDozeConfig; + @VisibleForTesting + public enum DreamManagerEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The screensaver has started.") + DREAM_START(577), + + @UiEvent(doc = "The screensaver has stopped.") + DREAM_STOP(578); + + private final int mId; + + DreamManagerEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + public DreamManagerService(Context context) { super(context); mContext = context; @@ -102,6 +128,9 @@ public final class DreamManagerService extends SystemService { mAtmInternal = getLocalService(ActivityTaskManagerInternal.class); mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG); mDozeConfig = new AmbientDisplayConfiguration(mContext); + mUiEventLogger = new UiEventLoggerImpl(); + AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext); + mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent()); } @Override @@ -388,6 +417,9 @@ public final class DreamManagerService extends SystemService { .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream"); mHandler.post(wakeLock.wrap(() -> { mAtmInternal.notifyDreamStateChanged(true); + if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) { + mUiEventLogger.log(DreamManagerEvent.DREAM_START); + } mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock); })); } @@ -415,6 +447,9 @@ public final class DreamManagerService extends SystemService { } private void cleanupDreamLocked() { + if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) { + mUiEventLogger.log(DreamManagerEvent.DREAM_STOP); + } mCurrentDreamToken = null; mCurrentDreamName = null; mCurrentDreamIsTest = false; diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index b9669c74a6df..87a908c10721 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -3142,7 +3142,7 @@ public class HdmiControlService extends SystemService { return; } - setHdmiCecVolumeControlEnabled(false); + mHdmiCecVolumeControlEnabled = false; // Call the vendor handler before the service is disabled. invokeVendorCommandListenersOnControlStateChanged(false, HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index a498e3867c05..b647a1ab9873 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -207,6 +207,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; static final int MSG_INITIALIZE_IME = 1040; static final int MSG_CREATE_SESSION = 1050; + static final int MSG_REMOVE_IME_SURFACE = 1060; static final int MSG_START_INPUT = 2000; @@ -2352,6 +2353,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow); mStartInputHistory.addEntry(info); + // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user + // implicit visibility (e.g. IME[user=10] -> App[user=0]) thus we do this only for the + // same-user scenarios. + // That said ignoring cross-user scenario will never affect IMEs that do not have + // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case. + if (mSettings.getCurrentUserId() == UserHandle.getUserId(mCurClient.uid)) { + mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(), + null /* intent */, UserHandle.getAppId(mCurMethodUid), mCurClient.uid, true); + } + final SessionState session = mCurClient.curSession; executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO( MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */, @@ -3936,6 +3947,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @Override + public void removeImeSurface() { + mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null); + mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE)); + } + @BinderThread private void notifyUserAction(@NonNull IBinder token) { if (DEBUG) { @@ -4206,6 +4223,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub args.recycle(); return true; } + case MSG_REMOVE_IME_SURFACE: { + try { + if (mEnabledSession != null && mEnabledSession.session != null) { + mEnabledSession.session.removeImeSurface(); + } + } catch (RemoteException e) { + } + return true; + } // --------------------------------------------------------- case MSG_START_INPUT: { diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 2129e9bd34f3..33c78e403ce3 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1347,13 +1347,14 @@ public final class MultiClientInputMethodManagerService { for (WindowInfo windowInfo : clientInfo.mWindowMap.values()) { if (windowInfo.mWindowHandle == targetWindowHandle) { final IBinder targetWindowToken = windowInfo.mWindowToken; - // TODO(yukawa): Report targetWindowToken and targetWindowToken to WMS. if (DEBUG) { Slog.v(TAG, "reportImeWindowTarget" + " clientId=" + clientId + " imeWindowToken=" + imeWindowToken + " targetWindowToken=" + targetWindowToken); } + mIWindowManagerInternal.updateInputMethodTargetWindow( + imeWindowToken, targetWindowToken); } } // not found. @@ -1462,6 +1463,12 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override + public void removeImeSurface() { + reportNotSupported(); + } + + @BinderThread + @Override public boolean showSoftInput( IInputMethodClient client, IBinder token, int flags, ResultReceiver resultReceiver) { @@ -1490,6 +1497,9 @@ public final class MultiClientInputMethodManagerService { case InputMethodClientState.ALREADY_SENT_BIND_RESULT: try { clientInfo.mMSInputMethodSession.showSoftInput(flags, resultReceiver); + + // Forcing WM to show IME on imeTargetWindow + mWindowManagerInternal.showImePostLayout(token); } catch (RemoteException e) { } break; diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java index cbb06b86a291..8a35302d6fd5 100644 --- a/services/core/java/com/android/server/location/SettingsHelper.java +++ b/services/core/java/com/android/server/location/SettingsHelper.java @@ -168,7 +168,7 @@ public class SettingsHelper { * Remove a listener for changes to the location enabled setting. */ public void removeOnLocationEnabledChangedListener(UserSettingChangedListener listener) { - mLocationMode.addListener(listener); + mLocationMode.removeListener(listener); } /** diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 8d4efed8604b..5787f7c48138 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -26,6 +26,7 @@ import android.content.pm.UserInfo; import android.hardware.rebootescrow.IRebootEscrow; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.os.SystemClock; import android.os.UserManager; import android.provider.Settings; @@ -244,6 +245,9 @@ class RebootEscrowManager { } catch (RemoteException e) { Slog.w(TAG, "Could not retrieve escrow data"); return null; + } catch (ServiceSpecificException e) { + Slog.w(TAG, "Got service-specific exception: " + e.errorCode); + return null; } } @@ -335,7 +339,7 @@ class RebootEscrowManager { try { rebootEscrow.storeKey(new byte[32]); - } catch (RemoteException e) { + } catch (RemoteException | ServiceSpecificException e) { Slog.w(TAG, "Could not call RebootEscrow HAL to shred key"); } @@ -373,7 +377,7 @@ class RebootEscrowManager { rebootEscrow.storeKey(escrowKey.getKeyBytes()); armedRebootEscrow = true; Slog.i(TAG, "Reboot escrow key stored with RebootEscrow HAL"); - } catch (RemoteException e) { + } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Failed escrow secret to RebootEscrow HAL", e); } diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index 2461b0ce93a5..4def7db76bc5 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java +++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java @@ -48,9 +48,11 @@ import java.util.Objects; class BluetoothRouteProvider { private static final String TAG = "BTRouteProvider"; + private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_"; private static BluetoothRouteProvider sInstance; @SuppressWarnings("WeakerAccess") /* synthetic access */ + // Maps hardware address to BluetoothRouteInfo final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>(); @SuppressWarnings("WeakerAccess") /* synthetic access */ BluetoothRouteInfo mSelectedRoute = null; @@ -126,9 +128,10 @@ class BluetoothRouteProvider { return; } - BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(routeId); + BluetoothRouteInfo btRouteInfo = findBluetoothRouteWithRouteId(routeId); + if (btRouteInfo == null) { - Slog.w(TAG, "transferTo: unknown route id=" + routeId); + Slog.w(TAG, "transferTo: Unknown route. ID=" + routeId); return; } @@ -179,13 +182,32 @@ class BluetoothRouteProvider { @NonNull List<MediaRoute2Info> getAllBluetoothRoutes() { - ArrayList<MediaRoute2Info> routes = new ArrayList<>(); + List<MediaRoute2Info> routes = new ArrayList<>(); + List<String> routeIds = new ArrayList<>(); + for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) { + // A pair of hearing aid devices or the same hardware address + if (routeIds.contains(btRoute.route.getId())) { + continue; + } routes.add(btRoute.route); + routeIds.add(btRoute.route.getId()); } return routes; } + BluetoothRouteInfo findBluetoothRouteWithRouteId(String routeId) { + if (routeId == null) { + return null; + } + for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) { + if (TextUtils.equals(btRouteInfo.route.getId(), routeId)) { + return btRouteInfo; + } + } + return null; + } + /** * Updates the volume for {@link AudioManager#getDevicesForStream(int) devices}. * @@ -222,8 +244,8 @@ class BluetoothRouteProvider { private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) { BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo(); newBtRoute.btDevice = device; - // Current volume will be set when connected. - // TODO: Is there any BT device which has fixed volume? + + String routeId = device.getAddress(); String deviceName = device.getName(); if (TextUtils.isEmpty(deviceName)) { deviceName = mContext.getResources().getText(R.string.unknownName).toString(); @@ -236,10 +258,13 @@ class BluetoothRouteProvider { if (mHearingAidProfile != null && mHearingAidProfile.getConnectedDevices().contains(device)) { newBtRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true); + // Intentionally assign the same ID for a pair of devices to publish only one of them. + routeId = HEARING_AID_ROUTE_ID_PREFIX + mHearingAidProfile.getHiSyncId(device); type = MediaRoute2Info.TYPE_HEARING_AID; } - newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), deviceName) + // Current volume will be set when connected. + newBtRoute.route = new MediaRoute2Info.Builder(routeId, deviceName) .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO) .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED) .setDescription(mContext.getResources().getText( @@ -247,6 +272,7 @@ class BluetoothRouteProvider { .setType(type) .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) + .setAddress(device.getAddress()) .build(); return newBtRoute; } diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index 58c2707a1f19..9dae1b44117b 100644 --- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -85,6 +85,9 @@ final class MediaButtonReceiverHolder { return null; } ComponentName componentName = ComponentName.unflattenFromString(tokens[0]); + if (componentName == null) { + return null; + } int userId = Integer.parseInt(tokens[1]); // Guess component type if the OS version is updated from the older version. int componentType = (tokens.length == 3) diff --git a/services/core/java/com/android/server/media/MediaKeyDispatcher.java b/services/core/java/com/android/server/media/MediaKeyDispatcher.java index 176ec3f003ea..5933723f01b6 100644 --- a/services/core/java/com/android/server/media/MediaKeyDispatcher.java +++ b/services/core/java/com/android/server/media/MediaKeyDispatcher.java @@ -71,6 +71,9 @@ public abstract class MediaKeyDispatcher { mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_STOP, 0); mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_NEXT, 0); mOverriddenKeyEvents.put(KeyEvent.KEYCODE_MEDIA_PREVIOUS, 0); + mOverriddenKeyEvents.put(KeyEvent.KEYCODE_VOLUME_DOWN, 0); + mOverriddenKeyEvents.put(KeyEvent.KEYCODE_VOLUME_UP, 0); + mOverriddenKeyEvents.put(KeyEvent.KEYCODE_VOLUME_MUTE, 0); } // TODO: Move this method into SessionPolicyProvider.java for better readability. @@ -126,6 +129,9 @@ public abstract class MediaKeyDispatcher { * <li> {@link KeyEvent#KEYCODE_MEDIA_STOP} * <li> {@link KeyEvent#KEYCODE_MEDIA_NEXT} * <li> {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS} + * <li> {@link KeyEvent#KEYCODE_VOLUME_UP} + * <li> {@link KeyEvent#KEYCODE_VOLUME_DOWN} + * <li> {@link KeyEvent#KEYCODE_VOLUME_MUTE} * </ul> * @see {@link KeyEvent#isMediaSessionKey(int)} */ @@ -164,6 +170,9 @@ public abstract class MediaKeyDispatcher { * <li> {@link KeyEvent#KEYCODE_MEDIA_STOP} * <li> {@link KeyEvent#KEYCODE_MEDIA_NEXT} * <li> {@link KeyEvent#KEYCODE_MEDIA_PREVIOUS} + * <li> {@link KeyEvent#KEYCODE_VOLUME_DOWN} + * <li> {@link KeyEvent#KEYCODE_VOLUME_UP} + * <li> {@link KeyEvent#KEYCODE_VOLUME_MUTE} * </ul> * @see {@link KeyEvent#isMediaSessionKey(int)} * @param keyCode diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index c12f89c25a9e..93a27f2d17a9 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -567,7 +567,8 @@ class MediaRouter2ServiceImpl { boolean hasModifyAudioRoutingPermission) { final IBinder binder = router.asBinder(); if (mAllRouterRecords.get(binder) != null) { - Slog.w(TAG, "Same router already exists. packageName=" + packageName); + Slog.w(TAG, "registerRouter2Locked: Same router already exists. packageName=" + + packageName); return; } @@ -597,6 +598,10 @@ class MediaRouter2ServiceImpl { UserRecord userRecord = routerRecord.mUserRecord; userRecord.mRouterRecords.remove(routerRecord); + routerRecord.mUserRecord.mHandler.sendMessage( + obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers, + routerRecord.mUserRecord.mHandler, + routerRecord.mPackageName, /* preferredFeatures=*/ null)); userRecord.mHandler.sendMessage( obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler, userRecord.mHandler)); @@ -612,7 +617,9 @@ class MediaRouter2ServiceImpl { routerRecord.mDiscoveryPreference = discoveryRequest; routerRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers, - routerRecord.mUserRecord.mHandler, routerRecord)); + routerRecord.mUserRecord.mHandler, + routerRecord.mPackageName, + routerRecord.mDiscoveryPreference.getPreferredFeatures())); routerRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler, routerRecord.mUserRecord.mHandler)); @@ -665,8 +672,7 @@ class MediaRouter2ServiceImpl { final RouterRecord routerRecord = mAllRouterRecords.get(binder); if (routerRecord == null) { - Slog.w(TAG, "requestCreateSessionWithRouter2ByManagerRequestLocked: " - + "Ignoring unknown router."); + Slog.w(TAG, "notifySessionHintsForCreatingSessionLocked: Ignoring unknown router."); return; } @@ -789,7 +795,8 @@ class MediaRouter2ServiceImpl { ManagerRecord managerRecord = mAllManagerRecords.get(binder); if (managerRecord != null) { - Slog.w(TAG, "Same manager already exists. packageName=" + packageName); + Slog.w(TAG, "registerManagerLocked: Same manager already exists. packageName=" + + packageName); return; } @@ -854,7 +861,8 @@ class MediaRouter2ServiceImpl { RouterRecord routerRecord = managerRecord.mUserRecord.findRouterRecordLocked(packageName); if (routerRecord == null) { - Slog.w(TAG, "Ignoring session creation for unknown router."); + Slog.w(TAG, "requestCreateSessionWithManagerLocked: Ignoring session creation for " + + "unknown router."); return; } @@ -1231,7 +1239,8 @@ class MediaRouter2ServiceImpl { for (MediaRoute2Info route : currentRoutes) { if (!route.isValid()) { - Slog.w(TAG, "Ignoring invalid route : " + route); + Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : " + + route); continue; } MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId()); @@ -1309,8 +1318,8 @@ class MediaRouter2ServiceImpl { try { routerRecord.mRouter.getSessionHintsForCreatingSession(uniqueRequestId, route); } catch (RemoteException ex) { - Slog.w(TAG, "requestGetSessionHintsOnHandler: " - + "Failed to request. Router probably died."); + Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: " + + "Failed to request. Router probably died.", ex); mSessionCreationRequests.remove(request); notifyRequestFailedToManager(managerRecord.mManager, toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR); @@ -1323,8 +1332,8 @@ class MediaRouter2ServiceImpl { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider == null) { - Slog.w(TAG, "Ignoring session creation request since no provider found for" - + " given route=" + route); + Slog.w(TAG, "requestCreateSessionWithRouter2OnHandler: Ignoring session " + + "creation request since no provider found for given route=" + route); notifySessionCreationFailedToRouter(routerRecord, toOriginalRequestId(uniqueRequestId)); return; @@ -1349,22 +1358,21 @@ class MediaRouter2ServiceImpl { } } if (matchingRequest == null) { - Slog.w(TAG, "requestCreateSessionWithKnownRequestOnHandler: " - + "Ignoring an unknown request."); + Slog.w(TAG, "requestCreateSessionWithManagerOnHandler: " + + "Ignoring an unknown session creation request."); return; } if (!TextUtils.equals(matchingRequest.mRoute.getId(), route.getId())) { - Slog.w(TAG, "requestCreateSessionWithKnownRequestOnHandler: " + Slog.w(TAG, "requestCreateSessionWithManagerOnHandler: " + "The given route is different from the requested route."); return; } final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider == null) { - Slog.w(TAG, "Ignoring session creation request since no provider found for" - + " given route=" + route); - + Slog.w(TAG, "requestCreateSessionWithManagerOnHandler: Ignoring session " + + "creation request since no provider found for given route=" + route); mSessionCreationRequests.remove(matchingRequest); notifyRequestFailedToManager(matchingRequest.mRequestedManagerRecord.mManager, toOriginalRequestId(uniqueRequestId), REASON_ROUTE_NOT_AVAILABLE); @@ -1468,7 +1476,7 @@ class MediaRouter2ServiceImpl { int volume) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider == null) { - Slog.w(TAG, "setRouteVolume: couldn't find provider for route=" + route); + Slog.w(TAG, "setRouteVolumeOnHandler: Couldn't find provider for route=" + route); return; } provider.setRouteVolume(uniqueRequestId, route.getOriginalId(), volume); @@ -1478,8 +1486,8 @@ class MediaRouter2ServiceImpl { @NonNull String uniqueSessionId, int volume) { final MediaRoute2Provider provider = findProvider(getProviderId(uniqueSessionId)); if (provider == null) { - Slog.w(TAG, "setSessionVolume: couldn't find provider for session " - + "id=" + uniqueSessionId); + Slog.w(TAG, "setSessionVolumeOnHandler: Couldn't find provider for session id=" + + uniqueSessionId); return; } provider.setSessionVolume(uniqueRequestId, getOriginalId(uniqueSessionId), volume); @@ -1599,7 +1607,8 @@ class MediaRouter2ServiceImpl { RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId()); if (routerRecord == null) { - Slog.w(TAG, "No matching router found for session=" + sessionInfo); + Slog.w(TAG, "onSessionInfoChangedOnHandler: No matching router found for session=" + + sessionInfo); return; } notifySessionInfoChangedToRouter(routerRecord, sessionInfo); @@ -1612,7 +1621,8 @@ class MediaRouter2ServiceImpl { RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId()); if (routerRecord == null) { - Slog.w(TAG, "No matching router found for session=" + sessionInfo); + Slog.w(TAG, "onSessionReleasedOnHandler: No matching router found for session=" + + sessionInfo); return; } notifySessionReleasedToRouter(routerRecord, sessionInfo); @@ -1789,7 +1799,7 @@ class MediaRouter2ServiceImpl { currentRoutes.addAll(systemProviderInfo.getRoutes()); } else { // This shouldn't happen. - Slog.w(TAG, "notifyRoutesToRouter: System route provider not found."); + Slog.wtf(TAG, "System route provider not found."); } currentSystemSessionInfo = mSystemProvider.getSessionInfos().get(0); } else { @@ -1950,7 +1960,8 @@ class MediaRouter2ServiceImpl { } } - private void notifyPreferredFeaturesChangedToManagers(@NonNull RouterRecord routerRecord) { + private void notifyPreferredFeaturesChangedToManagers(@NonNull String routerPackageName, + @Nullable List<String> preferredFeatures) { MediaRouter2ServiceImpl service = mServiceRef.get(); if (service == null) { return; @@ -1963,8 +1974,7 @@ class MediaRouter2ServiceImpl { } for (IMediaRouter2Manager manager : managers) { try { - manager.notifyPreferredFeaturesChanged(routerRecord.mPackageName, - routerRecord.mDiscoveryPreference.getPreferredFeatures()); + manager.notifyPreferredFeaturesChanged(routerPackageName, preferredFeatures); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify preferred features changed." + " Manager probably died.", ex); diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index bf2cc5e68fac..3337b480d6a8 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -891,8 +891,24 @@ public final class MediaRouterService extends IMediaRouterService.Stub if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); synchronized (mLock) { + boolean wasA2dpOn = mGlobalBluetoothA2dpOn; mActiveBluetoothDevice = btDevice; mGlobalBluetoothA2dpOn = btDevice != null; + if (wasA2dpOn != mGlobalBluetoothA2dpOn) { + UserRecord userRecord = mUserRecords.get(mCurrentUserId); + if (userRecord != null) { + for (ClientRecord cr : userRecord.mClientRecords) { + // mSelectedRouteId will be null for BT and phone speaker. + if (cr.mSelectedRouteId == null) { + try { + cr.mClient.onGlobalA2dpChanged(mGlobalBluetoothA2dpOn); + } catch (RemoteException e) { + // Ignore exception + } + } + } + } + } } } } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index afae20dcf525..96250411f984 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -830,9 +830,6 @@ public class MediaSessionService extends SystemService implements Monitor { private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener; private int mOnVolumeKeyLongPressListenerUid; - private KeyEvent mInitialDownVolumeKeyEvent; - private int mInitialDownVolumeStream; - private boolean mInitialDownMusicOnly; private IOnMediaKeyListener mOnMediaKeyListener; private int mOnMediaKeyListenerUid; @@ -1104,12 +1101,10 @@ public class MediaSessionService extends SystemService implements Monitor { "android.media.AudioService.WAKELOCK_ACQUIRED"; private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number - private KeyEvent mTrackingFirstDownKeyEvent = null; - private boolean mIsLongPressing = false; - private Runnable mLongPressTimeoutRunnable = null; - private int mMultiTapCount = 0; - private int mMultiTapKeyCode = 0; - private Runnable mMultiTapTimeoutRunnable = null; + private KeyEventHandler mMediaKeyEventHandler = + new KeyEventHandler(KeyEventHandler.KEY_TYPE_MEDIA); + private KeyEventHandler mVolumeKeyEventHandler = + new KeyEventHandler(KeyEventHandler.KEY_TYPE_VOLUME); @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, @@ -1387,8 +1382,8 @@ public class MediaSessionService extends SystemService implements Monitor { dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, needWakeLock); } else { - handleKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, - needWakeLock); + mMediaKeyEventHandler.handleMediaKeyEventLocked(packageName, pid, uid, + asSystemService, keyEvent, needWakeLock); } } } finally { @@ -1710,53 +1705,14 @@ public class MediaSessionService extends SystemService implements Monitor { try { synchronized (mLock) { - if (isGlobalPriorityActiveLocked() - || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) { + if (isGlobalPriorityActiveLocked()) { dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, asSystemService, keyEvent, stream, musicOnly); } else { // TODO: Consider the case when both volume up and down keys are pressed // at the same time. - if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { - if (keyEvent.getRepeatCount() == 0) { - // Keeps the copy of the KeyEvent because it can be reused. - mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = - KeyEvent.obtain(keyEvent); - mCurrentFullUserRecord.mInitialDownVolumeStream = stream; - mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly; - mHandler.sendMessageDelayed( - mHandler.obtainMessage( - MessageHandler.MSG_VOLUME_INITIAL_DOWN, - mCurrentFullUserRecord.mFullUserId, 0), - LONG_PRESS_TIMEOUT); - } - if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) { - mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN); - if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) { - dispatchVolumeKeyLongPressLocked( - mCurrentFullUserRecord.mInitialDownVolumeKeyEvent); - // Mark that the key is already handled. - mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null; - } - dispatchVolumeKeyLongPressLocked(keyEvent); - } - } else { // if up - mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN); - if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null - && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent - .getDownTime() == keyEvent.getDownTime()) { - // Short-press. Should change volume. - dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, - asSystemService, - mCurrentFullUserRecord.mInitialDownVolumeKeyEvent, - mCurrentFullUserRecord.mInitialDownVolumeStream, - mCurrentFullUserRecord.mInitialDownMusicOnly); - dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, - asSystemService, keyEvent, stream, musicOnly); - } else { - dispatchVolumeKeyLongPressLocked(keyEvent); - } - } + mVolumeKeyEventHandler.handleVolumeKeyEventLocked(packageName, pid, uid, + asSystemService, keyEvent, opPackageName, stream, musicOnly); } } } finally { @@ -2136,266 +2092,6 @@ public class MediaSessionService extends SystemService implements Monitor { } } - // A long press is determined by: - // 1) A KeyEvent.ACTION_DOWN KeyEvent and repeat count of 0, followed by - // 2) A KeyEvent.ACTION_DOWN KeyEvent with the same key code, a repeat count of 1, and - // FLAG_LONG_PRESS received within ViewConfiguration.getLongPressTimeout(). - // A tap is determined by: - // 1) A KeyEvent.ACTION_DOWN KeyEvent followed by - // 2) A KeyEvent.ACTION_UP KeyEvent with the same key code. - private void handleKeyEventLocked(String packageName, int pid, int uid, - boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) { - if (keyEvent.isCanceled()) { - return; - } - - int overriddenKeyEvents = (mCustomMediaKeyDispatcher == null) ? 0 - : mCustomMediaKeyDispatcher.getOverriddenKeyEvents().get(keyEvent.getKeyCode()); - cancelTrackingIfNeeded(packageName, pid, uid, asSystemService, keyEvent, needWakeLock, - overriddenKeyEvents); - if (!needTracking(keyEvent, overriddenKeyEvents)) { - dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, - needWakeLock); - return; - } - - if (isFirstDownKeyEvent(keyEvent)) { - mTrackingFirstDownKeyEvent = keyEvent; - mIsLongPressing = false; - return; - } - - // Long press is always overridden here, otherwise the key event would have been already - // handled - if (isFirstLongPressKeyEvent(keyEvent)) { - mIsLongPressing = true; - } - if (mIsLongPressing) { - handleLongPressLocked(keyEvent, needWakeLock, overriddenKeyEvents); - return; - } - - if (keyEvent.getAction() == KeyEvent.ACTION_UP) { - mTrackingFirstDownKeyEvent = null; - if (shouldTrackForMultipleTapsLocked(overriddenKeyEvents)) { - if (mMultiTapCount == 0) { - mMultiTapTimeoutRunnable = createSingleTapRunnable(packageName, pid, uid, - asSystemService, keyEvent, needWakeLock, - isSingleTapOverridden(overriddenKeyEvents)); - if (isSingleTapOverridden(overriddenKeyEvents) - && !isDoubleTapOverridden(overriddenKeyEvents) - && !isTripleTapOverridden(overriddenKeyEvents)) { - mMultiTapTimeoutRunnable.run(); - } else { - mHandler.postDelayed(mMultiTapTimeoutRunnable, - MULTI_TAP_TIMEOUT); - mMultiTapCount = 1; - mMultiTapKeyCode = keyEvent.getKeyCode(); - } - } else if (mMultiTapCount == 1) { - mHandler.removeCallbacks(mMultiTapTimeoutRunnable); - mMultiTapTimeoutRunnable = createDoubleTapRunnable(packageName, pid, uid, - asSystemService, keyEvent, needWakeLock, - isSingleTapOverridden(overriddenKeyEvents), - isDoubleTapOverridden(overriddenKeyEvents)); - if (isTripleTapOverridden(overriddenKeyEvents)) { - mHandler.postDelayed(mMultiTapTimeoutRunnable, MULTI_TAP_TIMEOUT); - mMultiTapCount = 2; - } else { - mMultiTapTimeoutRunnable.run(); - } - } else if (mMultiTapCount == 2) { - mHandler.removeCallbacks(mMultiTapTimeoutRunnable); - onTripleTap(keyEvent); - } - } else { - dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, - keyEvent, needWakeLock); - } - } - } - - private boolean shouldTrackForMultipleTapsLocked(int overriddenKeyEvents) { - return isSingleTapOverridden(overriddenKeyEvents) - || isDoubleTapOverridden(overriddenKeyEvents) - || isTripleTapOverridden(overriddenKeyEvents); - } - - private void cancelTrackingIfNeeded(String packageName, int pid, int uid, - boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, - int overriddenKeyEvents) { - if (mTrackingFirstDownKeyEvent == null && mMultiTapTimeoutRunnable == null) { - return; - } - - if (isFirstDownKeyEvent(keyEvent)) { - if (mLongPressTimeoutRunnable != null) { - mHandler.removeCallbacks(mLongPressTimeoutRunnable); - mLongPressTimeoutRunnable.run(); - } - if (mMultiTapTimeoutRunnable != null && keyEvent.getKeyCode() != mMultiTapKeyCode) { - runExistingMultiTapRunnableLocked(); - } - resetLongPressTracking(); - return; - } - - if (mTrackingFirstDownKeyEvent != null - && mTrackingFirstDownKeyEvent.getDownTime() == keyEvent.getDownTime() - && mTrackingFirstDownKeyEvent.getKeyCode() == keyEvent.getKeyCode() - && keyEvent.getAction() == KeyEvent.ACTION_DOWN) { - if (isFirstLongPressKeyEvent(keyEvent)) { - if (mMultiTapTimeoutRunnable != null) { - runExistingMultiTapRunnableLocked(); - } - if ((overriddenKeyEvents & KEY_EVENT_LONG_PRESS) == 0 - && !isVoiceKey(keyEvent.getKeyCode())) { - dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, - mTrackingFirstDownKeyEvent, needWakeLock); - mTrackingFirstDownKeyEvent = null; - } - } else if (keyEvent.getRepeatCount() > 1 && !mIsLongPressing) { - resetLongPressTracking(); - } - } - } - - private boolean needTracking(KeyEvent keyEvent, int overriddenKeyEvents) { - if (!isFirstDownKeyEvent(keyEvent)) { - if (mTrackingFirstDownKeyEvent == null) { - return false; - } else if (mTrackingFirstDownKeyEvent.getDownTime() != keyEvent.getDownTime() - || mTrackingFirstDownKeyEvent.getKeyCode() != keyEvent.getKeyCode()) { - return false; - } - } - if (overriddenKeyEvents == 0 && !isVoiceKey(keyEvent.getKeyCode())) { - return false; - } - return true; - } - - private void runExistingMultiTapRunnableLocked() { - mHandler.removeCallbacks(mMultiTapTimeoutRunnable); - mMultiTapTimeoutRunnable.run(); - } - - private void resetMultiTapTrackingLocked() { - mMultiTapCount = 0; - mMultiTapTimeoutRunnable = null; - mMultiTapKeyCode = 0; - } - - private void handleLongPressLocked(KeyEvent keyEvent, boolean needWakeLock, - int overriddenKeyEvents) { - if (mCustomMediaKeyDispatcher != null - && isLongPressOverridden(overriddenKeyEvents)) { - mCustomMediaKeyDispatcher.onLongPress(keyEvent); - - if (mLongPressTimeoutRunnable != null) { - mHandler.removeCallbacks(mLongPressTimeoutRunnable); - } - if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { - if (mLongPressTimeoutRunnable == null) { - mLongPressTimeoutRunnable = createLongPressTimeoutRunnable(keyEvent); - } - mHandler.postDelayed(mLongPressTimeoutRunnable, LONG_PRESS_TIMEOUT); - } else { - resetLongPressTracking(); - } - } else if (isFirstLongPressKeyEvent(keyEvent) && isVoiceKey(keyEvent.getKeyCode())) { - // Default implementation - startVoiceInput(needWakeLock); - resetLongPressTracking(); - } - } - - private Runnable createLongPressTimeoutRunnable(KeyEvent keyEvent) { - return new Runnable() { - @Override - public void run() { - if (mCustomMediaKeyDispatcher != null) { - mCustomMediaKeyDispatcher.onLongPress(createCanceledKeyEvent(keyEvent)); - } - resetLongPressTracking(); - } - }; - } - - private void resetLongPressTracking() { - mTrackingFirstDownKeyEvent = null; - mIsLongPressing = false; - mLongPressTimeoutRunnable = null; - } - - private KeyEvent createCanceledKeyEvent(KeyEvent keyEvent) { - KeyEvent upEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_UP); - return KeyEvent.changeTimeRepeat(upEvent, System.currentTimeMillis(), 0, - KeyEvent.FLAG_CANCELED); - } - - private boolean isFirstLongPressKeyEvent(KeyEvent keyEvent) { - return ((keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) - && keyEvent.getRepeatCount() == 1; - } - - private boolean isFirstDownKeyEvent(KeyEvent keyEvent) { - return keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.getRepeatCount() == 0; - } - - private void dispatchDownAndUpKeyEventsLocked(String packageName, int pid, int uid, - boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) { - KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN); - dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, - downEvent, needWakeLock); - dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, - keyEvent, needWakeLock); - } - - Runnable createSingleTapRunnable(String packageName, int pid, int uid, - boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, - boolean overridden) { - return new Runnable() { - @Override - public void run() { - resetMultiTapTrackingLocked(); - if (overridden) { - mCustomMediaKeyDispatcher.onSingleTap(keyEvent); - } else { - dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, - keyEvent, needWakeLock); - } - } - }; - }; - - Runnable createDoubleTapRunnable(String packageName, int pid, int uid, - boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, - boolean singleTapOverridden, boolean doubleTapOverridden) { - return new Runnable() { - @Override - public void run() { - resetMultiTapTrackingLocked(); - if (doubleTapOverridden) { - mCustomMediaKeyDispatcher.onDoubleTap(keyEvent); - } else if (singleTapOverridden) { - mCustomMediaKeyDispatcher.onSingleTap(keyEvent); - mCustomMediaKeyDispatcher.onSingleTap(keyEvent); - } else { - dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, - keyEvent, needWakeLock); - dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, - keyEvent, needWakeLock); - } - } - }; - }; - - private void onTripleTap(KeyEvent keyEvent) { - resetMultiTapTrackingLocked(); - mCustomMediaKeyDispatcher.onTripleTap(keyEvent); - } - private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid, boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) { if (mCurrentFullUserRecord.getMediaButtonSessionLocked() @@ -2579,8 +2275,8 @@ public class MediaSessionService extends SystemService implements Monitor { dispatchMediaKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService, mKeyEvent, mNeedWakeLock); } else { - handleKeyEventLocked(mPackageName, mPid, mUid, mAsSystemService, - mKeyEvent, mNeedWakeLock); + mMediaKeyEventHandler.handleMediaKeyEventLocked(mPackageName, mPid, mUid, + mAsSystemService, mKeyEvent, mNeedWakeLock); } } } @@ -2655,12 +2351,338 @@ public class MediaSessionService extends SystemService implements Monitor { onReceiveResult(resultCode, null); } }; + + // A long press is determined by: + // 1) A KeyEvent.ACTION_DOWN KeyEvent and repeat count of 0, followed by + // 2) A KeyEvent.ACTION_DOWN KeyEvent with the same key code, a repeat count of 1, and + // FLAG_LONG_PRESS received within ViewConfiguration.getLongPressTimeout(). + // A tap is determined by: + // 1) A KeyEvent.ACTION_DOWN KeyEvent followed by + // 2) A KeyEvent.ACTION_UP KeyEvent with the same key code. + class KeyEventHandler { + private static final int KEY_TYPE_MEDIA = 0; + private static final int KEY_TYPE_VOLUME = 1; + + private KeyEvent mTrackingFirstDownKeyEvent; + private boolean mIsLongPressing; + private Runnable mLongPressTimeoutRunnable; + private int mMultiTapCount; + private Runnable mMultiTapTimeoutRunnable; + private int mMultiTapKeyCode; + private int mKeyType; + + KeyEventHandler(int keyType) { + mKeyType = keyType; + } + + void handleMediaKeyEventLocked(String packageName, int pid, int uid, + boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) { + handleKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, needWakeLock, + null, 0, false); + } + + void handleVolumeKeyEventLocked(String packageName, int pid, int uid, + boolean asSystemService, KeyEvent keyEvent, String opPackageName, int stream, + boolean musicOnly) { + handleKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, false, + opPackageName, stream, musicOnly); + } + + void handleKeyEventLocked(String packageName, int pid, int uid, + boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, + String opPackageName, int stream, boolean musicOnly) { + if (keyEvent.isCanceled()) { + return; + } + + int overriddenKeyEvents = (mCustomMediaKeyDispatcher == null) ? 0 + : mCustomMediaKeyDispatcher.getOverriddenKeyEvents() + .get(keyEvent.getKeyCode()); + cancelTrackingIfNeeded(packageName, pid, uid, asSystemService, keyEvent, + needWakeLock, opPackageName, stream, musicOnly, overriddenKeyEvents); + if (!needTracking(keyEvent, overriddenKeyEvents)) { + if (mKeyType == KEY_TYPE_VOLUME) { + dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, + asSystemService, keyEvent, stream, musicOnly); + } else { + dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, + keyEvent, needWakeLock); + } + return; + } + + if (isFirstDownKeyEvent(keyEvent)) { + mTrackingFirstDownKeyEvent = keyEvent; + mIsLongPressing = false; + return; + } + + // Long press is always overridden here, otherwise the key event would have been + // already handled + if (isFirstLongPressKeyEvent(keyEvent)) { + mIsLongPressing = true; + } + if (mIsLongPressing) { + handleLongPressLocked(keyEvent, needWakeLock, overriddenKeyEvents); + return; + } + + if (keyEvent.getAction() == KeyEvent.ACTION_UP) { + mTrackingFirstDownKeyEvent = null; + if (shouldTrackForMultipleTapsLocked(overriddenKeyEvents)) { + if (mMultiTapCount == 0) { + mMultiTapTimeoutRunnable = createSingleTapRunnable(packageName, pid, + uid, asSystemService, keyEvent, needWakeLock, + opPackageName, stream, musicOnly, + isSingleTapOverridden(overriddenKeyEvents)); + if (isSingleTapOverridden(overriddenKeyEvents) + && !isDoubleTapOverridden(overriddenKeyEvents) + && !isTripleTapOverridden(overriddenKeyEvents)) { + mMultiTapTimeoutRunnable.run(); + } else { + mHandler.postDelayed(mMultiTapTimeoutRunnable, + MULTI_TAP_TIMEOUT); + mMultiTapCount = 1; + mMultiTapKeyCode = keyEvent.getKeyCode(); + } + } else if (mMultiTapCount == 1) { + mHandler.removeCallbacks(mMultiTapTimeoutRunnable); + mMultiTapTimeoutRunnable = createDoubleTapRunnable(packageName, pid, + uid, asSystemService, keyEvent, needWakeLock, opPackageName, + stream, musicOnly, isSingleTapOverridden(overriddenKeyEvents), + isDoubleTapOverridden(overriddenKeyEvents)); + if (isTripleTapOverridden(overriddenKeyEvents)) { + mHandler.postDelayed(mMultiTapTimeoutRunnable, MULTI_TAP_TIMEOUT); + mMultiTapCount = 2; + } else { + mMultiTapTimeoutRunnable.run(); + } + } else if (mMultiTapCount == 2) { + mHandler.removeCallbacks(mMultiTapTimeoutRunnable); + onTripleTap(keyEvent); + } + } else { + dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, + keyEvent, needWakeLock, opPackageName, stream, musicOnly); + } + } + } + + private boolean shouldTrackForMultipleTapsLocked(int overriddenKeyEvents) { + return isSingleTapOverridden(overriddenKeyEvents) + || isDoubleTapOverridden(overriddenKeyEvents) + || isTripleTapOverridden(overriddenKeyEvents); + } + + private void cancelTrackingIfNeeded(String packageName, int pid, int uid, + boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, + String opPackageName, int stream, boolean musicOnly, int overriddenKeyEvents) { + if (mTrackingFirstDownKeyEvent == null && mMultiTapTimeoutRunnable == null) { + return; + } + + if (isFirstDownKeyEvent(keyEvent)) { + if (mLongPressTimeoutRunnable != null) { + mHandler.removeCallbacks(mLongPressTimeoutRunnable); + mLongPressTimeoutRunnable.run(); + } + if (mMultiTapTimeoutRunnable != null + && keyEvent.getKeyCode() != mMultiTapKeyCode) { + runExistingMultiTapRunnableLocked(); + } + resetLongPressTracking(); + return; + } + + if (mTrackingFirstDownKeyEvent != null + && mTrackingFirstDownKeyEvent.getDownTime() == keyEvent.getDownTime() + && mTrackingFirstDownKeyEvent.getKeyCode() == keyEvent.getKeyCode() + && keyEvent.getAction() == KeyEvent.ACTION_DOWN) { + if (isFirstLongPressKeyEvent(keyEvent)) { + if (mMultiTapTimeoutRunnable != null) { + runExistingMultiTapRunnableLocked(); + } + if ((overriddenKeyEvents & KEY_EVENT_LONG_PRESS) == 0) { + if (mKeyType == KEY_TYPE_VOLUME) { + if (mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) { + dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, + uid, asSystemService, keyEvent, stream, musicOnly); + mTrackingFirstDownKeyEvent = null; + } + } else if (!isVoiceKey(keyEvent.getKeyCode())) { + dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, + keyEvent, needWakeLock); + mTrackingFirstDownKeyEvent = null; + } + } + } else if (keyEvent.getRepeatCount() > 1 && !mIsLongPressing) { + resetLongPressTracking(); + } + } + } + + private boolean needTracking(KeyEvent keyEvent, int overriddenKeyEvents) { + if (!isFirstDownKeyEvent(keyEvent)) { + if (mTrackingFirstDownKeyEvent == null) { + return false; + } else if (mTrackingFirstDownKeyEvent.getDownTime() != keyEvent.getDownTime() + || mTrackingFirstDownKeyEvent.getKeyCode() != keyEvent.getKeyCode()) { + return false; + } + } + if (overriddenKeyEvents == 0) { + if (mKeyType == KEY_TYPE_VOLUME) { + if (mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) { + return false; + } + } else if (!isVoiceKey(keyEvent.getKeyCode())) { + return false; + } + } + return true; + } + + private void runExistingMultiTapRunnableLocked() { + mHandler.removeCallbacks(mMultiTapTimeoutRunnable); + mMultiTapTimeoutRunnable.run(); + } + + private void resetMultiTapTrackingLocked() { + mMultiTapCount = 0; + mMultiTapTimeoutRunnable = null; + mMultiTapKeyCode = 0; + } + + private void handleLongPressLocked(KeyEvent keyEvent, boolean needWakeLock, + int overriddenKeyEvents) { + if (mCustomMediaKeyDispatcher != null + && isLongPressOverridden(overriddenKeyEvents)) { + mCustomMediaKeyDispatcher.onLongPress(keyEvent); + + if (mLongPressTimeoutRunnable != null) { + mHandler.removeCallbacks(mLongPressTimeoutRunnable); + } + if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) { + if (mLongPressTimeoutRunnable == null) { + mLongPressTimeoutRunnable = createLongPressTimeoutRunnable(keyEvent); + } + mHandler.postDelayed(mLongPressTimeoutRunnable, LONG_PRESS_TIMEOUT); + } else { + resetLongPressTracking(); + } + } else { + if (mKeyType == KEY_TYPE_VOLUME) { + dispatchVolumeKeyLongPressLocked(keyEvent); + } else if (isFirstLongPressKeyEvent(keyEvent) + && isVoiceKey(keyEvent.getKeyCode())) { + // Default implementation + startVoiceInput(needWakeLock); + resetLongPressTracking(); + } + } + } + + private Runnable createLongPressTimeoutRunnable(KeyEvent keyEvent) { + return new Runnable() { + @Override + public void run() { + if (mCustomMediaKeyDispatcher != null) { + mCustomMediaKeyDispatcher.onLongPress(createCanceledKeyEvent(keyEvent)); + } + resetLongPressTracking(); + } + }; + } + + private void resetLongPressTracking() { + mTrackingFirstDownKeyEvent = null; + mIsLongPressing = false; + mLongPressTimeoutRunnable = null; + } + + private KeyEvent createCanceledKeyEvent(KeyEvent keyEvent) { + KeyEvent upEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_UP); + return KeyEvent.changeTimeRepeat(upEvent, System.currentTimeMillis(), 0, + KeyEvent.FLAG_CANCELED); + } + + private boolean isFirstLongPressKeyEvent(KeyEvent keyEvent) { + return ((keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) + && keyEvent.getRepeatCount() == 1; + } + + private boolean isFirstDownKeyEvent(KeyEvent keyEvent) { + return keyEvent.getAction() == KeyEvent.ACTION_DOWN + && keyEvent.getRepeatCount() == 0; + } + + private void dispatchDownAndUpKeyEventsLocked(String packageName, int pid, int uid, + boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, + String opPackageName, int stream, boolean musicOnly) { + KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN); + if (mKeyType == KEY_TYPE_VOLUME) { + dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, + asSystemService, downEvent, stream, musicOnly); + dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, + asSystemService, keyEvent, stream, musicOnly); + } else { + dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, downEvent, + needWakeLock); + dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, + needWakeLock); + } + } + + Runnable createSingleTapRunnable(String packageName, int pid, int uid, + boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, + String opPackageName, int stream, boolean musicOnly, boolean overridden) { + return new Runnable() { + @Override + public void run() { + resetMultiTapTrackingLocked(); + if (overridden) { + mCustomMediaKeyDispatcher.onSingleTap(keyEvent); + } else { + dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, + keyEvent, needWakeLock, opPackageName, stream, musicOnly); + } + } + }; + }; + + Runnable createDoubleTapRunnable(String packageName, int pid, int uid, + boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock, + String opPackageName, int stream, boolean musicOnly, + boolean singleTapOverridden, boolean doubleTapOverridden) { + return new Runnable() { + @Override + public void run() { + resetMultiTapTrackingLocked(); + if (doubleTapOverridden) { + mCustomMediaKeyDispatcher.onDoubleTap(keyEvent); + } else if (singleTapOverridden) { + mCustomMediaKeyDispatcher.onSingleTap(keyEvent); + mCustomMediaKeyDispatcher.onSingleTap(keyEvent); + } else { + dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, + keyEvent, needWakeLock, opPackageName, stream, musicOnly); + dispatchDownAndUpKeyEventsLocked(packageName, pid, uid, asSystemService, + keyEvent, needWakeLock, opPackageName, stream, musicOnly); + } + } + }; + }; + + private void onTripleTap(KeyEvent keyEvent) { + resetMultiTapTrackingLocked(); + mCustomMediaKeyDispatcher.onTripleTap(keyEvent); + } + } } final class MessageHandler extends Handler { private static final int MSG_SESSIONS_1_CHANGED = 1; private static final int MSG_SESSIONS_2_CHANGED = 2; - private static final int MSG_VOLUME_INITIAL_DOWN = 3; private final SparseArray<Integer> mIntegerCache = new SparseArray<>(); @Override @@ -2672,16 +2694,6 @@ public class MediaSessionService extends SystemService implements Monitor { case MSG_SESSIONS_2_CHANGED: pushSession2Changed((int) msg.obj); break; - case MSG_VOLUME_INITIAL_DOWN: - synchronized (mLock) { - FullUserRecord user = mUserRecords.get((int) msg.arg1); - if (user != null && user.mInitialDownVolumeKeyEvent != null) { - dispatchVolumeKeyLongPressLocked(user.mInitialDownVolumeKeyEvent); - // Mark that the key is already handled. - user.mInitialDownVolumeKeyEvent = null; - } - } - break; } } diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java index 0bdf3f22ee9a..a0ab5eae7315 100644 --- a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java +++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java @@ -28,6 +28,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.CollectionUtils; import java.util.ArrayList; @@ -38,8 +39,6 @@ import java.util.concurrent.Executor; /** * Helper class that watches for events that are triggered per subscription. */ -// TODO (b/152176562): Write tests to verify subscription changes generate corresponding -// register/unregister calls. public class NetworkStatsSubscriptionsMonitor extends SubscriptionManager.OnSubscriptionsChangedListener { @@ -207,5 +206,10 @@ public class NetworkStatsSubscriptionsMonitor extends mLastCollapsedRatType = collapsedRatType; mMonitor.mDelegate.onCollapsedRatTypeChanged(mSubscriberId, mLastCollapsedRatType); } + + @VisibleForTesting + public int getSubId() { + return mSubId; + } } } diff --git a/services/core/java/com/android/server/notification/CalendarTracker.java b/services/core/java/com/android/server/notification/CalendarTracker.java index 3829b6580c59..cfcf6ebf9540 100644 --- a/services/core/java/com/android/server/notification/CalendarTracker.java +++ b/services/core/java/com/android/server/notification/CalendarTracker.java @@ -21,6 +21,7 @@ import android.content.ContentUris; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; +import android.database.sqlite.SQLiteException; import android.net.Uri; import android.provider.CalendarContract.Attendees; import android.provider.CalendarContract.Calendars; @@ -102,6 +103,8 @@ public class CalendarTracker { while (cursor != null && cursor.moveToNext()) { rt.add(cursor.getLong(0)); } + } catch (SQLiteException e) { + Slog.w(TAG, "error querying calendar content provider", e); } finally { if (cursor != null) { cursor.close(); @@ -118,11 +121,12 @@ public class CalendarTracker { ContentUris.appendId(uriBuilder, time); ContentUris.appendId(uriBuilder, time + EVENT_CHECK_LOOKAHEAD); final Uri uri = uriBuilder.build(); - final Cursor cursor = mUserContext.getContentResolver().query(uri, INSTANCE_PROJECTION, - null, null, INSTANCE_ORDER_BY); + Cursor cursor = null; final CheckEventResult result = new CheckEventResult(); result.recheckAt = time + EVENT_CHECK_LOOKAHEAD; try { + cursor = mUserContext.getContentResolver().query(uri, INSTANCE_PROJECTION, + null, null, INSTANCE_ORDER_BY); final ArraySet<Long> calendars = getCalendarsWithAccess(); while (cursor != null && cursor.moveToNext()) { final long begin = cursor.getLong(0); @@ -183,9 +187,10 @@ public class CalendarTracker { selection = null; selectionArgs = null; } - final Cursor cursor = mUserContext.getContentResolver().query(Attendees.CONTENT_URI, - ATTENDEE_PROJECTION, selection, selectionArgs, null); + Cursor cursor = null; try { + cursor = mUserContext.getContentResolver().query(Attendees.CONTENT_URI, + ATTENDEE_PROJECTION, selection, selectionArgs, null); if (cursor == null || cursor.getCount() == 0) { if (DEBUG) Log.d(TAG, "No attendees found"); return true; @@ -205,6 +210,9 @@ public class CalendarTracker { rt |= eventMeets; } return rt; + } catch (SQLiteException e) { + Slog.w(TAG, "error querying attendees content provider", e); + return false; } finally { if (cursor != null) { cursor.close(); diff --git a/services/core/java/com/android/server/notification/NotificationChannelLogger.java b/services/core/java/com/android/server/notification/NotificationChannelLogger.java index a7b18778f868..5c127c31d6c2 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelLogger.java +++ b/services/core/java/com/android/server/notification/NotificationChannelLogger.java @@ -99,6 +99,16 @@ public interface NotificationChannelLogger { } /** + * Log blocking or unblocking of the entire app's notifications. + * @param uid UID of the app. + * @param pkg Package name of the app. + * @param enabled If true, notifications are now allowed. + */ + default void logAppNotificationsAllowed(int uid, String pkg, boolean enabled) { + logAppEvent(NotificationChannelEvent.getBlocked(enabled), uid, pkg); + } + + /** * Low-level interface for logging events, to be implemented. * @param event Event to log. * @param channel Notification channel. @@ -124,6 +134,13 @@ public interface NotificationChannelLogger { boolean wasBlocked); /** + * Low-level interface for logging app-as-a-whole events, to be implemented. + * @param uid UID of app. + * @param pkg Package of app. + */ + void logAppEvent(@NonNull NotificationChannelEvent event, int uid, String pkg); + + /** * The UiEvent enums that this class can log. */ enum NotificationChannelEvent implements UiEventLogger.UiEventEnum { @@ -144,8 +161,11 @@ public interface NotificationChannelLogger { @UiEvent(doc = "System created a new conversation (sub-channel in a notification channel)") NOTIFICATION_CHANNEL_CONVERSATION_CREATED(272), @UiEvent(doc = "System deleted a new conversation (sub-channel in a notification channel)") - NOTIFICATION_CHANNEL_CONVERSATION_DELETED(274); - + NOTIFICATION_CHANNEL_CONVERSATION_DELETED(274), + @UiEvent(doc = "All notifications for the app were blocked.") + APP_NOTIFICATIONS_BLOCKED(557), + @UiEvent(doc = "Notifications for the app as a whole were unblocked.") + APP_NOTIFICATIONS_UNBLOCKED(558); private final int mId; NotificationChannelEvent(int id) { @@ -178,6 +198,10 @@ public interface NotificationChannelLogger { ? NotificationChannelEvent.NOTIFICATION_CHANNEL_GROUP_CREATED : NotificationChannelEvent.NOTIFICATION_CHANNEL_GROUP_DELETED; } + + public static NotificationChannelEvent getBlocked(boolean enabled) { + return enabled ? APP_NOTIFICATIONS_UNBLOCKED : APP_NOTIFICATIONS_BLOCKED; + } } /** diff --git a/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java index 2f7772eec2d2..fd3dd568f634 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java +++ b/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java @@ -19,6 +19,8 @@ package com.android.server.notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.util.FrameworkStatsLog; /** @@ -27,6 +29,8 @@ import com.android.internal.util.FrameworkStatsLog; * should live in the interface so it can be tested. */ public class NotificationChannelLoggerImpl implements NotificationChannelLogger { + UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + @Override public void logNotificationChannel(NotificationChannelEvent event, NotificationChannel channel, int uid, String pkg, @@ -51,4 +55,9 @@ public class NotificationChannelLoggerImpl implements NotificationChannelLogger /* int old_importance*/ NotificationChannelLogger.getImportance(wasBlocked), /* int importance*/ NotificationChannelLogger.getImportance(channelGroup)); } + + @Override + public void logAppEvent(NotificationChannelEvent event, int uid, String pkg) { + mUiEventLogger.log(event, uid, pkg); + } } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index e472e3097777..afc75572ae4f 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -1654,6 +1654,7 @@ public class PreferencesHelper implements RankingConfig { } setImportance(packageName, uid, enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE); + mNotificationChannelLogger.logAppNotificationsAllowed(uid, packageName, enabled); } /** diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 4b6ee71803a7..cd6b98d759f1 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -19,6 +19,8 @@ package com.android.server.pm; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,12 +29,12 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; +import android.content.pm.UserInfo; import android.content.pm.parsing.component.ParsedComponent; import android.content.pm.parsing.component.ParsedInstrumentation; import android.content.pm.parsing.component.ParsedIntentInfo; import android.content.pm.parsing.component.ParsedMainComponent; import android.content.pm.parsing.component.ParsedProvider; -import android.os.Binder; import android.os.Process; import android.os.Trace; import android.os.UserHandle; @@ -94,6 +96,14 @@ public class AppsFilter { private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>(); /** + * Pending full recompute of mQueriesViaComponent. Occurs when a package adds a new set of + * protected broadcast. This in turn invalidates all prior additions and require a very + * computationally expensive recomputing. + * Full recompute is done lazily at the point when we use mQueriesViaComponent to filter apps. + */ + private boolean mQueriesViaComponentRequireRecompute = false; + + /** * A set of App IDs that are always queryable by any package, regardless of their manifest * content. */ @@ -109,12 +119,25 @@ public class AppsFilter { private final boolean mSystemAppsQueryable; private final FeatureConfig mFeatureConfig; - private final OverlayReferenceMapper mOverlayReferenceMapper; + private final StateProvider mStateProvider; + private PackageParser.SigningDetails mSystemSigningDetails; private Set<String> mProtectedBroadcasts = new ArraySet<>(); - AppsFilter(FeatureConfig featureConfig, String[] forceQueryableWhitelist, + /** + * This structure maps uid -> uid and indicates whether access from the first should be + * filtered to the second. It's essentially a cache of the + * {@link #shouldFilterApplicationInternal(int, SettingBase, PackageSetting, int)} call. + * NOTE: It can only be relied upon after the system is ready to avoid unnecessary update on + * initial scam and is null until {@link #onSystemReady()} is called. + */ + private volatile SparseArray<SparseBooleanArray> mShouldFilterCache; + + @VisibleForTesting(visibility = PRIVATE) + AppsFilter(StateProvider stateProvider, + FeatureConfig featureConfig, + String[] forceQueryableWhitelist, boolean systemAppsQueryable, @Nullable OverlayReferenceMapper.Provider overlayProvider) { mFeatureConfig = featureConfig; @@ -122,8 +145,23 @@ public class AppsFilter { mSystemAppsQueryable = systemAppsQueryable; mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/, overlayProvider); + mStateProvider = stateProvider; } + /** + * Provides system state to AppsFilter via {@link CurrentStateCallback} after properly guarding + * the data with the package lock. + */ + @VisibleForTesting(visibility = PRIVATE) + public interface StateProvider { + void runWithState(CurrentStateCallback callback); + + interface CurrentStateCallback { + void currentState(ArrayMap<String, PackageSetting> settings, UserInfo[] users); + } + } + + @VisibleForTesting(visibility = PRIVATE) public interface FeatureConfig { /** Called when the system is ready and components can be queried. */ @@ -140,6 +178,7 @@ public class AppsFilter { /** * Turns on logging for the given appId + * * @param enable true if logging should be enabled, false if disabled. */ void enableLogging(int appId, boolean enable); @@ -147,6 +186,7 @@ public class AppsFilter { /** * Initializes the package enablement state for the given package. This gives opportunity * to do any expensive operations ahead of the actual checks. + * * @param removed true if adding, false if removing */ void updatePackageState(PackageSetting setting, boolean removed); @@ -162,6 +202,7 @@ public class AppsFilter { @Nullable private SparseBooleanArray mLoggingEnabled = null; + private AppsFilter mAppsFilter; private FeatureConfigImpl( PackageManagerInternal pmInternal, PackageManagerService.Injector injector) { @@ -169,6 +210,10 @@ public class AppsFilter { mInjector = injector; } + public void setAppsFilter(AppsFilter filter) { + mAppsFilter = filter; + } + @Override public void onSystemReady() { mFeatureEnabled = DeviceConfig.getBoolean( @@ -236,23 +281,17 @@ public class AppsFilter { @Override public void onCompatChange(String packageName) { updateEnabledState(mPmInternal.getPackage(packageName)); + mAppsFilter.updateShouldFilterCacheForPackage(packageName); } private void updateEnabledState(AndroidPackage pkg) { - final long token = Binder.clearCallingIdentity(); - try { - // TODO(b/135203078): Do not use toAppInfo - final boolean enabled = - mInjector.getCompatibility().isChangeEnabled( - PackageManager.FILTER_APPLICATION_QUERY, - pkg.toAppInfoWithoutState()); - if (enabled) { - mDisabledPackages.remove(pkg.getPackageName()); - } else { - mDisabledPackages.add(pkg.getPackageName()); - } - } finally { - Binder.restoreCallingIdentity(token); + // TODO(b/135203078): Do not use toAppInfo + final boolean enabled = mInjector.getCompatibility().isChangeEnabledInternal( + PackageManager.FILTER_APPLICATION_QUERY, pkg.toAppInfoWithoutState()); + if (enabled) { + mDisabledPackages.remove(pkg.getPackageName()); + } else { + mDisabledPackages.add(pkg.getPackageName()); } } @@ -275,7 +314,7 @@ public class AppsFilter { final boolean forceSystemAppsQueryable = injector.getContext().getResources() .getBoolean(R.bool.config_forceSystemPackagesQueryable); - final FeatureConfig featureConfig = new FeatureConfigImpl(pms, injector); + final FeatureConfigImpl featureConfig = new FeatureConfigImpl(pms, injector); final String[] forcedQueryablePackageNames; if (forceSystemAppsQueryable) { // all system apps already queryable, no need to read and parse individual exceptions @@ -288,8 +327,16 @@ public class AppsFilter { forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern(); } } - return new AppsFilter(featureConfig, forcedQueryablePackageNames, - forceSystemAppsQueryable, null); + final StateProvider stateProvider = command -> { + synchronized (injector.getLock()) { + command.currentState(injector.getSettings().mPackages, + injector.getUserManagerInternal().getUserInfos()); + } + }; + AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig, + forcedQueryablePackageNames, forceSystemAppsQueryable, null); + featureConfig.setAppsFilter(appsFilter); + return appsFilter; } public FeatureConfig getFeatureConfig() { @@ -412,27 +459,59 @@ public class AppsFilter { * visibility of the caller from the target. * * @param recipientUid the uid gaining visibility of the {@code visibleUid}. - * @param visibleUid the uid becoming visible to the {@recipientUid} + * @param visibleUid the uid becoming visible to the {@recipientUid} */ public void grantImplicitAccess(int recipientUid, int visibleUid) { - if (recipientUid != visibleUid - && mImplicitlyQueryable.add(recipientUid, visibleUid) && DEBUG_LOGGING) { - Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid); + if (recipientUid != visibleUid) { + if (mImplicitlyQueryable.add(recipientUid, visibleUid) && DEBUG_LOGGING) { + Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid); + } + if (mShouldFilterCache != null) { + // update the cache in a one-off manner since we've got all the information we need. + SparseBooleanArray visibleUids = mShouldFilterCache.get(recipientUid); + if (visibleUids == null) { + visibleUids = new SparseBooleanArray(); + mShouldFilterCache.put(recipientUid, visibleUids); + } + visibleUids.put(visibleUid, false); + } } } public void onSystemReady() { + mStateProvider.runWithState(new StateProvider.CurrentStateCallback() { + @Override + public void currentState(ArrayMap<String, PackageSetting> settings, + UserInfo[] users) { + mShouldFilterCache = new SparseArray<>(users.length * settings.size()); + } + }); mFeatureConfig.onSystemReady(); mOverlayReferenceMapper.rebuildIfDeferred(); + updateEntireShouldFilterCache(); } /** * Adds a package that should be considered when filtering visibility between apps. * - * @param newPkgSetting the new setting being added - * @param existingSettings all other settings currently on the device. + * @param newPkgSetting the new setting being added */ - public void addPackage(PackageSetting newPkgSetting, + public void addPackage(PackageSetting newPkgSetting) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage"); + try { + mStateProvider.runWithState((settings, users) -> { + addPackageInternal(newPkgSetting, settings); + if (mShouldFilterCache != null) { + updateShouldFilterCacheForPackage( + null, newPkgSetting, settings, users, settings.size()); + } // else, rebuild entire cache when system is ready + }); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + } + + private void addPackageInternal(PackageSetting newPkgSetting, ArrayMap<String, PackageSetting> existingSettings) { if (Objects.equals("android", newPkgSetting.name)) { // let's set aside the framework signatures @@ -446,79 +525,154 @@ public class AppsFilter { } } - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage"); - try { - final AndroidPackage newPkg = newPkgSetting.pkg; - if (newPkg == null) { - // nothing to add - return; - } + final AndroidPackage newPkg = newPkgSetting.pkg; + if (newPkg == null) { + // nothing to add + return; + } - if (!newPkg.getProtectedBroadcasts().isEmpty()) { - mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts()); - recomputeComponentVisibility(existingSettings, newPkg.getPackageName()); - } + if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) { + mQueriesViaComponentRequireRecompute = true; + } - final boolean newIsForceQueryable = - mForceQueryable.contains(newPkgSetting.appId) - /* shared user that is already force queryable */ - || newPkg.isForceQueryable() - || newPkgSetting.forceQueryableOverride - || (newPkgSetting.isSystem() && (mSystemAppsQueryable - || ArrayUtils.contains(mForceQueryableByDevicePackageNames, - newPkg.getPackageName()))); - if (newIsForceQueryable - || (mSystemSigningDetails != null - && isSystemSigned(mSystemSigningDetails, newPkgSetting))) { - mForceQueryable.add(newPkgSetting.appId); - } + final boolean newIsForceQueryable = + mForceQueryable.contains(newPkgSetting.appId) + /* shared user that is already force queryable */ + || newPkg.isForceQueryable() + || newPkgSetting.forceQueryableOverride + || (newPkgSetting.isSystem() && (mSystemAppsQueryable + || ArrayUtils.contains(mForceQueryableByDevicePackageNames, + newPkg.getPackageName()))); + if (newIsForceQueryable + || (mSystemSigningDetails != null + && isSystemSigned(mSystemSigningDetails, newPkgSetting))) { + mForceQueryable.add(newPkgSetting.appId); + } - for (int i = existingSettings.size() - 1; i >= 0; i--) { - final PackageSetting existingSetting = existingSettings.valueAt(i); - if (existingSetting.appId == newPkgSetting.appId || existingSetting.pkg == null) { - continue; + for (int i = existingSettings.size() - 1; i >= 0; i--) { + final PackageSetting existingSetting = existingSettings.valueAt(i); + if (existingSetting.appId == newPkgSetting.appId || existingSetting.pkg == null) { + continue; + } + final AndroidPackage existingPkg = existingSetting.pkg; + // let's evaluate the ability of already added packages to see this new package + if (!newIsForceQueryable) { + if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(existingPkg, + newPkg, mProtectedBroadcasts)) { + mQueriesViaComponent.add(existingSetting.appId, newPkgSetting.appId); } - final AndroidPackage existingPkg = existingSetting.pkg; - // let's evaluate the ability of already added packages to see this new package - if (!newIsForceQueryable) { - if (canQueryViaComponents(existingPkg, newPkg, mProtectedBroadcasts)) { - mQueriesViaComponent.add(existingSetting.appId, newPkgSetting.appId); - } - if (canQueryViaPackage(existingPkg, newPkg) - || canQueryAsInstaller(existingSetting, newPkg)) { - mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId); - } + if (canQueryViaPackage(existingPkg, newPkg) + || canQueryAsInstaller(existingSetting, newPkg)) { + mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId); } - // now we'll evaluate our new package's ability to see existing packages - if (!mForceQueryable.contains(existingSetting.appId)) { - if (canQueryViaComponents(newPkg, existingPkg, mProtectedBroadcasts)) { - mQueriesViaComponent.add(newPkgSetting.appId, existingSetting.appId); - } - if (canQueryViaPackage(newPkg, existingPkg) - || canQueryAsInstaller(newPkgSetting, existingPkg)) { - mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId); - } + } + // now we'll evaluate our new package's ability to see existing packages + if (!mForceQueryable.contains(existingSetting.appId)) { + if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(newPkg, + existingPkg, mProtectedBroadcasts)) { + mQueriesViaComponent.add(newPkgSetting.appId, existingSetting.appId); } - // if either package instruments the other, mark both as visible to one another - if (pkgInstruments(newPkgSetting, existingSetting) - || pkgInstruments(existingSetting, newPkgSetting)) { + if (canQueryViaPackage(newPkg, existingPkg) + || canQueryAsInstaller(newPkgSetting, existingPkg)) { mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId); - mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId); } } + // if either package instruments the other, mark both as visible to one another + if (pkgInstruments(newPkgSetting, existingSetting) + || pkgInstruments(existingSetting, newPkgSetting)) { + mQueriesViaPackage.add(newPkgSetting.appId, existingSetting.appId); + mQueriesViaPackage.add(existingSetting.appId, newPkgSetting.appId); + } + } + + int existingSize = existingSettings.size(); + ArrayMap<String, AndroidPackage> existingPkgs = new ArrayMap<>(existingSize); + for (int index = 0; index < existingSize; index++) { + PackageSetting pkgSetting = existingSettings.valueAt(index); + if (pkgSetting.pkg != null) { + existingPkgs.put(pkgSetting.name, pkgSetting.pkg); + } + } + mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs); + mFeatureConfig.updatePackageState(newPkgSetting, false /*removed*/); + } - int existingSize = existingSettings.size(); - ArrayMap<String, AndroidPackage> existingPkgs = new ArrayMap<>(existingSize); - for (int index = 0; index < existingSize; index++) { - PackageSetting pkgSetting = existingSettings.valueAt(index); - if (pkgSetting.pkg != null) { - existingPkgs.put(pkgSetting.name, pkgSetting.pkg); + private void removeAppIdFromVisibilityCache(int appId) { + if (mShouldFilterCache == null) { + return; + } + for (int i = mShouldFilterCache.size() - 1; i >= 0; i--) { + if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) { + mShouldFilterCache.removeAt(i); + continue; + } + SparseBooleanArray targetSparseArray = mShouldFilterCache.valueAt(i); + for (int j = targetSparseArray.size() - 1; j >= 0; j--) { + if (UserHandle.getAppId(targetSparseArray.keyAt(j)) == appId) { + targetSparseArray.removeAt(j); + } + } + } + } + + private void updateEntireShouldFilterCache() { + mStateProvider.runWithState((settings, users) -> { + mShouldFilterCache.clear(); + for (int i = settings.size() - 1; i >= 0; i--) { + updateShouldFilterCacheForPackage( + null /*skipPackage*/, settings.valueAt(i), settings, users, i); + } + }); + } + + public void onUsersChanged() { + if (mShouldFilterCache != null) { + updateEntireShouldFilterCache(); + } + } + + private void updateShouldFilterCacheForPackage(String packageName) { + mStateProvider.runWithState((settings, users) -> { + updateShouldFilterCacheForPackage(null /* skipPackage */, settings.get(packageName), + settings, users, settings.size() /*maxIndex*/); + }); + + } + + private void updateShouldFilterCacheForPackage(@Nullable String skipPackageName, + PackageSetting subjectSetting, ArrayMap<String, PackageSetting> allSettings, + UserInfo[] allUsers, int maxIndex) { + for (int i = Math.min(maxIndex, allSettings.size() - 1); i >= 0; i--) { + PackageSetting otherSetting = allSettings.valueAt(i); + if (subjectSetting.appId == otherSetting.appId) { + continue; + } + //noinspection StringEquality + if (subjectSetting.name == skipPackageName || otherSetting.name == skipPackageName) { + continue; + } + final int userCount = allUsers.length; + final int appxUidCount = userCount * allSettings.size(); + for (int su = 0; su < userCount; su++) { + int subjectUser = allUsers[su].id; + for (int ou = su; ou < userCount; ou++) { + int otherUser = allUsers[ou].id; + int subjectUid = UserHandle.getUid(subjectUser, subjectSetting.appId); + if (!mShouldFilterCache.contains(subjectUid)) { + mShouldFilterCache.put(subjectUid, new SparseBooleanArray(appxUidCount)); + } + int otherUid = UserHandle.getUid(otherUser, otherSetting.appId); + if (!mShouldFilterCache.contains(otherUid)) { + mShouldFilterCache.put(otherUid, new SparseBooleanArray(appxUidCount)); + } + mShouldFilterCache.get(subjectUid).put(otherUid, + shouldFilterApplicationInternal( + subjectUid, subjectSetting, otherSetting, otherUser)); + mShouldFilterCache.get(otherUid).put(subjectUid, + shouldFilterApplicationInternal( + otherUid, otherSetting, subjectSetting, subjectUser)); } } - mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs); - mFeatureConfig.updatePackageState(newPkgSetting, false /*removed*/); - } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } @@ -544,13 +698,11 @@ public class AppsFilter { return ret; } - private void recomputeComponentVisibility(ArrayMap<String, PackageSetting> existingSettings, - @Nullable String excludePackage) { + private void recomputeComponentVisibility(ArrayMap<String, PackageSetting> existingSettings) { mQueriesViaComponent.clear(); for (int i = existingSettings.size() - 1; i >= 0; i--) { PackageSetting setting = existingSettings.valueAt(i); if (setting.pkg == null - || setting.pkg.getPackageName().equals(excludePackage) || mForceQueryable.contains(setting.appId)) { continue; } @@ -559,8 +711,7 @@ public class AppsFilter { continue; } final PackageSetting otherSetting = existingSettings.valueAt(j); - if (otherSetting.pkg == null - || otherSetting.pkg.getPackageName().equals(excludePackage)) { + if (otherSetting.pkg == null) { continue; } if (canQueryViaComponents(setting.pkg, otherSetting.pkg, mProtectedBroadcasts)) { @@ -568,7 +719,9 @@ public class AppsFilter { } } } + mQueriesViaComponentRequireRecompute = false; } + /** * Fetches all app Ids that a given setting is currently visible to, per provided user. This * only includes UIDs >= {@link Process#FIRST_APPLICATION_UID} as all other UIDs can already see @@ -577,11 +730,11 @@ public class AppsFilter { * If the setting is visible to all UIDs, null is returned. If an app is not visible to any * applications, the int array will be empty. * - * @param users the set of users that should be evaluated for this calculation + * @param users the set of users that should be evaluated for this calculation * @param existingSettings the set of all package settings that currently exist on device * @return a SparseArray mapping userIds to a sorted int array of appIds that may view the - * provided setting or null if the app is visible to all and no whitelist should be - * applied. + * provided setting or null if the app is visible to all and no whitelist should be + * applied. */ @Nullable public SparseArray<int[]> getVisibilityWhitelist(PackageSetting setting, int[] users, @@ -626,52 +779,70 @@ public class AppsFilter { /** * Removes a package for consideration when filtering visibility between apps. * - * @param setting the setting of the package being removed. - * @param allUsers array of all current users on device. + * @param setting the setting of the package being removed. */ - public void removePackage(PackageSetting setting, int[] allUsers, - ArrayMap<String, PackageSetting> existingSettings) { - mForceQueryable.remove(setting.appId); + public void removePackage(PackageSetting setting) { + removeAppIdFromVisibilityCache(setting.appId); + mStateProvider.runWithState((settings, users) -> { + final int userCount = users.length; + for (int u = 0; u < userCount; u++) { + final int userId = users[u].id; + final int removingUid = UserHandle.getUid(userId, setting.appId); + mImplicitlyQueryable.remove(removingUid); + for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) { + mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), removingUid); + } + } - for (int u = 0; u < allUsers.length; u++) { - final int userId = allUsers[u]; - final int removingUid = UserHandle.getUid(userId, setting.appId); - mImplicitlyQueryable.remove(removingUid); - for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) { - mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), removingUid); + if (!mQueriesViaComponentRequireRecompute) { + mQueriesViaComponent.remove(setting.appId); + for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) { + mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId); + } + } + mQueriesViaPackage.remove(setting.appId); + for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) { + mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.appId); } - } - mQueriesViaComponent.remove(setting.appId); - for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) { - mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId); - } - mQueriesViaPackage.remove(setting.appId); - for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) { - mQueriesViaPackage.remove(mQueriesViaPackage.keyAt(i), setting.appId); - } + // re-add other shared user members to re-establish visibility between them and other + // packages + if (setting.sharedUser != null) { + for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) { + if (setting.sharedUser.packages.valueAt(i) == setting) { + continue; + } + addPackageInternal( + setting.sharedUser.packages.valueAt(i), settings); + } + } - // re-add other shared user members to re-establish visibility between them and other - // packages - if (setting.sharedUser != null) { - for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) { - if (setting.sharedUser.packages.valueAt(i) == setting) { - continue; + if (!setting.pkg.getProtectedBroadcasts().isEmpty()) { + final String removingPackageName = setting.pkg.getPackageName(); + final Set<String> protectedBroadcasts = mProtectedBroadcasts; + mProtectedBroadcasts = collectProtectedBroadcasts(settings, removingPackageName); + if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) { + mQueriesViaComponentRequireRecompute = true; } - addPackage(setting.sharedUser.packages.valueAt(i), existingSettings); } - } - if (!setting.pkg.getProtectedBroadcasts().isEmpty()) { - final String removingPackageName = setting.pkg.getPackageName(); - mProtectedBroadcasts.clear(); - mProtectedBroadcasts.addAll( - collectProtectedBroadcasts(existingSettings, removingPackageName)); - recomputeComponentVisibility(existingSettings, removingPackageName); - } + mOverlayReferenceMapper.removePkg(setting.name); + mFeatureConfig.updatePackageState(setting, true /*removed*/); + + if (mShouldFilterCache != null && setting.sharedUser != null) { + for (int i = setting.sharedUser.packages.size() - 1; i >= 0; i--) { + PackageSetting siblingSetting = setting.sharedUser.packages.valueAt(i); + if (siblingSetting == setting) { + continue; + } + updateShouldFilterCacheForPackage( + setting.name, siblingSetting, settings, users, settings.size()); + } + } + }); + mForceQueryable.remove(setting.appId); + - mOverlayReferenceMapper.removePkg(setting.name); - mFeatureConfig.updatePackageState(setting, true /*removed*/); } /** @@ -688,12 +859,35 @@ public class AppsFilter { PackageSetting targetPkgSetting, int userId) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplication"); try { - - if (!shouldFilterApplicationInternal( - callingUid, callingSetting, targetPkgSetting, userId)) { + int callingAppId = UserHandle.getAppId(callingUid); + if (callingAppId < Process.FIRST_APPLICATION_UID + || targetPkgSetting.appId < Process.FIRST_APPLICATION_UID + || callingAppId == targetPkgSetting.appId) { return false; } - if (DEBUG_LOGGING || mFeatureConfig.isLoggingEnabled(UserHandle.getAppId(callingUid))) { + if (mShouldFilterCache != null) { // use cache + SparseBooleanArray shouldFilterTargets = mShouldFilterCache.get(callingUid); + final int targetUid = UserHandle.getUid(userId, targetPkgSetting.appId); + if (shouldFilterTargets == null) { + Slog.wtf(TAG, "Encountered calling uid with no cached rules: " + callingUid); + return true; + } + int indexOfTargetUid = shouldFilterTargets.indexOfKey(targetUid); + if (indexOfTargetUid < 0) { + Slog.w(TAG, "Encountered calling -> target with no cached rules: " + + callingUid + " -> " + targetUid); + return true; + } + if (!shouldFilterTargets.valueAt(indexOfTargetUid)) { + return false; + } + } else { + if (!shouldFilterApplicationInternal( + callingUid, callingSetting, targetPkgSetting, userId)) { + return false; + } + } + if (DEBUG_LOGGING || mFeatureConfig.isLoggingEnabled(callingAppId)) { log(callingSetting, targetPkgSetting, "BLOCKED"); } return !DEBUG_ALLOW_ALL; @@ -703,7 +897,7 @@ public class AppsFilter { } private boolean shouldFilterApplicationInternal(int callingUid, SettingBase callingSetting, - PackageSetting targetPkgSetting, int userId) { + PackageSetting targetPkgSetting, int targetUserId) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplicationInternal"); try { final boolean featureEnabled = mFeatureConfig.isGloballyEnabled(); @@ -713,12 +907,6 @@ public class AppsFilter { } return false; } - if (callingUid < Process.FIRST_APPLICATION_UID) { - if (DEBUG_LOGGING) { - Slog.d(TAG, "filtering skipped; " + callingUid + " is system"); - } - return false; - } if (callingSetting == null) { Slog.wtf(TAG, "No setting found for non system uid " + callingUid); return true; @@ -727,8 +915,14 @@ public class AppsFilter { final ArraySet<PackageSetting> callingSharedPkgSettings; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingSetting instanceof"); if (callingSetting instanceof PackageSetting) { - callingPkgSetting = (PackageSetting) callingSetting; - callingSharedPkgSettings = null; + if (((PackageSetting) callingSetting).sharedUser == null) { + callingPkgSetting = (PackageSetting) callingSetting; + callingSharedPkgSettings = null; + } else { + callingPkgSetting = null; + callingSharedPkgSettings = + ((PackageSetting) callingSetting).sharedUser.packages; + } } else { callingPkgSetting = null; callingSharedPkgSettings = ((SharedUserSetting) callingSetting).packages; @@ -786,13 +980,19 @@ public class AppsFilter { } try { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "hasPermission"); - if (callingSetting.getPermissionsState().hasPermission( - Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) { - if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "has query-all permission"); + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "requestsQueryAllPackages"); + if (callingPkgSetting != null) { + if (callingPkgSetting.pkg != null + && requestsQueryAllPackages(callingPkgSetting.pkg)) { + return false; + } + } else { + for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) { + AndroidPackage pkg = callingSharedPkgSettings.valueAt(i).pkg; + if (pkg != null && requestsQueryAllPackages(pkg)) { + return false; + } } - return false; } } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -821,6 +1021,11 @@ public class AppsFilter { } try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaComponent"); + if (mQueriesViaComponentRequireRecompute) { + mStateProvider.runWithState((settings, users) -> { + recomputeComponentVisibility(settings); + }); + } if (mQueriesViaComponent.contains(callingAppId, targetAppId)) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "queries component"); @@ -833,7 +1038,7 @@ public class AppsFilter { try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mImplicitlyQueryable"); - final int targetUid = UserHandle.getUid(userId, targetAppId); + final int targetUid = UserHandle.getUid(targetUserId, targetAppId); if (mImplicitlyQueryable.contains(callingUid, targetUid)) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "implicitly queryable for user"); @@ -871,13 +1076,20 @@ public class AppsFilter { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - return true; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } + + private static boolean requestsQueryAllPackages(@NonNull AndroidPackage pkg) { + // we're not guaranteed to have permissions yet analyzed at package add, so we inspect the + // package directly + return pkg.getRequestedPermissions().contains( + Manifest.permission.QUERY_ALL_PACKAGES); + } + /** Returns {@code true} if the source package instruments the target package. */ private static boolean pkgInstruments(PackageSetting source, PackageSetting target) { try { diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java index 0bd8b28ee6ac..fc58968a7325 100644 --- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java +++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java @@ -133,8 +133,10 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { @Override public NativeLibraryPaths getNativeLibraryPaths(AndroidPackage pkg, PackageSetting pkgSetting, File appLib32InstallDir) { - return getNativeLibraryPaths(new Abis(pkg, pkgSetting), appLib32InstallDir, - pkg.getCodePath(), pkg.getBaseCodePath(), pkg.isSystem(), + // Trying to derive the paths, thus need the raw ABI info from the parsed package, and the + // current state in PackageSetting is irrelevant. + return getNativeLibraryPaths(new Abis(pkg.getPrimaryCpuAbi(), pkg.getSecondaryCpuAbi()), + appLib32InstallDir, pkg.getCodePath(), pkg.getBaseCodePath(), pkg.isSystem(), pkgSetting.getPkgState().isUpdatedSystemApp()); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 7ab05c4f762c..de8ad6b7db13 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2108,6 +2108,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { baseApk = apk; } + // Validate and add Dex Metadata (.dm). final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(addedFile); if (dexMetadataFile != null) { if (!FileUtils.isValidExtFilename(dexMetadataFile.getName())) { @@ -2295,6 +2296,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new PackageManagerException(INSTALL_FAILED_MISSING_SPLIT, "Missing split for " + mPackageName); } + + final boolean isInstallerShell = (mInstallerUid == Process.SHELL_UID); + if (isInstallerShell && isIncrementalInstallation() && mIncrementalFileStorages != null) { + if (!baseApk.debuggable && !baseApk.profilableByShell) { + mIncrementalFileStorages.disableReadLogs(); + } + } } private void resolveAndStageFile(File origFile, File targetFile) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ae8b3a0e9acc..13145d00274f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3175,6 +3175,10 @@ public class PackageManagerService extends IPackageManager.Stub psit.remove(); logCriticalInfo(Log.WARN, "System package " + ps.name + " no longer exists; it's data will be wiped"); + + // Assume package is truly gone and wipe residual permissions. + mPermissionManager.updatePermissions(ps.name, null); + // Actual deletion of code and data will be handled by later // reconciliation step } else { @@ -12362,7 +12366,7 @@ public class PackageManagerService extends IPackageManager.Stub ksms.addScannedPackageLPw(pkg); mComponentResolver.addAllComponents(pkg, chatty); - mAppsFilter.addPackage(pkgSetting, mSettings.mPackages); + mAppsFilter.addPackage(pkgSetting); // Don't allow ephemeral applications to define new permissions groups. if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) { @@ -12536,8 +12540,6 @@ public class PackageManagerService extends IPackageManager.Stub void cleanPackageDataStructuresLILPw(AndroidPackage pkg, boolean chatty) { mComponentResolver.removeAllComponents(pkg, chatty); - mAppsFilter.removePackage(getPackageSetting(pkg.getPackageName()), - mInjector.getUserManagerInternal().getUserIds(), mSettings.mPackages); mPermissionManager.removeAllPermissions(pkg, chatty); final int instrumentationSize = ArrayUtils.size(pkg.getInstrumentations()); @@ -14264,7 +14266,7 @@ public class PackageManagerService extends IPackageManager.Stub // Okay! targetPackageSetting.setInstallerPackageName(installerPackageName); mSettings.addInstallerPackageNames(targetPackageSetting.installSource); - mAppsFilter.addPackage(targetPackageSetting, mSettings.mPackages); + mAppsFilter.addPackage(targetPackageSetting); scheduleWriteSettingsLocked(); } } @@ -18717,6 +18719,7 @@ public class PackageManagerService extends IPackageManager.Stub clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL, true); clearDefaultBrowserIfNeeded(packageName); mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName); + mAppsFilter.removePackage(getPackageSetting(packageName)); removedAppId = mSettings.removePackageLPw(packageName); if (outInfo != null) { outInfo.removedAppId = removedAppId; @@ -23474,6 +23477,7 @@ public class PackageManagerService extends IPackageManager.Stub scheduleWritePackageRestrictionsLocked(userId); scheduleWritePackageListLocked(userId); primeDomainVerificationsLPw(userId); + mAppsFilter.onUsersChanged(); } } diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index 67e1994eac9a..fdd9636ae7b2 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -349,7 +349,8 @@ public final class SELinuxMMAC { if ((sharedUserSetting != null) && (sharedUserSetting.packages.size() != 0)) { return sharedUserSetting.seInfoTargetSdkVersion; } - if (compatibility.isChangeEnabled(SELINUX_LATEST_CHANGES, pkg.toAppInfoWithoutState())) { + if (compatibility.isChangeEnabledInternal(SELINUX_LATEST_CHANGES, + pkg.toAppInfoWithoutState())) { return android.os.Build.VERSION_CODES.R; } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 8ccf837f64dc..515225b1d3be 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -53,6 +53,7 @@ import android.os.ParcelableException; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManagerInternal; import android.os.storage.IStorageManager; @@ -1155,6 +1156,11 @@ public class StagingManager { } private void checkStateAndResume(@NonNull PackageInstallerSession session) { + // Do not resume session if boot completed already + if (SystemProperties.getBoolean("sys.boot_completed", false)) { + return; + } + if (!session.isCommitted()) { // Session hasn't been committed yet, ignore. return; diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java index 7c89b9850c5d..492b84a0a84b 100644 --- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java +++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java @@ -363,7 +363,8 @@ class UserSystemPackageInstaller { pmInt.forEachPackage(pkg -> { if (!pkg.isSystem()) return; final String pkgName = pkg.getManifestPackageName(); - if (!allWhitelistedPackages.contains(pkgName)) { + if (!allWhitelistedPackages.contains(pkgName) + && !isAutoGeneratedRRO(pmInt.getPackage(pkgName))) { errors.add(String.format(logMessageFmt, pkgName)); } }); diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index 51e07faf8443..8000c639139f 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -63,6 +63,10 @@ import libcore.io.IoUtils; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Objects; /** @@ -557,6 +561,20 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { private static final int TRON_COMPILATION_FILTER_FAKE_RUN_FROM_APK = 12; private static final int TRON_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK = 13; private static final int TRON_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK = 14; + // Filter with IORap + private static final int TRON_COMPILATION_FILTER_ASSUMED_VERIFIED_IORAP = 15; + private static final int TRON_COMPILATION_FILTER_EXTRACT_IORAP = 16; + private static final int TRON_COMPILATION_FILTER_VERIFY_IORAP = 17; + private static final int TRON_COMPILATION_FILTER_QUICKEN_IORAP = 18; + private static final int TRON_COMPILATION_FILTER_SPACE_PROFILE_IORAP = 19; + private static final int TRON_COMPILATION_FILTER_SPACE_IORAP = 20; + private static final int TRON_COMPILATION_FILTER_SPEED_PROFILE_IORAP = 21; + private static final int TRON_COMPILATION_FILTER_SPEED_IORAP = 22; + private static final int TRON_COMPILATION_FILTER_EVERYTHING_PROFILE_IORAP = 23; + private static final int TRON_COMPILATION_FILTER_EVERYTHING_IORAP = 24; + private static final int TRON_COMPILATION_FILTER_FAKE_RUN_FROM_APK_IORAP = 25; + private static final int TRON_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK_IORAP = 26; + private static final int TRON_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK_IORAP = 27; // Constants used for logging compilation reason to TRON. // DO NOT CHANGE existing values. @@ -623,6 +641,22 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { return TRON_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK; case "run-from-vdex-fallback" : return TRON_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK; + case "assume-verified-iorap" : return TRON_COMPILATION_FILTER_ASSUMED_VERIFIED_IORAP; + case "extract-iorap" : return TRON_COMPILATION_FILTER_EXTRACT_IORAP; + case "verify-iorap" : return TRON_COMPILATION_FILTER_VERIFY_IORAP; + case "quicken-iorap" : return TRON_COMPILATION_FILTER_QUICKEN_IORAP; + case "space-profile-iorap" : return TRON_COMPILATION_FILTER_SPACE_PROFILE_IORAP; + case "space-iorap" : return TRON_COMPILATION_FILTER_SPACE_IORAP; + case "speed-profile-iorap" : return TRON_COMPILATION_FILTER_SPEED_PROFILE_IORAP; + case "speed-iorap" : return TRON_COMPILATION_FILTER_SPEED_IORAP; + case "everything-profile-iorap" : + return TRON_COMPILATION_FILTER_EVERYTHING_PROFILE_IORAP; + case "everything-iorap" : return TRON_COMPILATION_FILTER_EVERYTHING_IORAP; + case "run-from-apk-iorap" : return TRON_COMPILATION_FILTER_FAKE_RUN_FROM_APK_IORAP; + case "run-from-apk-fallback-iorap" : + return TRON_COMPILATION_FILTER_FAKE_RUN_FROM_APK_FALLBACK_IORAP; + case "run-from-vdex-fallback-iorap" : + return TRON_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK_IORAP; default: return TRON_COMPILATION_FILTER_UNKNOWN; } } @@ -640,9 +674,12 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { } private class ArtManagerInternalImpl extends ArtManagerInternal { + private static final String IORAP_DIR = "/data/misc/iorapd"; + private static final String TAG = "ArtManagerInternalImpl"; + @Override public PackageOptimizationInfo getPackageOptimizationInfo( - ApplicationInfo info, String abi) { + ApplicationInfo info, String abi, String activityName) { String compilationReason; String compilationFilter; try { @@ -662,11 +699,45 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { compilationReason = "error"; } + if (checkIorapCompiledTrace(info.packageName, activityName, info.longVersionCode)) { + compilationFilter = compilationFilter + "-iorap"; + } + int compilationFilterTronValue = getCompilationFilterTronValue(compilationFilter); int compilationReasonTronValue = getCompilationReasonTronValue(compilationReason); return new PackageOptimizationInfo( compilationFilterTronValue, compilationReasonTronValue); } + + /* + * Checks the existence of IORap compiled trace for an app. + * + * @return true if the compiled trace exists and the size is greater than 1kb. + */ + private boolean checkIorapCompiledTrace( + String packageName, String activityName, long version) { + // For example: /data/misc/iorapd/com.google.android.GoogleCamera/ + // 60092239/com.android.camera.CameraLauncher/compiled_traces/compiled_trace.pb + Path tracePath = Paths.get(IORAP_DIR, + packageName, + Long.toString(version), + activityName, + "compiled_traces", + "compiled_trace.pb"); + try { + boolean exists = Files.exists(tracePath); + Log.d(TAG, tracePath.toString() + (exists? " exists" : " doesn't exist")); + if (exists) { + long bytes = Files.size(tracePath); + Log.d(TAG, tracePath.toString() + " size is " + Long.toString(bytes)); + return bytes > 0L; + } + return exists; + } catch (IOException e) { + Log.d(TAG, e.getMessage()); + return false; + } + } } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index d3f3ba1dc6bb..1b11e2d0860d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -2509,6 +2509,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { for (String permissionName : requestedPermissions) { BasePermission permission = mSettings.getPermission(permissionName); + if (permission == null) { + continue; + } if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME) && permission.isRuntime() && !permission.isRemoved()) { if (permission.isHardOrSoftRestricted() diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index c2bae1a8962b..802a35560ba5 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -209,14 +209,6 @@ public class StatsPullAtomService extends SystemService { // Random seed stable for StatsPullAtomService life cycle - can be used for stable sampling private static final int RANDOM_SEED = new Random().nextInt(); - /** - * Lowest available uid for apps. - * - * <p>Used to quickly discard memory snapshots of the zygote forks from native process - * measurements. - */ - private static final int MIN_APP_UID = 10_000; - private static final int DIMENSION_KEY_SIZE_HARD_LIMIT = 800; private static final int DIMENSION_KEY_SIZE_SOFT_LIMIT = 500; private static final long APP_OPS_SAMPLING_INITIALIZATION_DELAY_MILLIS = 45000; @@ -244,6 +236,17 @@ public class StatsPullAtomService extends SystemService { private static final String DANGEROUS_PERMISSION_STATE_SAMPLE_RATE = "dangerous_permission_state_sample_rate"; + /** Parameters relating to ProcStats data upload. */ + // Maximum shards to use when generating StatsEvent objects from ProcStats. + private static final int MAX_PROCSTATS_SHARDS = 5; + // Should match MAX_PAYLOAD_SIZE in StatsEvent, minus a small amount for overhead/metadata. + private static final int MAX_PROCSTATS_SHARD_SIZE = 48 * 1024; // 48 KB + // In ProcessStats, we measure the size of a raw ProtoOutputStream, before compaction. This + // typically runs 35-45% larger than the compacted size that will be written to StatsEvent. + // Hence, we can allow a little more room in each shard before moving to the next. Make this + // 20% as a conservative estimate. + private static final int MAX_PROCSTATS_RAW_SHARD_SIZE = (int) (MAX_PROCSTATS_SHARD_SIZE * 1.20); + private final Object mThermalLock = new Object(); @GuardedBy("mThermalLock") private IThermalService mThermalService; @@ -1819,10 +1822,6 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - private static boolean isAppUid(int uid) { - return uid >= MIN_APP_UID; - } - private void registerProcessMemoryHighWaterMark() { int tagId = FrameworkStatsLog.PROCESS_MEMORY_HIGH_WATER_MARK; mStatsManager.setPullAtomCallback( @@ -1859,7 +1858,7 @@ public class StatsPullAtomService extends SystemService { int size = processCmdlines.size(); for (int i = 0; i < size; ++i) { final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(processCmdlines.keyAt(i)); - if (snapshot == null || isAppUid(snapshot.uid)) { + if (snapshot == null) { continue; } StatsEvent e = StatsEvent.newBuilder() @@ -1920,7 +1919,7 @@ public class StatsPullAtomService extends SystemService { for (int i = 0; i < size; ++i) { int pid = processCmdlines.keyAt(i); final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid); - if (snapshot == null || isAppUid(snapshot.uid)) { + if (snapshot == null) { continue; } StatsEvent e = StatsEvent.newBuilder() @@ -2566,19 +2565,26 @@ public class StatsPullAtomService extends SystemService { long lastHighWaterMark = readProcStatsHighWaterMark(section); List<ParcelFileDescriptor> statsFiles = new ArrayList<>(); + ProtoOutputStream[] protoStreams = new ProtoOutputStream[MAX_PROCSTATS_SHARDS]; + for (int i = 0; i < protoStreams.length; i++) { + protoStreams[i] = new ProtoOutputStream(); + } + ProcessStats procStats = new ProcessStats(false); + // Force processStatsService to aggregate all in-storage and in-memory data. long highWaterMark = processStatsService.getCommittedStatsMerged( lastHighWaterMark, section, true, statsFiles, procStats); + procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE); - // aggregate the data together for westworld consumption - ProtoOutputStream proto = new ProtoOutputStream(); - procStats.dumpAggregatedProtoForStatsd(proto); - - StatsEvent e = StatsEvent.newBuilder() - .setAtomId(atomTag) - .writeByteArray(proto.getBytes()) - .build(); - pulledData.add(e); + for (ProtoOutputStream proto : protoStreams) { + if (proto.getBytes().length > 0) { + StatsEvent e = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeByteArray(proto.getBytes()) + .build(); + pulledData.add(e); + } + } new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + lastHighWaterMark) .delete(); @@ -3577,7 +3583,16 @@ public class StatsPullAtomService extends SystemService { private void processHistoricalOp(AppOpsManager.HistoricalOp op, List<AppOpEntry> opsList, int uid, int samplingRatio, String packageName, @Nullable String attributionTag) { - AppOpEntry entry = new AppOpEntry(packageName, attributionTag, op, uid); + int firstChar = 0; + if (attributionTag != null && attributionTag.startsWith(packageName)) { + firstChar = packageName.length(); + if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar) == '.') { + firstChar++; + } + } + AppOpEntry entry = new AppOpEntry(packageName, + attributionTag == null ? null : attributionTag.substring(firstChar), op, + uid); if (entry.mHash < samplingRatio) { opsList.add(entry); } diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java index 94a250236200..ed5706752cb2 100644 --- a/services/core/java/com/android/server/storage/StorageUserConnection.java +++ b/services/core/java/com/android/server/storage/StorageUserConnection.java @@ -62,7 +62,7 @@ import java.util.concurrent.TimeoutException; public final class StorageUserConnection { private static final String TAG = "StorageUserConnection"; - public static final int REMOTE_TIMEOUT_SECONDS = 5; + public static final int REMOTE_TIMEOUT_SECONDS = 20; private final Object mLock = new Object(); private final Context mContext; @@ -202,6 +202,7 @@ public final class StorageUserConnection { try { if (!latch.await(REMOTE_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { // TODO(b/140025078): Call ActivityManager ANR API? + Slog.wtf(TAG, "Failed to bind to the ExternalStorageService for user " + mUserId); throw new TimeoutException("Latch wait for " + reason + " elapsed"); } } catch (InterruptedException e) { diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 41aa4ee65f30..7cb59dcbfb9b 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.content.Context; +import android.media.IResourceManagerService; import android.media.tv.TvInputManager; import android.media.tv.tunerresourcemanager.CasSessionRequest; import android.media.tv.tunerresourcemanager.IResourcesReclaimListener; @@ -53,7 +54,7 @@ import java.util.Set; * * @hide */ -public class TunerResourceManagerService extends SystemService { +public class TunerResourceManagerService extends SystemService implements IBinder.DeathRecipient { private static final String TAG = "TunerResourceManagerService"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -76,6 +77,7 @@ public class TunerResourceManagerService extends SystemService { private TvInputManager mTvInputManager; private ActivityManager mActivityManager; + private IResourceManagerService mMediaResourceManager; private UseCasePriorityHints mPriorityCongfig = new UseCasePriorityHints(); // An internal resource request count to help generate resource handle. @@ -102,6 +104,22 @@ public class TunerResourceManagerService extends SystemService { mActivityManager = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE); mPriorityCongfig.parse(); + + if (mMediaResourceManager == null) { + IBinder mediaResourceManagerBinder = getBinderService("media.resource_manager"); + if (mediaResourceManagerBinder == null) { + Slog.w(TAG, "Resource Manager Service not available."); + return; + } + try { + mediaResourceManagerBinder.linkToDeath(this, /*flags*/ 0); + } catch (RemoteException e) { + Slog.w(TAG, "Could not link to death of native resource manager service."); + return; + } + mMediaResourceManager = IResourceManagerService.Stub.asInterface( + mediaResourceManagerBinder); + } } private final class BinderService extends ITunerResourceManager.Stub { @@ -380,6 +398,19 @@ public class TunerResourceManagerService extends SystemService { } } + /** + * Handle the death of the native resource manager service + */ + @Override + public void binderDied() { + if (DEBUG) { + Slog.w(TAG, "Native media resource manager service has died"); + } + synchronized (mLock) { + mMediaResourceManager = null; + } + } + @VisibleForTesting protected void registerClientProfileInternal(ResourceClientProfile profile, IResourcesReclaimListener listener, int[] clientId) { @@ -399,6 +430,16 @@ public class TunerResourceManagerService extends SystemService { ? Binder.getCallingPid() /*callingPid*/ : mTvInputManager.getClientPid(profile.getTvInputSessionId()); /*tvAppId*/ + // Update Media Resource Manager with the tvAppId + if (profile.getTvInputSessionId() != null && mMediaResourceManager != null) { + try { + mMediaResourceManager.overridePid(Binder.getCallingPid(), pid); + } catch (RemoteException e) { + Slog.e(TAG, "Could not overridePid in resourceManagerSercice," + + " remote exception: " + e); + } + } + ClientProfile clientProfile = new ClientProfile.Builder(clientId[0]) .tvInputSessionId(profile.getTvInputSessionId()) .useCase(profile.getUseCase()) @@ -415,6 +456,15 @@ public class TunerResourceManagerService extends SystemService { Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")"); } removeClientProfile(clientId); + // Remove the Media Resource Manager callingPid to tvAppId mapping + if (mMediaResourceManager != null) { + try { + mMediaResourceManager.overridePid(Binder.getCallingPid(), -1); + } catch (RemoteException e) { + Slog.e(TAG, "Could not overridePid in resourceManagerSercice when unregister," + + " remote exception: " + e); + } + } } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index fb29f9a93215..189b21fb81a6 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -842,7 +842,8 @@ class ActivityMetricsLogger { ? PackageOptimizationInfo.createWithNoInfo() : artManagerInternal.getPackageOptimizationInfo( info.applicationInfo, - info.launchedActivityAppRecordRequiredAbi); + info.launchedActivityAppRecordRequiredAbi, + info.launchedActivityName); builder.addTaggedData(PACKAGE_OPTIMIZATION_COMPILATION_REASON, packageOptimizationInfo.getCompilationReason()); builder.addTaggedData(PACKAGE_OPTIMIZATION_COMPILATION_FILTER, diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index be2f9d410475..304860c2588f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -311,7 +311,6 @@ import com.android.server.uri.UriPermissionOwner; import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot; import com.android.server.wm.ActivityStack.ActivityState; import com.android.server.wm.SurfaceAnimator.AnimationType; -import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import com.android.server.wm.WindowManagerService.H; import com.android.server.wm.utils.InsetUtils; @@ -2199,10 +2198,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override boolean isFocusable() { + return super.isFocusable() && (canReceiveKeys() || isAlwaysFocusable()); + } + + boolean canReceiveKeys() { // TODO(156521483): Propagate the state down the hierarchy instead of checking the parent - boolean canReceiveKeys = getWindowConfiguration().canReceiveKeys() - && getTask().getWindowConfiguration().canReceiveKeys(); - return super.isFocusable() && (canReceiveKeys || isAlwaysFocusable()); + return getWindowConfiguration().canReceiveKeys() + && (task == null || task.getWindowConfiguration().canReceiveKeys()); } boolean isResizeable() { @@ -2371,10 +2373,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // For the apps below Q, there can be only one app which has the focused window per // process, because legacy apps may not be ready for a multi-focus system. return false; + } } - return (getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable()) - && getDisplay() != null; + return (canReceiveKeys() || isAlwaysFocusable()) && getDisplay() != null; } /** @@ -3356,6 +3358,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final long origId = Binder.clearCallingIdentity(); try { + // Link the fixed rotation transform to this activity since we are transferring the + // starting window. + if (fromActivity.hasFixedRotationTransform()) { + mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(this, + false /* checkOpening */); + } + // Transfer the starting window over to the new token. mStartingData = fromActivity.mStartingData; startingSurface = fromActivity.startingSurface; @@ -4166,14 +4175,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @Override - boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - boolean isVoiceInteraction, - @Nullable OnAnimationFinishedCallback animationFinishedCallback) { + boolean applyAnimation(LayoutParams lp, int transit, boolean enter, + boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) { if (mUseTransferredAnimation) { return false; } - return super.applyAnimation(lp, transit, enter, isVoiceInteraction, - animationFinishedCallback); + return super.applyAnimation(lp, transit, enter, isVoiceInteraction, sources); } /** @@ -6449,14 +6456,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override public boolean matchParentBounds() { - if (super.matchParentBounds() && mCompatDisplayInsets == null) { + final Rect overrideBounds = getResolvedOverrideBounds(); + if (overrideBounds.isEmpty()) { return true; } - // An activity in size compatibility mode may have resolved override bounds, so the exact - // bounds should also be checked. Otherwise IME window will show with offset. See - // {@link DisplayContent#isImeAttachedToApp}. + // An activity in size compatibility mode may have override bounds which equals to its + // parent bounds, so the exact bounds should also be checked to allow IME window to attach + // to the activity. See {@link DisplayContent#isImeAttachedToApp}. final WindowContainer parent = getParent(); - return parent == null || parent.getBounds().equals(getResolvedOverrideBounds()); + return parent == null || parent.getBounds().equals(overrideBounds); } @Override @@ -7720,24 +7728,25 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A outAppBounds.set(outBounds); } } else { - outBounds.set(0, 0, mWidth, mHeight); - getFrameByOrientation(outAppBounds, orientation); - if (orientationRequested && !canChangeOrientation - && (outAppBounds.width() > outAppBounds.height()) != (mWidth > mHeight)) { - // The orientation is mismatched but the display cannot rotate. The bounds will - // fit to the short side of display. - if (orientation == ORIENTATION_LANDSCAPE) { - outAppBounds.bottom = (int) ((float) mWidth * mWidth / mHeight); - outAppBounds.right = mWidth; - } else { - outAppBounds.bottom = mHeight; - outAppBounds.right = (int) ((float) mHeight * mHeight / mWidth); + if (orientationRequested) { + getFrameByOrientation(outBounds, orientation); + if ((outBounds.width() > outBounds.height()) != (mWidth > mHeight)) { + // The orientation is mismatched but the display cannot rotate. The bounds + // will fit to the short side of display. + if (orientation == ORIENTATION_LANDSCAPE) { + outBounds.bottom = (int) ((float) mWidth * mWidth / mHeight); + outBounds.right = mWidth; + } else { + outBounds.bottom = mHeight; + outBounds.right = (int) ((float) mHeight * mHeight / mWidth); + } + outBounds.offset( + getHorizontalCenterOffset(mWidth, outBounds.width()), 0 /* dy */); } - outAppBounds.offset(getHorizontalCenterOffset(outBounds.width(), - outAppBounds.width()), 0 /* dy */); } else { - outAppBounds.set(outBounds); + outBounds.set(0, 0, mWidth, mHeight); } + outAppBounds.set(outBounds); } if (rotation != ROTATION_UNDEFINED) { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 4bede4c3623f..b4bc0f5b3a32 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -813,7 +813,7 @@ class ActivityStack extends Task { /** Resume next focusable stack after reparenting to another display. */ void postReparent() { adjustFocusToNextFocusableTask("reparent", true /* allowFocusSelf */, - true /* moveParentsToTop */); + true /* moveDisplayToTop */); mRootWindowContainer.resumeFocusedStacksTopActivities(); // Update visibility of activities before notifying WM. This way it won't try to resize // windows that are no longer visible. diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 67fe9685fd2a..d60d098071cc 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -49,7 +49,7 @@ import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_W import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; -import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -75,7 +75,6 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.function.Predicate; - /** * Checks for app transition readiness, resolves animation attributes and performs visibility * change for apps that animate as part of an app transition. @@ -375,18 +374,14 @@ public class AppTransitionController { // triggers WC#onAnimationFinished only on the promoted target. So we need to take care // of triggering AR#onAnimationFinished on each ActivityRecord which is a part of the // app transition. - final ArrayList<ActivityRecord> transitioningDecendants = new ArrayList<>(); + final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>(); for (int j = 0; j < apps.size(); ++j) { final ActivityRecord app = apps.valueAt(j); if (app.isDescendantOf(wc)) { - transitioningDecendants.add(app); + transitioningDescendants.add(app); } } - wc.applyAnimation(animLp, transit, visible, voiceInteraction, (type, anim) -> { - for (int j = 0; j < transitioningDecendants.size(); ++j) { - transitioningDecendants.get(j).onAnimationFinished(type, anim); - } - }); + wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants); } } @@ -540,7 +535,14 @@ public class AppTransitionController { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now opening app %s", app); app.commitVisibility(true /* visible */, false /* performLayout */); - if (!app.isAnimating(PARENTS | CHILDREN)) { + + // In case a trampoline activity is used, it can happen that a new ActivityRecord is + // added and a new app transition starts before the previous app transition animation + // ends. So we cannot simply use app.isAnimating(PARENTS) to determine if the app must + // to be added to the list of tokens to be notified of app transition complete. + final WindowContainer wc = app.getAnimatingContainer(PARENTS, + ANIMATION_TYPE_APP_TRANSITION); + if (wc == null || !wc.getAnimationSources().contains(app)) { // This token isn't going to be animating. Add it to the list of tokens to // be notified of app transition complete since the notification will not be // sent be the app window animator. @@ -599,8 +601,7 @@ public class AppTransitionController { for (int i = 0; i < appsCount; i++) { WindowContainer wc = apps.valueAt(i); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now changing app %s", wc); - wc.applyAnimation(null, transit, true, false, - null /* animationFinishedCallback */); + wc.applyAnimation(null, transit, true, false, null /* sources */); } } diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java index 8b14095874e3..26e0790a7604 100644 --- a/services/core/java/com/android/server/wm/BarController.java +++ b/services/core/java/com/android/server/wm/BarController.java @@ -59,6 +59,7 @@ public class BarController { private final int mTransparentFlag; private final int mStatusBarManagerId; private final int mTranslucentWmFlag; + private final int mWindowType; protected final Handler mHandler; private final Object mServiceAquireLock = new Object(); private StatusBarManagerInternal mStatusBarInternal; @@ -77,13 +78,14 @@ public class BarController { private OnBarVisibilityChangedListener mVisibilityChangeListener; BarController(String tag, int displayId, int transientFlag, int unhideFlag, int translucentFlag, - int statusBarManagerId, int translucentWmFlag, int transparentFlag) { + int statusBarManagerId, int windowType, int translucentWmFlag, int transparentFlag) { mTag = "BarController." + tag; mDisplayId = displayId; mTransientFlag = transientFlag; mUnhideFlag = unhideFlag; mTranslucentFlag = translucentFlag; mStatusBarManagerId = statusBarManagerId; + mWindowType = windowType; mTranslucentWmFlag = translucentWmFlag; mTransparentFlag = transparentFlag; mHandler = new BarHandler(); @@ -168,7 +170,12 @@ public class BarController { } boolean isTransparentAllowed(WindowState win) { - return win == null || win.letterboxNotIntersectsOrFullyContains(mContentFrame); + if (win == null) { + return true; + } + final Rect rotatedContentFrame = win.mToken.getFixedRotationBarContentFrame(mWindowType); + final Rect contentFrame = rotatedContentFrame != null ? rotatedContentFrame : mContentFrame; + return win.letterboxNotIntersectsOrFullyContains(contentFrame); } boolean setBarShowingLw(final boolean show) { diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index fdcc3f4d1510..d43a7b87ee35 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -22,7 +22,6 @@ import static com.android.server.wm.AlphaAnimationSpecProto.TO; import static com.android.server.wm.AnimationSpecProto.ALPHA; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; -import android.annotation.Nullable; import android.graphics.Rect; import android.util.Log; import android.util.proto.ProtoOutputStream; @@ -31,7 +30,6 @@ import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.SurfaceAnimator.AnimationType; -import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import java.io.PrintWriter; @@ -160,8 +158,7 @@ class Dimmer { @VisibleForTesting interface SurfaceAnimatorStarter { void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, - AnimationAdapter anim, boolean hidden, @AnimationType int type, - @Nullable OnAnimationFinishedCallback animationFinishedCallback); + AnimationAdapter anim, boolean hidden, @AnimationType int type); } Dimmer(WindowContainer host) { @@ -348,7 +345,7 @@ class Dimmer { mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter( new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */, - ANIMATION_TYPE_DIMMER, null /* animationFinishedCallback */); + ANIMATION_TYPE_DIMMER); } private long getDimDuration(WindowContainer container) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4e19a5224bb4..c24c1e466133 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -32,6 +32,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.os.Build.VERSION_CODES.N; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.util.DisplayMetrics.DENSITY_DEFAULT; @@ -1526,12 +1527,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } /** - * Sets the provided record to {@link mFixedRotationLaunchingApp} if possible to apply fixed + * Sets the provided record to {@link #mFixedRotationLaunchingApp} if possible to apply fixed * rotation transform to it and indicate that the display may be rotated after it is launched. */ void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Surface.Rotation int rotation) { final WindowToken prevRotatedLaunchingApp = mFixedRotationLaunchingApp; - if (prevRotatedLaunchingApp != null && prevRotatedLaunchingApp == r + if (prevRotatedLaunchingApp == r && r.getWindowConfiguration().getRotation() == rotation) { // The given launching app and target rotation are the same as the existing ones. return; @@ -2175,6 +2176,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return (mDisplay.getFlags() & FLAG_PRIVATE) != 0; } + boolean isTrusted() { + return mDisplay.isTrusted(); + } + /** * Returns the topmost stack on the display that is compatible with the input windowing mode and * activity type. Null is no compatible stack on the display. @@ -3522,7 +3527,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } boolean canShowIme() { - if (isUntrustedVirtualDisplay()) { + if (!isTrusted()) { return false; } return mWmService.mDisplayWindowSettings.shouldShowImeLocked(this) @@ -4753,15 +4758,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // VR virtual display will be used to run and render 2D app within a VR experience. && mDisplayId != mWmService.mVr2dDisplayId // Do not show system decorations on untrusted virtual display. - && !isUntrustedVirtualDisplay(); - } - - /** - * @return {@code true} if the display is non-system created virtual display. - */ - boolean isUntrustedVirtualDisplay() { - return mDisplay.getType() == Display.TYPE_VIRTUAL - && mDisplay.getOwnerUid() != Process.SYSTEM_UID; + && isTrusted(); } /** @@ -5647,8 +5644,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } if (animatingRecents != null && animatingRecents == mFixedRotationLaunchingApp) { - // Because it won't affect display orientation, just finish the transform. - animatingRecents.finishFixedRotationTransform(); + // The recents activity should be going to be invisible (switch to another app or + // return to original top). Only clear the top launching record without finishing + // the transform immediately because it won't affect display orientation. And before + // the visibility is committed, the recents activity may perform relayout which may + // cause unexpected configuration change if the rotated configuration is restored. + // The transform will be finished when the transition is done. setFixedRotationLaunchingAppUnchecked(null); } else { // If there is already a launching activity that is not the recents, before its @@ -5659,6 +5660,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + /** + * Return {@code true} if there is an ongoing animation to the "Recents" activity and this + * activity as a fixed orientation so shouldn't be rotated. + */ + boolean isFixedOrientationRecentsAnimating() { + return mAnimatingRecents != null + && mAnimatingRecents.getRequestedConfigurationOrientation() + != ORIENTATION_UNDEFINED; + } + @Override public void onAppTransitionFinishedLocked(IBinder token) { final ActivityRecord r = getActivityRecord(token); @@ -5671,7 +5682,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } if (mFixedRotationLaunchingApp != null && mFixedRotationLaunchingApp.hasFixedRotationTransform(r)) { - continueUpdateOrientationForDiffOrienLaunchingApp(); + // Waiting until all of the associated activities have done animation, or the + // orientation would be updated too early and cause flickers. + if (!mFixedRotationLaunchingApp.hasAnimatingFixedRotationTransition()) { + continueUpdateOrientationForDiffOrienLaunchingApp(); + } } else { r.finishFixedRotationTransform(); } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index e244b5551d19..8cfe1cd5e98f 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -151,6 +151,7 @@ import android.util.IntArray; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; +import android.util.SparseArray; import android.view.DisplayCutout; import android.view.Gravity; import android.view.InputChannel; @@ -199,6 +200,7 @@ import com.android.server.wallpaper.WallpaperManagerInternal; import com.android.server.wm.utils.InsetUtils; import java.io.PrintWriter; +import java.util.function.Consumer; /** * The policy that provides the basic behaviors and states of a display to show UI. @@ -471,6 +473,7 @@ public class DisplayPolicy { View.NAVIGATION_BAR_UNHIDE, View.NAVIGATION_BAR_TRANSLUCENT, StatusBarManager.WINDOW_NAVIGATION_BAR, + TYPE_NAVIGATION_BAR, FLAG_TRANSLUCENT_NAVIGATION, View.NAVIGATION_BAR_TRANSPARENT); @@ -1171,6 +1174,11 @@ public class DisplayPolicy { displayFrames.mDisplayCutoutSafe.top); } + @VisibleForTesting + StatusBarController getStatusBarController() { + return mStatusBarController; + } + WindowState getStatusBar() { return mStatusBar; } @@ -1469,13 +1477,16 @@ public class DisplayPolicy { } private void simulateLayoutDecorWindow(WindowState win, DisplayFrames displayFrames, - InsetsState insetsState, WindowFrames simulatedWindowFrames, Runnable layout) { + InsetsState insetsState, WindowFrames simulatedWindowFrames, + SparseArray<Rect> contentFrames, Consumer<Rect> layout) { win.setSimulatedWindowFrames(simulatedWindowFrames); + final Rect contentFrame = new Rect(); try { - layout.run(); + layout.accept(contentFrame); } finally { win.setSimulatedWindowFrames(null); } + contentFrames.put(win.mAttrs.type, contentFrame); mDisplayContent.getInsetsStateController().computeSimulatedState(insetsState, win, displayFrames, simulatedWindowFrames); } @@ -1487,24 +1498,25 @@ public class DisplayPolicy { * state and some temporal states. In other words, it doesn't change the window frames used to * show on screen. */ - void simulateLayoutDisplay(DisplayFrames displayFrames, InsetsState insetsState, int uiMode) { + void simulateLayoutDisplay(DisplayFrames displayFrames, InsetsState insetsState, + SparseArray<Rect> barContentFrames) { displayFrames.onBeginLayout(); updateInsetsStateForDisplayCutout(displayFrames, insetsState); insetsState.setDisplayFrame(displayFrames.mUnrestricted); final WindowFrames simulatedWindowFrames = new WindowFrames(); if (mNavigationBar != null) { - simulateLayoutDecorWindow( - mNavigationBar, displayFrames, insetsState, simulatedWindowFrames, - () -> layoutNavigationBar(displayFrames, uiMode, mLastNavVisible, + simulateLayoutDecorWindow(mNavigationBar, displayFrames, insetsState, + simulatedWindowFrames, barContentFrames, + contentFrame -> layoutNavigationBar(displayFrames, + mDisplayContent.getConfiguration().uiMode, mLastNavVisible, mLastNavTranslucent, mLastNavAllowedHidden, - mLastNotificationShadeForcesShowingNavigation, - false /* isRealLayout */)); + mLastNotificationShadeForcesShowingNavigation, contentFrame)); } if (mStatusBar != null) { - simulateLayoutDecorWindow( - mStatusBar, displayFrames, insetsState, simulatedWindowFrames, - () -> layoutStatusBar(displayFrames, mLastSystemUiFlags, - false /* isRealLayout */)); + simulateLayoutDecorWindow(mStatusBar, displayFrames, insetsState, + simulatedWindowFrames, barContentFrames, + contentFrame -> layoutStatusBar(displayFrames, mLastSystemUiFlags, + contentFrame)); } layoutScreenDecorWindows(displayFrames, simulatedWindowFrames); postAdjustDisplayFrames(displayFrames); @@ -1556,9 +1568,10 @@ public class DisplayPolicy { boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, navVisible, navTranslucent, navAllowedHidden, notificationShadeForcesShowingNavigation, - true /* isRealLayout */); + null /* simulatedContentFrame */); if (DEBUG_LAYOUT) Slog.i(TAG, "mDock rect:" + displayFrames.mDock); - updateSysUiVisibility |= layoutStatusBar(displayFrames, sysui, true /* isRealLayout */); + updateSysUiVisibility |= layoutStatusBar(displayFrames, sysui, + null /* simulatedContentFrame */); if (updateSysUiVisibility) { updateSystemUiVisibilityLw(); } @@ -1579,10 +1592,9 @@ public class DisplayPolicy { navControlTarget instanceof WindowState ? (WindowState) navControlTarget : null; final InsetsState requestedState = navControllingWin != null ? navControllingWin.getRequestedInsetsState() : null; - final InsetsSource navSource = requestedState != null - ? requestedState.peekSource(ITYPE_NAVIGATION_BAR) : null; - final boolean navVisible = navSource != null - ? navSource.isVisible() : InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR); + final boolean navVisible = requestedState != null + ? requestedState.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR) + : InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR); final boolean showBarsByTouch = navControllingWin != null && navControllingWin.mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_BARS_BY_TOUCH; // When the navigation bar isn't visible, we put up a fake input window to catch all @@ -1731,7 +1743,8 @@ public class DisplayPolicy { displayFrames.mContent.set(dockFrame); } - private boolean layoutStatusBar(DisplayFrames displayFrames, int sysui, boolean isRealLayout) { + private boolean layoutStatusBar(DisplayFrames displayFrames, int sysui, + Rect simulatedContentFrame) { // decide where the status bar goes ahead of time if (mStatusBar == null) { return false; @@ -1754,12 +1767,14 @@ public class DisplayPolicy { displayFrames.mStable.top = Math.max(displayFrames.mStable.top, displayFrames.mDisplayCutoutSafe.top); - if (isRealLayout) { - // Tell the bar controller where the collapsed status bar content is. - sTmpRect.set(windowFrames.mContentFrame); - sTmpRect.intersect(displayFrames.mDisplayCutoutSafe); - sTmpRect.top = windowFrames.mContentFrame.top; // Ignore top display cutout inset - sTmpRect.bottom = displayFrames.mStable.top; // Use collapsed status bar size + // Tell the bar controller where the collapsed status bar content is. + sTmpRect.set(windowFrames.mContentFrame); + sTmpRect.intersect(displayFrames.mDisplayCutoutSafe); + sTmpRect.top = windowFrames.mContentFrame.top; // Ignore top display cutout inset + sTmpRect.bottom = displayFrames.mStable.top; // Use collapsed status bar size + if (simulatedContentFrame != null) { + simulatedContentFrame.set(sTmpRect); + } else { mStatusBarController.setContentFrame(sTmpRect); } @@ -1796,7 +1811,7 @@ public class DisplayPolicy { private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, boolean navVisible, boolean navTranslucent, boolean navAllowedHidden, - boolean statusBarForcesShowingNavigation, boolean isRealLayout) { + boolean statusBarForcesShowingNavigation, Rect simulatedContentFrame) { if (mNavigationBar == null) { return false; } @@ -1818,10 +1833,11 @@ public class DisplayPolicy { if (navBarPosition == NAV_BAR_BOTTOM) { // It's a system nav bar or a portrait screen; nav bar goes on bottom. - final int top = cutoutSafeUnrestricted.bottom - - getNavigationBarHeight(rotation, uiMode); final int topNavBar = cutoutSafeUnrestricted.bottom - getNavigationBarFrameHeight(rotation, uiMode); + final int top = mNavButtonForcedVisible + ? topNavBar + : cutoutSafeUnrestricted.bottom - getNavigationBarHeight(rotation, uiMode); navigationFrame.set(0, topNavBar, displayWidth, displayFrames.mUnrestricted.bottom); displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top; if (transientNavBarShowing) { @@ -1900,7 +1916,9 @@ public class DisplayPolicy { navigationFrame /* visibleFrame */, sTmpRect /* decorFrame */, navigationFrame /* stableFrame */); mNavigationBar.computeFrame(displayFrames); - if (isRealLayout) { + if (simulatedContentFrame != null) { + simulatedContentFrame.set(windowFrames.mContentFrame); + } else { mNavigationBarPosition = navBarPosition; mNavigationBarController.setContentFrame(windowFrames.mContentFrame); } @@ -2372,12 +2390,13 @@ public class DisplayPolicy { final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0 || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0 || (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL - && !win.getRequestedInsetsState().getSource(ITYPE_STATUS_BAR).isVisible()); + && !win.getRequestedInsetsState().getSourceOrDefaultVisibility( + ITYPE_STATUS_BAR)); final boolean requestedHideNavigation = (requestedSysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 || (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL - && !win.getRequestedInsetsState().getSource(ITYPE_NAVIGATION_BAR) - .isVisible()); + && !win.getRequestedInsetsState().getSourceOrDefaultVisibility( + ITYPE_NAVIGATION_BAR)); // TYPE_BASE_APPLICATION windows are never considered floating here because they don't get // cropped / shifted to the displayFrame in WindowState. @@ -3187,24 +3206,32 @@ public class DisplayPolicy { return; } if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) { - if (swipeTarget == mNavigationBar - && !getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR)) { - // Don't show status bar when swiping on already visible navigation bar - return; - } final InsetsSourceProvider provider = swipeTarget.getControllableInsetProvider(); final InsetsControlTarget controlTarget = provider != null ? provider.getControlTarget() : null; - // No transient mode on lockscreen (in notification shade window). if (controlTarget == null || controlTarget == getNotificationShade()) { + // No transient mode on lockscreen (in notification shade window). return; } + + if (swipeTarget == mNavigationBar + && !getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR)) { + // Don't show status bar when swiping on already visible navigation bar. + // But restore the position of navigation bar if it has been moved by the control + // target. + controlTarget.showInsets(Type.navigationBars(), false); + return; + } + + int insetsTypesToShow = Type.systemBars(); + if (controlTarget.canShowTransient()) { - mDisplayContent.getInsetsPolicy().showTransient(IntArray.wrap( + insetsTypesToShow &= ~mDisplayContent.getInsetsPolicy().showTransient(IntArray.wrap( new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR})); - } else { - controlTarget.showInsets(Type.systemBars(), false); + } + if (insetsTypesToShow != 0) { + controlTarget.showInsets(insetsTypesToShow, false); } } else { boolean sb = mStatusBarController.checkShowTransientBarLw(); diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 831491dd145e..f093fd34bcc0 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -430,6 +430,15 @@ public class DisplayRotation { "Deferring rotation, still finishing previous rotation"); return false; } + + if (mDisplayContent.mFixedRotationTransitionListener + .isFixedOrientationRecentsAnimating()) { + // During the recents animation, the closing app might still be considered on top. + // In order to ignore its requested orientation to avoid a sensor led rotation (e.g + // user rotating the device while the recents animation is running), we ignore + // rotation update while the animation is running. + return false; + } } if (!mService.mDisplayEnabled) { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 035f2015fe91..3d7873ad2f7c 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -42,6 +42,7 @@ import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl; import android.view.SyncRtSurfaceTransactionApplier; import android.view.ViewRootImpl; +import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimation.Bounds; import android.view.WindowInsetsAnimationControlListener; @@ -127,14 +128,16 @@ class InsetsPolicy { return provider != null && provider.hasWindow() && !provider.getSource().isVisible(); } - void showTransient(IntArray types) { + @InsetsType int showTransient(IntArray types) { + @InsetsType int showingTransientTypes = 0; boolean changed = false; for (int i = types.size() - 1; i >= 0; i--) { final int type = types.get(i); - if (mShowingTransientTypes.indexOf(type) != -1) { + if (!isHidden(type)) { continue; } - if (!isHidden(type)) { + showingTransientTypes |= InsetsState.toPublicType(type); + if (mShowingTransientTypes.indexOf(type) != -1) { continue; } mShowingTransientTypes.add(type); @@ -161,6 +164,7 @@ class InsetsPolicy { } }); } + return showingTransientTypes; } void hideTransient() { @@ -192,18 +196,6 @@ class InsetsPolicy { state = new InsetsState(state); state.setSourceVisible(mShowingTransientTypes.get(i), false); } - if (mFocusedWin != null && getStatusControlTarget(mFocusedWin) == mDummyControlTarget) { - if (state == originalState) { - state = new InsetsState(state); - } - state.setSourceVisible(ITYPE_STATUS_BAR, mFocusedWin.getRequestedInsetsState()); - } - if (mFocusedWin != null && getNavControlTarget(mFocusedWin) == mDummyControlTarget) { - if (state == originalState) { - state = new InsetsState(state); - } - state.setSourceVisible(ITYPE_NAVIGATION_BAR, mFocusedWin.getRequestedInsetsState()); - } return state; } @@ -373,7 +365,7 @@ class InsetsPolicy { final WindowState controllingWin = controlTarget instanceof WindowState ? (WindowState) controlTarget : null; setVisible(controllingWin == null - || controllingWin.getRequestedInsetsState().getSource(type).isVisible()); + || controllingWin.getRequestedInsetsState().getSourceOrDefaultVisibility(type)); } private void setVisible(boolean visible) { @@ -392,7 +384,7 @@ class InsetsPolicy { InsetsPolicyAnimationControlCallbacks mControlCallbacks; InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) { - super(show, false /* hasCallbacks */, types); + super(show, false /* hasCallbacks */, types, false /* disable */); mFinishCallback = finishCallback; mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this); } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 6a4975950f1a..c8d9fe0f0a65 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -280,7 +280,7 @@ class InsetsSourceProvider { } final Transaction t = mDisplayContent.getPendingTransaction(); mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */, - ANIMATION_TYPE_INSETS_CONTROL, null /* animationFinishedCallback */); + ANIMATION_TYPE_INSETS_CONTROL); // The leash was just created. We cannot dispatch it until its surface transaction is // applied. Otherwise, the client's operation to the leash might be overwritten by us. diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 77bc37f0c2d7..bf9a78489167 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -178,6 +178,7 @@ class InsetsStateController { if (imeSource != null && imeSource.isVisible()) { imeSource = new InsetsSource(imeSource); imeSource.setVisible(false); + imeSource.setFrame(0, 0, 0, 0); state = new InsetsState(state); state.addSource(imeSource); } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 1cd94b40f660..24bb7c8d5560 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1341,6 +1341,17 @@ class RecentTasks { break; } + // Tasks managed by/associated with an ActivityView should be excluded from recents. + // singleTaskInstance is set on the VirtualDisplay managed by ActivityView + // TODO(b/126185105): Find a different signal to use besides isSingleTaskInstance + final ActivityStack stack = task.getStack(); + if (stack != null) { + DisplayContent display = stack.getDisplay(); + if (display != null && display.isSingleTaskInstance()) { + return false; + } + } + // If we're in lock task mode, ignore the root task if (task == mService.getLockTaskController().getRootTask()) { return false; diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index bded65141a3a..178082016bbb 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -233,6 +233,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // duration of the gesture that is driven by the recents component targetActivity.mLaunchTaskBehind = true; mLaunchedTargetActivity = targetActivity; + // TODO(b/156772625): Evaluate to send new intents vs. replacing the intent extras. + targetActivity.intent.replaceExtras(mTargetIntent); // Fetch all the surface controls and pass them to the client to get the animation // started. Cancel any existing recents animation running synchronously (do not hold the diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java index 7a38bb65f73b..0ae9ca9b882e 100644 --- a/services/core/java/com/android/server/wm/ShellRoot.java +++ b/services/core/java/com/android/server/wm/ShellRoot.java @@ -104,7 +104,7 @@ public class ShellRoot { 0 /* windowCornerRadius */), mDisplayContent.mWmService.mSurfaceAnimationRunner); mToken.startAnimation(mToken.getPendingTransaction(), adapter, false /* hidden */, - ANIMATION_TYPE_WINDOW_ANIMATION, null /* animationFinishedCallback */); + ANIMATION_TYPE_WINDOW_ANIMATION); } WindowInfo getWindowInfo() { diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java index cac992a67541..3564e0bce5f5 100644 --- a/services/core/java/com/android/server/wm/StatusBarController.java +++ b/services/core/java/com/android/server/wm/StatusBarController.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; @@ -90,6 +91,7 @@ public class StatusBarController extends BarController { View.STATUS_BAR_UNHIDE, View.STATUS_BAR_TRANSLUCENT, StatusBarManager.WINDOW_STATUS_BAR, + TYPE_STATUS_BAR, FLAG_TRANSLUCENT_STATUS, View.STATUS_BAR_TRANSPARENT); } diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index 5633b6be87b9..837f1b523b68 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -239,9 +239,6 @@ class SurfaceAnimationRunner { } private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) { - if (a.mAnimSpec.needsEarlyWakeup()) { - t.setEarlyWakeup(); - } a.mAnimSpec.apply(t, a.mLeash, currentPlayTime); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3ee7ee7a4276..6670dbfea282 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -85,7 +85,6 @@ import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; -import static com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.TASK; @@ -1436,15 +1435,6 @@ class Task extends WindowContainer<WindowContainer> { mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged(); } - final boolean isRootTask = isRootTask(); - if (isRootTask) { - final DisplayContent display = getDisplayContent(); - if (display.isSingleTaskInstance()) { - mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId); - } - display.mDisplayContent.setLayoutNeeded(); - } - if (hasChild()) { updateEffectiveIntent(); @@ -1465,7 +1455,7 @@ class Task extends WindowContainer<WindowContainer> { } else if (!mReuseTask && !mCreatedByOrganizer) { // Remove entire task if it doesn't have any activity left and it isn't marked for reuse // or created by task organizer. - if (!isRootTask) { + if (!isRootTask()) { getStack().removeChild(this, reason); } EventLogTags.writeWmTaskRemoved(mTaskId, @@ -2648,7 +2638,7 @@ class Task extends WindowContainer<WindowContainer> { */ ActivityStack adjustFocusToNextFocusableTask(String reason) { return adjustFocusToNextFocusableTask(reason, false /* allowFocusSelf */, - true /* moveParentsToTop */); + true /* moveDisplayToTop */); } /** Return the next focusable task by looking from the siblings and parent tasks */ @@ -2671,11 +2661,11 @@ class Task extends WindowContainer<WindowContainer> { * Find next proper focusable task and make it focused. * @param reason The reason of making the adjustment. * @param allowFocusSelf Is the focus allowed to remain on the same task. - * @param moveParentsToTop Whether to move parents to top while making the task focused. + * @param moveDisplayToTop Whether to move display to top while making the task focused. * @return The root task that now got the focus, {@code null} if none found. */ ActivityStack adjustFocusToNextFocusableTask(String reason, boolean allowFocusSelf, - boolean moveParentsToTop) { + boolean moveDisplayToTop) { ActivityStack focusableTask = (ActivityStack) getNextFocusableTask(allowFocusSelf); if (focusableTask == null) { focusableTask = mRootWindowContainer.getNextFocusableStack((ActivityStack) this, @@ -2686,10 +2676,17 @@ class Task extends WindowContainer<WindowContainer> { } final ActivityStack rootTask = (ActivityStack) focusableTask.getRootTask(); - if (!moveParentsToTop) { - // Only move the next stack to top in its task container. + if (!moveDisplayToTop) { + // There may be multiple task layers above this task, so when relocating the task to the + // top, we should move this task and each of its parent task that below display area to + // the top of each layer. WindowContainer parent = focusableTask.getParent(); - parent.positionChildAt(POSITION_TOP, focusableTask, false /* includingParents */); + WindowContainer next = focusableTask; + do { + parent.positionChildAt(POSITION_TOP, next, false /* includingParents */); + next = parent; + parent = next.getParent(); + } while (next.asTask() != null && parent != null); return rootTask; } @@ -2817,6 +2814,10 @@ class Task extends WindowContainer<WindowContainer> { if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId); EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask"); + if (mDisplayContent != null && mDisplayContent.isSingleTaskInstance()) { + mAtmService.notifySingleTaskDisplayEmpty(mDisplayContent.mDisplayId); + } + // If applicable let the TaskOrganizer know the Task is vanishing. setTaskOrganizer(null); @@ -2923,6 +2924,19 @@ class Task extends WindowContainer<WindowContainer> { } boolean cropWindowsToStackBounds() { + // Don't crop HOME/RECENTS windows to stack bounds. This is because in split-screen + // they extend past their stack and sysui uses the stack surface to control cropping. + // TODO(b/158242495): get rid of this when drag/drop can use surface bounds. + if (isActivityTypeHome() || isActivityTypeRecents()) { + // Make sure this is the top-most non-organizer root task (if not top-most, it means + // another translucent task could be above this, so this needs to stay cropped. + final Task rootTask = getRootTask(); + final Task topNonOrgTask = + rootTask.mCreatedByOrganizer ? rootTask.getTopMostTask() : rootTask; + if (isDescendantOf(topNonOrgTask)) { + return false; + } + } return isResizeable(); } @@ -3500,7 +3514,7 @@ class Task extends WindowContainer<WindowContainer> { @Override protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, int transit, boolean isVoiceInteraction, - @Nullable OnAnimationFinishedCallback finishedCallback) { + @Nullable ArrayList<WindowContainer> sources) { final RecentsAnimationController control = mWmService.getRecentsAnimationController(); if (control != null) { // We let the transition to be controlled by RecentsAnimation, and callback task's @@ -3509,10 +3523,14 @@ class Task extends WindowContainer<WindowContainer> { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "applyAnimationUnchecked, control: %s, task: %s, transit: %s", control, asTask(), AppTransition.appTransitionToString(transit)); - control.addTaskToTargets(this, finishedCallback); + control.addTaskToTargets(this, (type, anim) -> { + for (int i = 0; i < sources.size(); ++i) { + sources.get(i).onAnimationFinished(type, anim); + } + }); } } else { - super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback); + super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources); } } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 102c2a6364f4..07e309e1126a 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; @@ -99,6 +100,9 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { private ActivityStack mRootPinnedTask; private ActivityStack mRootSplitScreenPrimaryTask; + // TODO(b/159029784): Remove when getStack() behavior is cleaned-up + private ActivityStack mRootRecentsTask; + private final ArrayList<ActivityStack> mTmpAlwaysOnTopStacks = new ArrayList<>(); private final ArrayList<ActivityStack> mTmpNormalStacks = new ArrayList<>(); private final ArrayList<ActivityStack> mTmpHomeStacks = new ArrayList<>(); @@ -163,6 +167,8 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { ActivityStack getStack(int windowingMode, int activityType) { if (activityType == ACTIVITY_TYPE_HOME) { return mRootHomeTask; + } else if (activityType == ACTIVITY_TYPE_RECENTS) { + return mRootRecentsTask; } if (windowingMode == WINDOWING_MODE_PINNED) { return mRootPinnedTask; @@ -199,6 +205,10 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { return mRootHomeTask; } + @Nullable ActivityStack getRootRecentsTask() { + return mRootRecentsTask; + } + ActivityStack getRootPinnedTask() { return mRootPinnedTask; } @@ -207,6 +217,15 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { return mRootSplitScreenPrimaryTask; } + ActivityStack getRootSplitScreenSecondaryTask() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + if (mChildren.get(i).inSplitScreenSecondaryWindowingMode()) { + return mChildren.get(i); + } + } + return null; + } + ArrayList<Task> getVisibleTasks() { final ArrayList<Task> visibleTasks = new ArrayList<>(); forAllTasks(task -> { @@ -237,6 +256,16 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } else { mRootHomeTask = stack; } + } else if (stack.isActivityTypeRecents()) { + if (mRootRecentsTask != null) { + if (!stack.isDescendantOf(mRootRecentsTask)) { + throw new IllegalArgumentException("addStackReferenceIfNeeded: recents stack=" + + mRootRecentsTask + " already exist on display=" + this + + " stack=" + stack); + } + } else { + mRootRecentsTask = stack; + } } if (!stack.isRootTask()) { @@ -264,6 +293,8 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { void removeStackReferenceIfNeeded(ActivityStack stack) { if (stack == mRootHomeTask) { mRootHomeTask = null; + } else if (stack == mRootRecentsTask) { + mRootRecentsTask = null; } else if (stack == mRootPinnedTask) { mRootPinnedTask = null; } else if (stack == mRootSplitScreenPrimaryTask) { @@ -299,8 +330,17 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { @Override void positionChildAt(int position, ActivityStack child, boolean includingParents) { - final boolean moveToTop = (position == POSITION_TOP || position == getChildCount()); + final boolean moveToTop = position >= getChildCount() - 1; final boolean moveToBottom = (position == POSITION_BOTTOM || position == 0); + + // Reset mPreferredTopFocusableStack before positioning to top or {@link + // ActivityStackSupervisor#updateTopResumedActivityIfNeeded()} won't update the top + // resumed activity. + final boolean wasContained = mChildren.contains(child); + if (moveToTop && wasContained && child.isFocusable()) { + mPreferredTopFocusableStack = null; + } + if (child.getWindowConfiguration().isAlwaysOnTop() && !moveToTop) { // This stack is always-on-top, override the default behavior. Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom"); @@ -313,7 +353,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } // We don't allow untrusted display to top when task stack moves to top, // until user tapping this display to change display position as top intentionally. - if (mDisplayContent.isUntrustedVirtualDisplay() && !getParent().isOnTop()) { + if (!mDisplayContent.isTrusted() && !getParent().isOnTop()) { includingParents = false; } final int targetPosition = findPositionForStack(position, child, false /* adding */); @@ -330,6 +370,17 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { child.updateTaskMovement(moveToTop); mDisplayContent.setLayoutNeeded(); + + // The insert position may be adjusted to non-top when there is always-on-top stack. Since + // the original position is preferred to be top, the stack should have higher priority when + // we are looking for top focusable stack. The condition {@code wasContained} restricts the + // preferred stack is set only when moving an existing stack to top instead of adding a new + // stack that may be too early (e.g. in the middle of launching or reparenting). + if (moveToTop && child.isFocusableAndVisible()) { + mPreferredTopFocusableStack = child; + } else if (mPreferredTopFocusableStack == child) { + mPreferredTopFocusableStack = null; + } } /** @@ -727,29 +778,10 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { "positionStackAt: Can only have one task on display=" + this); } - final boolean movingToTop = wasContained && position >= getStackCount() - 1; - // Reset mPreferredTopFocusableStack before positioning to top or {@link - // ActivityStackSupervisor#updateTopResumedActivityIfNeeded()} won't update the top - // resumed activity. - if (movingToTop && stack.isFocusable()) { - mPreferredTopFocusableStack = null; - } - // Since positionChildAt() is called during the creation process of pinned stacks, // ActivityStack#getStack() can be null. positionStackAt(position, stack, includingParents); - // The insert position may be adjusted to non-top when there is always-on-top stack. Since - // the original position is preferred to be top, the stack should have higher priority when - // we are looking for top focusable stack. The condition {@code wasContained} restricts the - // preferred stack is set only when moving an existing stack to top instead of adding a new - // stack that may be too early (e.g. in the middle of launching or reparenting). - if (movingToTop && stack.isFocusableAndVisible()) { - mPreferredTopFocusableStack = stack; - } else if (mPreferredTopFocusableStack == stack) { - mPreferredTopFocusableStack = null; - } - if (updateLastFocusedStackReason != null) { final ActivityStack currentFocusedStack = getFocusedStack(); if (currentFocusedStack != prevFocusedStack) { @@ -1497,8 +1529,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { @Nullable ActivityStack getOrCreateRootHomeTask(boolean onTop) { ActivityStack homeTask = getRootHomeTask(); - if (homeTask == null && mDisplayContent.supportsSystemDecorations() - && !mDisplayContent.isUntrustedVirtualDisplay()) { + if (homeTask == null && mDisplayContent.supportsSystemDecorations()) { homeTask = createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, onTop); } return homeTask; @@ -1741,21 +1772,23 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { // reparenting stack finished. // Keep the order from bottom to top. int numStacks = getStackCount(); + + final boolean splitScreenActivated = toDisplayArea.isSplitScreenModeActivated(); + final ActivityStack rootStack = splitScreenActivated ? toDisplayArea + .getTopStackInWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) : null; for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) { final ActivityStack stack = getStackAt(stackNdx); // Always finish non-standard type stacks. if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) { stack.finishAllActivitiesImmediately(); } else { - // If default display is in split-window mode, set windowing mode of the - // stack to split-screen secondary. Otherwise, set the windowing mode to - // undefined by default to let stack inherited the windowing mode from the - // new display. - final int windowingMode = toDisplayArea.isSplitScreenModeActivated() - ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - : WINDOWING_MODE_UNDEFINED; - stack.reparent(toDisplayArea, true /* onTop */); - stack.setWindowingMode(windowingMode); + // Reparent the stack to the root task of secondary-split-screen or display area. + stack.reparent(stack.supportsSplitScreenWindowingMode() && rootStack != null + ? rootStack : toDisplayArea, POSITION_TOP); + + // Set the windowing mode to undefined by default to let the stack inherited the + // windowing mode. + stack.setWindowingMode(WINDOWING_MODE_UNDEFINED); lastReparentedStack = stack; } // Stacks may be removed from this display. Ensure each stack will be processed @@ -1763,6 +1796,17 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { stackNdx -= numStacks - getStackCount(); numStacks = getStackCount(); } + if (lastReparentedStack != null && splitScreenActivated) { + if (!lastReparentedStack.supportsSplitScreenWindowingMode()) { + mAtmService.getTaskChangeNotificationController() + .notifyActivityDismissingDockedStack(); + toDisplayArea.onSplitScreenModeDismissed(lastReparentedStack); + } else if (rootStack != null) { + // update focus + rootStack.moveToFront("display-removed"); + } + } + mRemoved = true; return lastReparentedStack; diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 1a2672bd0132..51cf858715b4 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -28,6 +28,7 @@ import android.app.ActivityManager.TaskSnapshot; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.RecordingCanvas; @@ -37,8 +38,11 @@ import android.os.Environment; import android.os.Handler; import android.util.ArraySet; import android.util.Slog; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.SurfaceControl; import android.view.ThreadedRenderer; +import android.view.WindowInsets; import android.view.WindowManager.LayoutParams; import com.android.internal.annotations.VisibleForTesting; @@ -475,9 +479,12 @@ class TaskSnapshotController { final int color = ColorUtils.setAlphaComponent( task.getTaskDescription().getBackgroundColor(), 255); final LayoutParams attrs = mainWindow.getAttrs(); + final InsetsPolicy insetsPolicy = mainWindow.getDisplayContent().getInsetsPolicy(); + final InsetsState insetsState = insetsPolicy.getInsetsForDispatch(mainWindow); + final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrameLw(), insetsState); final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags, attrs.privateFlags, attrs.systemUiVisibility, task.getTaskDescription(), - mHighResTaskSnapshotScale, mainWindow.getRequestedInsetsState()); + mHighResTaskSnapshotScale, insetsState); final int taskWidth = task.getBounds().width(); final int taskHeight = task.getBounds().height(); final int width = (int) (taskWidth * mHighResTaskSnapshotScale); @@ -488,7 +495,7 @@ class TaskSnapshotController { node.setClipToBounds(false); final RecordingCanvas c = node.start(width, height); c.drawColor(color); - decorPainter.setInsets(mainWindow.getContentInsets(), mainWindow.getStableInsets()); + decorPainter.setInsets(systemBarInsets); decorPainter.drawDecors(c, null /* statusBarExcludeFrame */); node.end(c); final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height); @@ -593,6 +600,13 @@ class TaskSnapshotController { return 0; } + static Rect getSystemBarInsets(Rect frame, InsetsState state) { + return state.calculateInsets(frame, null /* ignoringVisibilityState */, + false /* isScreenRound */, false /* alwaysConsumeSystemBars */, + null /* displayCutout */, 0 /* legacySoftInputMode */, 0 /* legacySystemUiFlags */, + null /* typeSideMap */).getInsets(WindowInsets.Type.systemBars()).toRect(); + } + void dump(PrintWriter pw, String prefix) { pw.println(prefix + "mHighResTaskSnapshotScale=" + mHighResTaskSnapshotScale); mCache.dump(pw, prefix); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index e26f1e1fe06f..f1f576220a9a 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -39,10 +39,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; -import static com.android.internal.policy.DecorView.getColorViewLeftInset; -import static com.android.internal.policy.DecorView.getColorViewTopInset; import static com.android.internal.policy.DecorView.getNavigationBarRect; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; +import static com.android.server.wm.TaskSnapshotController.getSystemBarInsets; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -131,9 +130,8 @@ class TaskSnapshotSurface implements StartingSurface { private final IWindowSession mSession; private final WindowManagerService mService; private final Rect mTaskBounds; - private final Rect mStableInsets = new Rect(); - private final Rect mContentInsets = new Rect(); private final Rect mFrame = new Rect(); + private final Rect mSystemBarInsets = new Rect(); private TaskSnapshot mSnapshot; private final RectF mTmpSnapshotSize = new RectF(); private final RectF mTmpDstFrame = new RectF(); @@ -174,6 +172,7 @@ class TaskSnapshotSurface implements StartingSurface { final int windowFlags; final int windowPrivateFlags; final int currentOrientation; + final InsetsState insetsState; synchronized (service.mGlobalLock) { final WindowState mainWindow = activity.findMainWindow(); final Task task = activity.getTask(); @@ -241,6 +240,10 @@ class TaskSnapshotSurface implements StartingSurface { taskBounds = new Rect(); task.getBounds(taskBounds); currentOrientation = topFullscreenOpaqueWindow.getConfiguration().orientation; + + final InsetsPolicy insetsPolicy = topFullscreenOpaqueWindow.getDisplayContent() + .getInsetsPolicy(); + insetsState = insetsPolicy.getInsetsForDispatch(topFullscreenOpaqueWindow); } try { final int res = session.addToDisplay(window, window.mSeq, layoutParams, @@ -255,8 +258,7 @@ class TaskSnapshotSurface implements StartingSurface { } final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window, surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis, - windowFlags, windowPrivateFlags, taskBounds, - currentOrientation, topFullscreenOpaqueWindow.getRequestedInsetsState()); + windowFlags, windowPrivateFlags, taskBounds, currentOrientation, insetsState); window.setOuter(snapshotSurface); try { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1, @@ -266,7 +268,9 @@ class TaskSnapshotSurface implements StartingSurface { } catch (RemoteException e) { // Local call. } - snapshotSurface.setFrames(tmpFrame, tmpContentInsets, tmpStableInsets); + + final Rect systemBarInsets = getSystemBarInsets(tmpFrame, insetsState); + snapshotSurface.setFrames(tmpFrame, systemBarInsets); snapshotSurface.drawSnapshot(); return snapshotSurface; } @@ -315,13 +319,12 @@ class TaskSnapshotSurface implements StartingSurface { } @VisibleForTesting - void setFrames(Rect frame, Rect contentInsets, Rect stableInsets) { + void setFrames(Rect frame, Rect systemBarInsets) { mFrame.set(frame); - mContentInsets.set(contentInsets); - mStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets); mSizeMismatch = (mFrame.width() != mSnapshot.getSnapshot().getWidth() || mFrame.height() != mSnapshot.getSnapshot().getHeight()); - mSystemBarBackgroundPainter.setInsets(contentInsets, stableInsets); + mSystemBarBackgroundPainter.setInsets(systemBarInsets); } private void drawSnapshot() { @@ -453,9 +456,7 @@ class TaskSnapshotSurface implements StartingSurface { ); // However, we also need to make space for the navigation bar on the left side. - final int colorViewLeftInset = getColorViewLeftInset(mStableInsets.left, - mContentInsets.left); - frame.offset(colorViewLeftInset, 0); + frame.offset(mSystemBarInsets.left, 0); return frame; } @@ -540,8 +541,6 @@ class TaskSnapshotSurface implements StartingSurface { */ static class SystemBarBackgroundPainter { - private final Rect mContentInsets = new Rect(); - private final Rect mStableInsets = new Rect(); private final Paint mStatusBarPaint = new Paint(); private final Paint mNavigationBarPaint = new Paint(); private final int mStatusBarColor; @@ -551,6 +550,7 @@ class TaskSnapshotSurface implements StartingSurface { private final int mSysUiVis; private final float mScale; private final InsetsState mInsetsState; + private final Rect mSystemBarInsets = new Rect(); SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int sysUiVis, TaskDescription taskDescription, float scale, InsetsState insetsState) { @@ -576,9 +576,8 @@ class TaskSnapshotSurface implements StartingSurface { mInsetsState = insetsState; } - void setInsets(Rect contentInsets, Rect stableInsets) { - mContentInsets.set(contentInsets); - mStableInsets.set(stableInsets); + void setInsets(Rect systemBarInsets) { + mSystemBarInsets.set(systemBarInsets); } int getStatusBarColorViewHeight() { @@ -589,7 +588,7 @@ class TaskSnapshotSurface implements StartingSurface { mSysUiVis, mStatusBarColor, mWindowFlags, forceBarBackground) : STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) { - return (int) (getColorViewTopInset(mStableInsets.top, mContentInsets.top) * mScale); + return (int) (mSystemBarInsets.top * mScale); } else { return 0; } @@ -615,8 +614,7 @@ class TaskSnapshotSurface implements StartingSurface { int statusBarHeight) { if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0 && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) { - final int rightInset = (int) (DecorView.getColorViewRightInset(mStableInsets.right, - mContentInsets.right) * mScale); + final int rightInset = (int) (mSystemBarInsets.right * mScale); final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0; c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint); } @@ -625,8 +623,8 @@ class TaskSnapshotSurface implements StartingSurface { @VisibleForTesting void drawNavigationBarBackground(Canvas c) { final Rect navigationBarRect = new Rect(); - getNavigationBarRect(c.getWidth(), c.getHeight(), mStableInsets, mContentInsets, - navigationBarRect, mScale); + getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect, + mScale); final boolean visible = isNavigationBarColorViewVisible(); if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) { c.drawRect(navigationBarRect, mNavigationBarPaint); diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 92a9e30c2f0a..9d0bac9dd290 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -17,6 +17,10 @@ package com.android.server.wm; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE; @@ -52,6 +56,9 @@ public class WindowAnimator { /** Is any window animating? */ private boolean mLastRootAnimating; + /** True if we are running any animations that require expensive composition. */ + private boolean mRunningExpensiveAnimations; + final Choreographer.FrameCallback mAnimationFrameCallback; /** Time of current animation step. Reset on each iteration */ @@ -165,12 +172,8 @@ public class WindowAnimator { mService.mWatermark.drawIfNeeded(); } - SurfaceControl.mergeToGlobalTransaction(mTransaction); } catch (RuntimeException e) { Slog.wtf(TAG, "Unhandled exception in Window Manager", e); - } finally { - mService.closeSurfaceTransaction("WindowAnimator"); - ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate"); } final boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this); @@ -179,21 +182,36 @@ public class WindowAnimator { mService.mWindowPlacerLocked.requestTraversal(); } - final boolean rootAnimating = mService.mRoot.isAnimating(TRANSITION | CHILDREN); + final boolean rootAnimating = mService.mRoot.isAnimating(TRANSITION | CHILDREN /* flags */, + ANIMATION_TYPE_ALL /* typesToCheck */); if (rootAnimating && !mLastRootAnimating) { - // Usually app transitions but quite a load onto the system already (with all the things - // happening in app), so pause task snapshot persisting to not increase the load. - mService.mTaskSnapshotController.setPersisterPaused(true); Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0); } if (!rootAnimating && mLastRootAnimating) { mService.mWindowPlacerLocked.requestTraversal(); - mService.mTaskSnapshotController.setPersisterPaused(false); Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0); } - mLastRootAnimating = rootAnimating; + final boolean runningExpensiveAnimations = + mService.mRoot.isAnimating(TRANSITION | CHILDREN /* flags */, + ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_SCREEN_ROTATION + | ANIMATION_TYPE_RECENTS /* typesToCheck */); + if (runningExpensiveAnimations && !mRunningExpensiveAnimations) { + // Usually app transitions put quite a load onto the system already (with all the things + // happening in app), so pause task snapshot persisting to not increase the load. + mService.mTaskSnapshotController.setPersisterPaused(true); + mTransaction.setEarlyWakeupStart(); + } else if (!runningExpensiveAnimations && mRunningExpensiveAnimations) { + mService.mTaskSnapshotController.setPersisterPaused(false); + mTransaction.setEarlyWakeupEnd(); + } + mRunningExpensiveAnimations = runningExpensiveAnimations; + + SurfaceControl.mergeToGlobalTransaction(mTransaction); + mService.closeSurfaceTransaction("WindowAnimator"); + ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate"); + if (mRemoveReplacedWindows) { mService.mRoot.removeReplacedWindows(); mRemoveReplacedWindows = false; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 90156fd4f475..dd08f4208eca 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -64,6 +64,7 @@ import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; import android.os.Trace; +import android.util.ArraySet; import android.util.Pair; import android.util.Pools; import android.util.Slog; @@ -181,6 +182,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final SurfaceFreezer mSurfaceFreezer; protected final WindowManagerService mWmService; + /** + * Sources which triggered a surface animation on this container. An animation target can be + * promoted to higher level, for example, from a set of {@link ActivityRecord}s to + * {@link ActivityStack}. In this case, {@link ActivityRecord}s are set on this variable while + * the animation is running, and reset after finishing it. + */ + private final ArraySet<WindowContainer> mSurfaceAnimationSources = new ArraySet<>(); + private final Point mTmpPos = new Point(); protected final Point mLastSurfacePosition = new Point(); @@ -193,8 +202,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ private boolean mCommittedReparentToAnimationLeash; - private final Configuration mTmpConfig = new Configuration(); - /** Interface for {@link #isAnimating} to check which cases for the container is animating. */ public interface AnimationFlags { /** @@ -872,29 +879,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @see AnimationFlags#PARENTS * @see AnimationFlags#CHILDREN */ - boolean isAnimating(int flags, int typesToCheck) { - int animationType = mSurfaceAnimator.getAnimationType(); - if (mSurfaceAnimator.isAnimating() && (animationType & typesToCheck) > 0) { - return true; - } - if ((flags & TRANSITION) != 0 && isWaitingForTransitionStart()) { - return true; - } - if ((flags & PARENTS) != 0) { - final WindowContainer parent = getParent(); - if (parent != null && parent.isAnimating(flags & ~CHILDREN, typesToCheck)) { - return true; - } - } - if ((flags & CHILDREN) != 0) { - for (int i = 0; i < mChildren.size(); ++i) { - final WindowContainer wc = mChildren.get(i); - if (wc.isAnimating(flags & ~PARENTS, typesToCheck)) { - return true; - } - } - } - return false; + final boolean isAnimating(int flags, int typesToCheck) { + return getAnimatingContainer(flags, typesToCheck) != null; } /** @@ -904,16 +890,20 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * checking animating status. * @param typesToExclude The combination of bitmask {@link AnimationType} to exclude when * checking if animating. + * + * @deprecated Use {@link #isAnimating(int, int)} */ - boolean isAnimatingExcluding(int flags, int typesToExclude) { + @Deprecated + final boolean isAnimatingExcluding(int flags, int typesToExclude) { return isAnimating(flags, ANIMATION_TYPE_ALL & ~typesToExclude); } /** - * @see #isAnimating(int, int) + * @deprecated Use {@link #isAnimating(int, int)} * TODO (b/152333373): Migrate calls to use isAnimating with specified animation type */ - boolean isAnimating(int flags) { + @Deprecated + final boolean isAnimating(int flags) { return isAnimating(flags, ANIMATION_TYPE_ALL); } @@ -1042,7 +1032,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @return Whether this child is on top of the window hierarchy. */ boolean isOnTop() { - return getParent().getTopChild() == this && getParent().isOnTop(); + final WindowContainer parent = getParent(); + return parent != null && parent.getTopChild() == this && parent.isOnTop(); } /** Returns the top child container. */ @@ -2107,10 +2098,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } void cancelAnimation() { + doAnimationFinished(mSurfaceAnimator.getAnimationType(), mSurfaceAnimator.getAnimation()); mSurfaceAnimator.cancelAnimation(); mSurfaceFreezer.unfreeze(getPendingTransaction()); } + ArraySet<WindowContainer> getAnimationSources() { + return mSurfaceAnimationSources; + } + @Override public SurfaceControl getFreezeSnapshotTarget() { return null; @@ -2156,6 +2152,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @param transit The app transition type indicates what kind of transition to be applied. * @param enter Whether the app transition is entering transition or not. * @param isVoiceInteraction Whether the container is participating in voice interaction or not. + * @param sources {@link ActivityRecord}s which causes this app transition animation. * * @return {@code true} when the container applied the app transition, {@code false} if the * app transition is disabled or skipped. @@ -2163,7 +2160,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @see #getAnimationAdapter */ boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - boolean isVoiceInteraction, @Nullable OnAnimationFinishedCallback finishedCallback) { + boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) { if (mWmService.mDisableTransitionAnimation) { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: transition animation is disabled or skipped. " @@ -2178,7 +2175,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation"); if (okToAnimate()) { - applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback); + applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources); } else { cancelAnimation(); } @@ -2276,14 +2273,17 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, int transit, boolean isVoiceInteraction, - @Nullable OnAnimationFinishedCallback finishedCallback) { + @Nullable ArrayList<WindowContainer> sources) { final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, transit, enter, isVoiceInteraction); AnimationAdapter adapter = adapters.first; AnimationAdapter thumbnailAdapter = adapters.second; if (adapter != null) { + if (sources != null) { + mSurfaceAnimationSources.addAll(sources); + } startAnimation(getPendingTransaction(), adapter, !isVisible(), - ANIMATION_TYPE_APP_TRANSITION, finishedCallback); + ANIMATION_TYPE_APP_TRANSITION); if (adapter.getShowWallpaper()) { getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } @@ -2411,10 +2411,18 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< updateSurfacePosition(t); } + private void doAnimationFinished(@AnimationType int type, AnimationAdapter anim) { + for (int i = 0; i < mSurfaceAnimationSources.size(); ++i) { + mSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim); + } + mSurfaceAnimationSources.clear(); + } + /** * Called when an animation has finished running. */ protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) { + doAnimationFinished(type, anim); mWmService.onAnimationFinished(); } @@ -2428,16 +2436,66 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< /** * @return The {@link WindowContainer} which is running an animation. * - * It traverses from the current container to its parents recursively. If nothing is animating, - * it will return {@code null}. + * By default this only checks if this container itself is actually running an animation, but + * you can extend the check target over its relatives, or relax the condition so that this can + * return {@code WindowContainer} if an animation starts soon by giving a combination + * of {@link AnimationFlags}. + * + * Note that you can give a combination of bitmask flags to specify targets and condition for + * checking animating status. + * e.g. {@code isAnimating(TRANSITION | PARENT)} returns {@code true} if either this + * container itself or one of its parents is running an animation or waiting for an app + * transition. + * + * Note that TRANSITION propagates to parents and children as well. + * + * @param flags The combination of bitmask flags to specify targets and condition for + * checking animating status. + * @param typesToCheck The combination of bitmask {@link AnimationType} to compare when + * determining if animating. + * + * @see AnimationFlags#TRANSITION + * @see AnimationFlags#PARENTS + * @see AnimationFlags#CHILDREN */ @Nullable - WindowContainer getAnimatingContainer() { - if (isAnimating()) { + WindowContainer getAnimatingContainer(int flags, int typesToCheck) { + int animationType = mSurfaceAnimator.getAnimationType(); + if (mSurfaceAnimator.isAnimating() && (animationType & typesToCheck) > 0) { return this; } - final WindowContainer parent = getParent(); - return (parent != null) ? parent.getAnimatingContainer() : null; + if ((flags & TRANSITION) != 0 && isWaitingForTransitionStart()) { + return this; + } + if ((flags & PARENTS) != 0) { + final WindowContainer parent = getParent(); + if (parent != null) { + final WindowContainer wc = parent.getAnimatingContainer( + flags & ~CHILDREN, typesToCheck); + if (wc != null) { + return wc; + } + } + } + if ((flags & CHILDREN) != 0) { + for (int i = 0; i < mChildren.size(); ++i) { + final WindowContainer wc = mChildren.get(i).getAnimatingContainer( + flags & ~PARENTS, typesToCheck); + if (wc != null) { + return wc; + } + } + } + return null; + } + + /** + * @deprecated Use {@link #getAnimatingContainer(int, int)} instead. + */ + @Nullable + @Deprecated + final WindowContainer getAnimatingContainer() { + return getAnimatingContainer(PARENTS, ANIMATION_TYPE_ALL); } /** diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java index a27a11259772..126154b10350 100644 --- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java +++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java @@ -133,15 +133,14 @@ class WindowContainerThumbnail implements Animatable { mWindowContainer.getDisplayContent().mAppTransition.canSkipFirstFrame(), mWindowContainer.getDisplayContent().getWindowCornerRadius()), mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */, - ANIMATION_TYPE_RECENTS, null /* animationFinishedCallback */); + ANIMATION_TYPE_RECENTS); } /** * Start animation with existing adapter. */ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) { - mSurfaceAnimator.startAnimation(t, anim, hidden, ANIMATION_TYPE_RECENTS, - null /* animationFinishedCallback */); + mSurfaceAnimator.startAnimation(t, anim, hidden, ANIMATION_TYPE_RECENTS); } private void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0590288a7f8b..4718b59f3bfe 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -369,7 +369,8 @@ public class WindowManagerService extends IWindowManager.Stub static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L; // Poll interval in milliseconds for watching boot animation finished. - private static final int BOOT_ANIMATION_POLL_INTERVAL = 200; + // TODO(b/159045990) Migrate to SystemService.waitForState with dedicated thread. + private static final int BOOT_ANIMATION_POLL_INTERVAL = 50; // The name of the boot animation service in init.rc. private static final String BOOT_ANIMATION_SERVICE = "bootanim"; @@ -7150,9 +7151,6 @@ public class WindowManagerService extends IWindowManager.Stub + "not exist: %d", displayId); return false; } - if (displayContent.isUntrustedVirtualDisplay()) { - return false; - } return displayContent.supportsSystemDecorations(); } } @@ -7171,7 +7169,7 @@ public class WindowManagerService extends IWindowManager.Stub + "does not exist: %d", displayId); return; } - if (displayContent.isUntrustedVirtualDisplay()) { + if (!displayContent.isTrusted()) { throw new SecurityException("Attempted to set system decors flag to an " + "untrusted virtual display: " + displayId); } @@ -7219,7 +7217,7 @@ public class WindowManagerService extends IWindowManager.Stub + "exist: %d", displayId); return; } - if (displayContent.isUntrustedVirtualDisplay()) { + if (!displayContent.isTrusted()) { throw new SecurityException("Attempted to set IME flag to an untrusted " + "virtual display: " + displayId); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index fe3ee50c34c5..5fc519c86f11 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1529,6 +1529,29 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } /** + * This is a form of rectangle "difference". It cut off each dimension of rect by the amount + * that toRemove is "pushing into" it from the outside. Any dimension that fully contains + * toRemove won't change. + */ + private void cutRect(Rect rect, Rect toRemove) { + if (toRemove.isEmpty()) return; + if (toRemove.top < rect.bottom && toRemove.bottom > rect.top) { + if (toRemove.right >= rect.right && toRemove.left >= rect.left) { + rect.right = toRemove.left; + } else if (toRemove.left <= rect.left && toRemove.right <= rect.right) { + rect.left = toRemove.right; + } + } + if (toRemove.left < rect.right && toRemove.right > rect.left) { + if (toRemove.bottom >= rect.bottom && toRemove.top >= rect.top) { + rect.bottom = toRemove.top; + } else if (toRemove.top <= rect.top && toRemove.bottom <= rect.bottom) { + rect.top = toRemove.bottom; + } + } + } + + /** * Retrieves the visible bounds of the window. * @param bounds The rect which gets the bounds. */ @@ -1544,6 +1567,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } else { intersectWithStackBounds = false; } + if (inSplitScreenPrimaryWindowingMode()) { + // If this is in the primary split and the home stack is the top visible task in + // the secondary split, it means this is "minimized" and thus must prevent + // overlapping with home. + // TODO(b/158242495): get rid of this when drag/drop can use surface bounds. + final ActivityStack rootSecondary = + task.getDisplayArea().getRootSplitScreenSecondaryTask(); + if (rootSecondary.isActivityTypeHome() || rootSecondary.isActivityTypeRecents()) { + final WindowContainer topTask = rootSecondary.getTopChild(); + if (topTask.isVisible()) { + cutRect(mTmpRect, topTask.getBounds()); + } + } + } } bounds.set(mWindowFrames.mVisibleFrame); @@ -2302,6 +2339,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } + if (inPinnedWindowingMode()) { + return false; + } + final boolean windowsAreFocusable = mActivityRecord == null || mActivityRecord.windowsAreFocusable(); if (!windowsAreFocusable) { // This window can't be an IME target if the app's windows should not be focusable. @@ -2793,7 +2834,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Do not allow untrusted virtual display to receive keys unless user intentionally // touches the display. return fromUserTouch || getDisplayContent().isOnTop() - || !getDisplayContent().isUntrustedVirtualDisplay(); + || getDisplayContent().isTrusted(); } @Override @@ -3375,6 +3416,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private void setTouchableRegionCropIfNeeded(InputWindowHandle handle) { final Task task = getTask(); if (task == null || !task.cropWindowsToStackBounds()) { + handle.setTouchableRegionCrop(null); return; } @@ -3576,6 +3618,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void notifyInsetsControlChanged() { ProtoLog.d(WM_DEBUG_IME, "notifyInsetsControlChanged for %s ", this); + if (mAppDied || mRemoved) { + return; + } final InsetsStateController stateController = getDisplayContent().getInsetsStateController(); try { @@ -5049,16 +5094,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWindowFrames.updateLastInsetValues(); } + @Nullable @Override - boolean isAnimating(int flags, int typesToCheck) { - - // If we are an inset provider, all our animations are driven by the inset client, so we - // aren't really animating. - // TODO: Replace this with a proper animation type system. + WindowContainer<WindowState> getAnimatingContainer(int flags, int typesToCheck) { if (mControllableInsetProvider != null) { - return false; + return null; } - return super.isAnimating(flags, typesToCheck); + return super.getAnimatingContainer(flags, typesToCheck); } void startAnimation(Animation anim) { @@ -5101,8 +5143,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } private void startAnimation(Transaction t, AnimationAdapter adapter) { - startAnimation(t, adapter, mWinAnimator.mLastHidden, ANIMATION_TYPE_WINDOW_ANIMATION, - null /* animationFinishedCallback */); + startAnimation(t, adapter, mWinAnimator.mLastHidden, ANIMATION_TYPE_WINDOW_ANIMATION); } @Override @@ -5122,17 +5163,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx; float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy; float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy; - int x = mSurfacePosition.x; - int y = mSurfacePosition.y; + transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets); + int x = mSurfacePosition.x + mTmpPoint.x; + int y = mSurfacePosition.y + mTmpPoint.y; // We might be on a display which has been re-parented to a view in another window, so here // computes the global location of our display. DisplayContent dc = getDisplayContent(); while (dc != null && dc.getParentWindow() != null) { final WindowState displayParent = dc.getParentWindow(); - x += displayParent.mWindowFrames.mFrame.left - displayParent.mAttrs.surfaceInsets.left + x += displayParent.mWindowFrames.mFrame.left + (dc.getLocationInParentWindow().x * displayParent.mGlobalScale + 0.5f); - y += displayParent.mWindowFrames.mFrame.top - displayParent.mAttrs.surfaceInsets.top + y += displayParent.mWindowFrames.mFrame.top + (dc.getLocationInParentWindow().y * displayParent.mGlobalScale + 0.5f); dc = displayParent.getDisplayContent(); } @@ -5390,6 +5432,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final WindowState imeTarget = getDisplayContent().mInputMethodTarget; boolean inTokenWithAndAboveImeTarget = imeTarget != null && imeTarget != this && imeTarget.mToken == mToken + && mAttrs.type != TYPE_APPLICATION_STARTING && getParent() != null && imeTarget.compareTo(this) <= 0; return inTokenWithAndAboveImeTarget; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index c0252363a159..8115ac8c6bef 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -247,10 +247,6 @@ class WindowStateAnimator { private final SurfaceControl.Transaction mPostDrawTransaction = new SurfaceControl.Transaction(); - // Used to track whether we have called detach children on the way to invisibility, in which - // case we need to give the client a new Surface if it lays back out to a visible state. - boolean mChildrenDetached = false; - // Set to true after the first frame of the Pinned stack animation // and reset after the last to ensure we only reset mForceScaleUntilResize // once per animation. @@ -425,7 +421,8 @@ class WindowStateAnimator { // transparent to the app. // If the children are detached, we don't want to reparent them to the new surface. // Instead let the children get removed when the old surface is deleted. - if (mSurfaceController != null && mPendingDestroySurface != null && !mChildrenDetached + if (mSurfaceController != null && mPendingDestroySurface != null + && !mPendingDestroySurface.mChildrenDetached && (mWin.mActivityRecord == null || !mWin.mActivityRecord.isRelaunching())) { mPostDrawTransaction.reparentChildren( mPendingDestroySurface.getClientViewRootSurface(), @@ -461,7 +458,6 @@ class WindowStateAnimator { if (mSurfaceController != null) { return mSurfaceController; } - mChildrenDetached = false; if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) { windowType = SurfaceControl.WINDOW_TYPE_DONT_SCREENSHOT; @@ -1365,7 +1361,7 @@ class WindowStateAnimator { mPostDrawTransaction.reparent(pendingSurfaceControl, null); // If the children are detached, we don't want to reparent them to the new surface. // Instead let the children get removed when the old surface is deleted. - if (!mChildrenDetached) { + if (!mPendingDestroySurface.mChildrenDetached) { mPostDrawTransaction.reparentChildren( mPendingDestroySurface.getClientViewRootSurface(), mSurfaceController.mSurfaceControl); @@ -1596,7 +1592,6 @@ class WindowStateAnimator { if (mSurfaceController != null) { mSurfaceController.detachChildren(); } - mChildrenDetached = true; // If the children are detached, it means the app is exiting. We don't want to tear the // content down too early, otherwise we could end up with a flicker. By preserving the // current surface, we ensure the content remains on screen until the window is completely diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 0a7ca5a0cf35..b2bfcdc8a900 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -90,6 +90,9 @@ class WindowSurfaceController { private final SurfaceControl.Transaction mTmpTransaction; + // Used to track whether we have called detach children on the way to invisibility. + boolean mChildrenDetached; + WindowSurfaceController(String name, int w, int h, int format, int flags, WindowStateAnimator animator, int windowType, int ownerUid) { mAnimator = animator; @@ -144,6 +147,7 @@ class WindowSurfaceController { void detachChildren() { ProtoLog.i(WM_SHOW_TRANSACTIONS, "SEVER CHILDREN"); + mChildrenDetached = true; if (mSurfaceControl != null) { mSurfaceControl.detachChildren(); } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index d2570023f419..86aacf308068 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -48,6 +48,7 @@ import android.os.Debug; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.DisplayAdjustments.FixedRotationAdjustments; import android.view.DisplayInfo; @@ -124,7 +125,7 @@ class WindowToken extends WindowContainer<WindowState> { private static class FixedRotationTransformState { final DisplayInfo mDisplayInfo; final DisplayFrames mDisplayFrames; - final InsetsState mInsetsState; + final InsetsState mInsetsState = new InsetsState(); final Configuration mRotatedOverrideConfiguration; final SeamlessRotator mRotator; /** @@ -133,14 +134,14 @@ class WindowToken extends WindowContainer<WindowState> { */ final ArrayList<WindowToken> mAssociatedTokens = new ArrayList<>(3); final ArrayList<WindowContainer<?>> mRotatedContainers = new ArrayList<>(3); + final SparseArray<Rect> mBarContentFrames = new SparseArray<>(); boolean mIsTransforming = true; FixedRotationTransformState(DisplayInfo rotatedDisplayInfo, - DisplayFrames rotatedDisplayFrames, InsetsState rotatedInsetsState, - Configuration rotatedConfig, int currentRotation) { + DisplayFrames rotatedDisplayFrames, Configuration rotatedConfig, + int currentRotation) { mDisplayInfo = rotatedDisplayInfo; mDisplayFrames = rotatedDisplayFrames; - mInsetsState = rotatedInsetsState; mRotatedOverrideConfiguration = rotatedConfig; // This will use unrotate as rotate, so the new and old rotation are inverted. mRotator = new SeamlessRotator(rotatedDisplayInfo.rotation, currentRotation, @@ -516,6 +517,12 @@ class WindowToken extends WindowContainer<WindowState> { : null; } + Rect getFixedRotationBarContentFrame(int windowType) { + return isFixedRotationTransforming() + ? mFixedRotationTransformState.mBarContentFrames.get(windowType) + : null; + } + InsetsState getFixedRotationTransformInsetsState() { return isFixedRotationTransforming() ? mFixedRotationTransformState.mInsetsState : null; } @@ -526,12 +533,12 @@ class WindowToken extends WindowContainer<WindowState> { if (mFixedRotationTransformState != null) { return; } - final InsetsState insetsState = new InsetsState(); - mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames, insetsState, - mDisplayContent.getConfiguration().uiMode); mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames, - insetsState, new Configuration(config), mDisplayContent.getRotation()); + new Configuration(config), mDisplayContent.getRotation()); mFixedRotationTransformState.mAssociatedTokens.add(this); + mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames, + mFixedRotationTransformState.mInsetsState, + mFixedRotationTransformState.mBarContentFrames); onConfigurationChanged(getParent().getConfiguration()); notifyFixedRotationTransform(true /* enabled */); } @@ -554,6 +561,25 @@ class WindowToken extends WindowContainer<WindowState> { notifyFixedRotationTransform(true /* enabled */); } + /** + * Return {@code true} if one of the associated activity is still animating. Otherwise, + * return {@code false}. + */ + boolean hasAnimatingFixedRotationTransition() { + if (mFixedRotationTransformState == null) { + return false; + } + + for (int i = mFixedRotationTransformState.mAssociatedTokens.size() - 1; i >= 0; i--) { + final ActivityRecord r = + mFixedRotationTransformState.mAssociatedTokens.get(i).asActivityRecord(); + if (r != null && r.isAnimating(TRANSITION | PARENTS)) { + return true; + } + } + return false; + } + void finishFixedRotationTransform() { finishFixedRotationTransform(null /* applyDisplayRotation */); } diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp index 098b2ef6439d..4e1a23416330 100644 --- a/services/core/jni/com_android_server_tv_TvInputHal.cpp +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -301,6 +301,7 @@ private: JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput, const sp<Looper>& looper); Mutex mLock; + Mutex mStreamLock; jweak mThiz; sp<Looper> mLooper; @@ -338,6 +339,7 @@ JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Loo } int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) { + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); if (connections.indexOfKey(streamId) < 0) { connections.add(streamId, Connection()); @@ -412,6 +414,7 @@ int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface> } int JTvInputHal::removeStream(int deviceId, int streamId) { + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); if (connections.indexOfKey(streamId) < 0) { return BAD_VALUE; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 10ad07cff847..401649a2e522 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -631,7 +631,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * Whether or not device admin feature is supported. If it isn't return defaults for all - * public methods. + * public methods, unless the caller has the appropriate permission for a particular method. */ final boolean mHasFeature; @@ -6032,7 +6032,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void lockNow(int flags, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature && mContext.checkCallingPermission(android.Manifest.permission.LOCK_DEVICE) + != PackageManager.PERMISSION_GRANTED) { return; } @@ -12711,7 +12712,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { for (ResolveInfo receiver : receivers) { final String packageName = receiver.getComponentInfo().packageName; if (checkCrossProfilePackagePermissions(packageName, userId, - requiresPermission)) { + requiresPermission) + || checkModifyQuietModePermission(packageName, userId)) { Slog.i(LOG_TAG, String.format("Sending %s broadcast to %s.", intent.getAction(), packageName)); @@ -12729,6 +12731,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } /** + * Checks whether the package {@code packageName} has the {@code MODIFY_QUIET_MODE} + * permission granted for the user {@code userId}. + */ + private boolean checkModifyQuietModePermission(String packageName, @UserIdInt int userId) { + try { + final int uid = Objects.requireNonNull( + mInjector.getPackageManager().getApplicationInfoAsUser( + Objects.requireNonNull(packageName), /* flags= */ 0, userId)).uid; + return PackageManager.PERMISSION_GRANTED + == ActivityManager.checkComponentPermission( + android.Manifest.permission.MODIFY_QUIET_MODE, uid, /* owningUid= */ + -1, /* exported= */ true); + } catch (NameNotFoundException ex) { + Slog.w(LOG_TAG, + String.format("Cannot find the package %s to check for permissions.", + packageName)); + return false; + } + } + + /** * Checks whether the package {@code packageName} has the required permissions to receive * cross-profile broadcasts on behalf of the user {@code userId}. */ diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index e790a196ad64..7132706c4ef1 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -165,6 +165,11 @@ binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) { return ok(); } +binder::Status BinderIncrementalService::disableReadLogs(int32_t storageId) { + mImpl.disableReadLogs(storageId); + return ok(); +} + binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, const std::string& path, int32_t* _aidl_return) { *_aidl_return = mImpl.makeDir(storageId, path); diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index 68549f5a8ff8..10154946d3ee 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -74,7 +74,7 @@ public: std::vector<uint8_t>* _aidl_return) final; binder::Status startLoading(int32_t storageId, bool* _aidl_return) final; binder::Status deleteStorage(int32_t storageId) final; - + binder::Status disableReadLogs(int32_t storageId) final; binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath, const std::string& libDirRelativePath, const std::string& abi, bool extractNativeLibs, diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 885f4d2d34d7..3450c3ae9fb3 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -60,6 +60,7 @@ struct Constants { static constexpr auto storagePrefix = "st"sv; static constexpr auto mountpointMdPrefix = ".mountpoint."sv; static constexpr auto infoMdName = ".info"sv; + static constexpr auto readLogsDisabledMarkerName = ".readlogs_disabled"sv; static constexpr auto libDir = "lib"sv; static constexpr auto libSuffix = ".so"sv; static constexpr auto blockSize = 4096; @@ -172,6 +173,13 @@ std::string makeBindMdName() { return name; } + +static bool checkReadLogsDisabledMarker(std::string_view root) { + const auto markerPath = path::c_str(path::join(root, constants().readLogsDisabledMarkerName)); + struct stat st; + return (::stat(markerPath, &st) == 0); +} + } // namespace IncrementalService::IncFsMount::~IncFsMount() { @@ -618,6 +626,32 @@ StorageId IncrementalService::findStorageId(std::string_view path) const { return it->second->second.storage; } +void IncrementalService::disableReadLogs(StorageId storageId) { + std::unique_lock l(mLock); + const auto ifs = getIfsLocked(storageId); + if (!ifs) { + LOG(ERROR) << "disableReadLogs failed, invalid storageId: " << storageId; + return; + } + if (!ifs->readLogsEnabled()) { + return; + } + ifs->disableReadLogs(); + l.unlock(); + + const auto metadata = constants().readLogsDisabledMarkerName; + if (auto err = mIncFs->makeFile(ifs->control, + path::join(ifs->root, constants().mount, + constants().readLogsDisabledMarkerName), + 0777, idFromMetadata(metadata), {})) { + //{.metadata = {metadata.data(), (IncFsSize)metadata.size()}})) { + LOG(ERROR) << "Failed to make marker file for storageId: " << storageId; + return; + } + + setStorageParams(storageId, /*enableReadLogs=*/false); +} + int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) { const auto ifs = getIfs(storageId); if (!ifs) { @@ -627,6 +661,11 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog const auto& params = ifs->dataLoaderStub->params(); if (enableReadLogs) { + if (!ifs->readLogsEnabled()) { + LOG(ERROR) << "setStorageParams failed, readlogs disabled for storageId: " << storageId; + return -EPERM; + } + if (auto status = mAppOpsManager->checkPermission(kDataUsageStats, kOpUsage, params.packageName.c_str()); !status.isOk()) { @@ -1072,6 +1111,11 @@ std::unordered_set<std::string_view> IncrementalService::adoptMountedInstances() std::move(control), *this); cleanupFiles.release(); // ifs will take care of that now + // Check if marker file present. + if (checkReadLogsDisabledMarker(root)) { + ifs->disableReadLogs(); + } + std::vector<std::pair<std::string, metadata::BindPoint>> permanentBindPoints; auto d = openDir(root); while (auto e = ::readdir(d.get())) { @@ -1243,6 +1287,11 @@ bool IncrementalService::mountExistingImage(std::string_view root) { ifs->mountId = mount.storage().id(); mNextId = std::max(mNextId, ifs->mountId + 1); + // Check if marker file present. + if (checkReadLogsDisabledMarker(mountTarget)) { + ifs->disableReadLogs(); + } + // DataLoader params DataLoaderParamsParcel dataLoaderParams; { diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index 918531b7921c..a6cc94639c8a 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -94,6 +94,10 @@ public: Permanent = 1, }; + enum StorageFlags { + ReadLogsEnabled = 1, + }; + static FileId idFromMetadata(std::span<const uint8_t> metadata); static inline FileId idFromMetadata(std::span<const char> metadata) { return idFromMetadata({(const uint8_t*)metadata.data(), metadata.size()}); @@ -116,6 +120,7 @@ public: int unbind(StorageId storage, std::string_view target); void deleteStorage(StorageId storage); + void disableReadLogs(StorageId storage); int setStorageParams(StorageId storage, bool enableReadLogs); int makeFile(StorageId storage, std::string_view path, int mode, FileId id, @@ -264,6 +269,7 @@ private: const std::string root; Control control; /*const*/ MountId mountId; + int32_t flags = StorageFlags::ReadLogsEnabled; StorageMap storages; BindMap bindPoints; DataLoaderStubPtr dataLoaderStub; @@ -282,6 +288,9 @@ private: StorageMap::iterator makeStorage(StorageId id); + void disableReadLogs() { flags &= ~StorageFlags::ReadLogsEnabled; } + int32_t readLogsEnabled() const { return (flags & StorageFlags::ReadLogsEnabled); } + static void cleanupFilesystem(std::string_view root); }; diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index 26b5094a795a..1ae9e256c9f4 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -929,6 +929,34 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) { ASSERT_GE(mDataLoader->setStorageParams(true), 0); } +TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndDisabled) { + mVold->mountIncFsSuccess(); + mIncFs->makeFileSuccess(); + mVold->bindMountSuccess(); + mVold->setIncFsMountOptionsSuccess(); + mDataLoaderManager->bindToDataLoaderSuccess(); + mDataLoaderManager->getDataLoaderSuccess(); + mAppOpsManager->checkPermissionSuccess(); + EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)); + EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); + // Enabling and then disabling readlogs. + EXPECT_CALL(*mVold, setIncFsMountOptions(_, true)).Times(1); + EXPECT_CALL(*mVold, setIncFsMountOptions(_, false)).Times(1); + // After setIncFsMountOptions succeeded expecting to start watching. + EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(1); + // Not expecting callback removal. + EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0); + TemporaryDir tempDir; + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); + ASSERT_GE(storageId, 0); + ASSERT_GE(mDataLoader->setStorageParams(true), 0); + // Now disable. + mIncrementalService->disableReadLogs(storageId); + ASSERT_EQ(mDataLoader->setStorageParams(true), -EPERM); +} + TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChanged) { mVold->mountIncFsSuccess(); mIncFs->makeFileSuccess(); diff --git a/services/net/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java index c93e5c5e4759..b172c4be7b0d 100644 --- a/services/net/java/android/net/ip/IpClientCallbacks.java +++ b/services/net/java/android/net/ip/IpClientCallbacks.java @@ -16,7 +16,6 @@ package android.net.ip; -import android.net.DhcpResults; import android.net.DhcpResultsParcelable; import android.net.Layer2PacketParcelable; import android.net.LinkProperties; @@ -67,19 +66,15 @@ public class IpClientCallbacks { * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not * the passed-in DhcpResults object is null. */ - public void onNewDhcpResults(DhcpResults dhcpResults) {} - - /** - * Callback called when new DHCP results are available. - * - * <p>This is purely advisory and not an indication of provisioning success or failure. This is - * only here for callers that want to expose DHCPv4 results to other APIs - * (e.g., WifiInfo#setInetAddress). - * - * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not - * the passed-in DhcpResults object is null. - */ - public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {} + public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) { + // In general callbacks would not use a parcelable directly (DhcpResultsParcelable), and + // would use a wrapper instead. But there are already two classes in the tree for DHCP + // information: DhcpInfo and DhcpResults, and each of them do not expose an appropriate API + // (they are bags of mutable fields and can't be changed because they are public API and + // @UnsupportedAppUsage). Adding a third class would cost more than the gain considering + // that the only client of this callback is WiFi, which will end up converting the results + // to DhcpInfo anyway. + } /** * Indicates that provisioning was successful. diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java index b329aeec4853..426614ec2f53 100644 --- a/services/net/java/android/net/ip/IpClientUtil.java +++ b/services/net/java/android/net/ip/IpClientUtil.java @@ -16,8 +16,6 @@ package android.net.ip; -import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable; - import android.content.Context; import android.net.DhcpResultsParcelable; import android.net.Layer2PacketParcelable; @@ -118,7 +116,6 @@ public class IpClientUtil { // null or not. @Override public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) { - mCb.onNewDhcpResults(fromStableParcelable(dhcpResults)); mCb.onNewDhcpResults(dhcpResults); } diff --git a/services/net/java/android/net/util/DhcpResultsCompatUtil.java b/services/net/java/android/net/util/DhcpResultsCompatUtil.java new file mode 100644 index 000000000000..fce0834c116e --- /dev/null +++ b/services/net/java/android/net/util/DhcpResultsCompatUtil.java @@ -0,0 +1,54 @@ +/* + * 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 android.net.util; + +import static android.net.shared.IpConfigurationParcelableUtil.unparcelAddress; + +import android.annotation.Nullable; +import android.net.DhcpResults; +import android.net.DhcpResultsParcelable; + +import java.net.Inet4Address; + +/** + * Compatibility utility for code that still uses DhcpResults. + * + * TODO: remove this class when all usages of DhcpResults (including Wifi in AOSP) are removed. + */ +public class DhcpResultsCompatUtil { + + /** + * Convert a DhcpResultsParcelable to DhcpResults. + * + * contract { + * returns(null) implies p == null + * returnsNotNull() implies p != null + * } + */ + @Nullable + public static DhcpResults fromStableParcelable(@Nullable DhcpResultsParcelable p) { + if (p == null) return null; + final DhcpResults results = new DhcpResults(p.baseConfiguration); + results.leaseDuration = p.leaseDuration; + results.mtu = p.mtu; + results.serverAddress = (Inet4Address) unparcelAddress(p.serverAddress); + results.vendorInfo = p.vendorInfo; + results.serverHostName = p.serverHostName; + results.captivePortalApiUrl = p.captivePortalApiUrl; + return results; + } +} diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java index 2e60f2afcdea..236ac8407faa 100644 --- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java +++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java @@ -16,6 +16,8 @@ package com.android.server.people.prediction; +import static java.util.Collections.reverseOrder; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -39,6 +41,7 @@ import com.android.server.people.data.PackageData; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.function.Consumer; @@ -85,7 +88,9 @@ class ShareTargetPredictor extends AppTargetPredictor { List<ShareTarget> shareTargets = getDirectShareTargets(); SharesheetModelScorer.computeScore(shareTargets, getShareEventType(mIntentFilter), System.currentTimeMillis()); - Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore())); + Collections.sort(shareTargets, + Comparator.comparing(ShareTarget::getScore, reverseOrder()) + .thenComparing(t -> t.getAppTarget().getRank())); List<AppTarget> res = new ArrayList<>(); for (int i = 0; i < Math.min(getPredictionContext().getPredictedTargetCount(), shareTargets.size()); i++) { @@ -135,6 +140,7 @@ class ShareTargetPredictor extends AppTargetPredictor { new AppTargetId(shortcutInfo.getId()), shortcutInfo) .setClassName(shareShortcut.getTargetComponent().getClassName()) + .setRank(shortcutInfo.getRank()) .build(); String packageName = shortcutInfo.getPackage(); int userId = shortcutInfo.getUserId(); diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp index dad001b52b15..41dfade1a09a 100644 --- a/services/tests/PackageManagerServiceTests/host/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/Android.bp @@ -28,6 +28,14 @@ java_test_host { ":PackageManagerDummyAppVersion1", ":PackageManagerDummyAppVersion2", ":PackageManagerDummyAppVersion3", + ":PackageManagerDummyAppVersion4", ":PackageManagerDummyAppOriginalOverride", + ":PackageManagerServiceHostTestsResources", ] } + +filegroup { + name: "PackageManagerServiceHostTestsResources", + srcs: [ "resources/*" ], + path: "resources/" +} diff --git a/services/tests/PackageManagerServiceTests/host/resources/PackageManagerDummyAppVersion3Invalid.apk b/services/tests/PackageManagerServiceTests/host/resources/PackageManagerDummyAppVersion3Invalid.apk Binary files differnew file mode 100644 index 000000000000..127886cf8e9e --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/resources/PackageManagerDummyAppVersion3Invalid.apk diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt index 4927c45550b5..490f96d8f426 100644 --- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt @@ -19,6 +19,7 @@ package com.android.server.pm.test import com.android.internal.util.test.SystemPreparer import com.android.tradefed.device.ITestDevice import java.io.File +import java.io.FileOutputStream internal fun SystemPreparer.pushApk(file: String, partition: Partition) = pushResourceFile(file, HostUtils.makePathForApk(file, partition)) @@ -43,4 +44,13 @@ internal object HostUtils { .resolve(file.nameWithoutExtension) .resolve(file.name) .toString() + + fun copyResourceToHostFile(javaResourceName: String, file: File): File { + javaClass.classLoader!!.getResource(javaResourceName).openStream().use { input -> + FileOutputStream(file).use { output -> + input.copyTo(output) + } + } + return file + } } diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt new file mode 100644 index 000000000000..98e045d0a203 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test + +import com.android.internal.util.test.SystemPreparer +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith + +@RunWith(DeviceJUnit4ClassRunner::class) +class InvalidNewSystemAppTest : BaseHostJUnit4Test() { + + companion object { + private const val TEST_PKG_NAME = "com.android.server.pm.test.dummy_app" + private const val VERSION_ONE = "PackageManagerDummyAppVersion1.apk" + private const val VERSION_TWO = "PackageManagerDummyAppVersion2.apk" + private const val VERSION_THREE_INVALID = "PackageManagerDummyAppVersion3Invalid.apk" + private const val VERSION_FOUR = "PackageManagerDummyAppVersion4.apk" + + @get:ClassRule + val deviceRebootRule = SystemPreparer.TestRuleDelegate(true) + } + + private val tempFolder = TemporaryFolder() + private val preparer: SystemPreparer = SystemPreparer(tempFolder, + SystemPreparer.RebootStrategy.START_STOP, deviceRebootRule) { this.device } + + @get:Rule + val rules = RuleChain.outerRule(tempFolder).around(preparer)!! + + @Before + @After + fun uninstallDataPackage() { + device.uninstallPackage(TEST_PKG_NAME) + } + + @Test + fun verify() { + // First, push a system app to the device and then update it so there's a data variant + val filePath = HostUtils.makePathForApk("PackageManagerDummyApp.apk", Partition.PRODUCT) + + preparer.pushResourceFile(VERSION_ONE, filePath) + .reboot() + + val versionTwoFile = HostUtils.copyResourceToHostFile(VERSION_TWO, tempFolder.newFile()) + + assertThat(device.installPackage(versionTwoFile, true)).isNull() + + // Then push a bad update to the system, overwriting the existing file as if an OTA occurred + preparer.deleteFile(filePath) + .pushResourceFile(VERSION_THREE_INVALID, filePath) + .reboot() + + // This will remove the package from the device, which is expected + assertThat(device.getAppPackageInfo(TEST_PKG_NAME)).isNull() + + // Then check that a user would still be able to install the application manually + val versionFourFile = HostUtils.copyResourceToHostFile(VERSION_FOUR, tempFolder.newFile()) + assertThat(device.installPackage(versionFourFile, true)).isNull() + } +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp index 9568faa7dfd0..c9b29275a731 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp @@ -28,6 +28,11 @@ android_test_helper_app { } android_test_helper_app { + name: "PackageManagerDummyAppVersion4", + manifest: "AndroidManifestVersion4.xml" +} + +android_test_helper_app { name: "PackageManagerDummyAppOriginalOverride", manifest: "AndroidManifestOriginalOverride.xml" } diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml index d772050d7fd0..b492a31349fc 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml @@ -18,4 +18,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.pm.test.dummy_app" android:versionCode="1" - /> + > + + <permission + android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION" + android:protectionLevel="normal" + /> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml index 53f836b222e6..25e9f8eb2a67 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml @@ -18,4 +18,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.pm.test.dummy_app" android:versionCode="2" - /> + > + + <permission + android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION" + android:protectionLevel="normal" + /> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml index 90ca9d0ac02c..935f5e62f508 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml @@ -18,4 +18,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.pm.test.dummy_app" android:versionCode="3" - /> + > + + <permission + android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION" + android:protectionLevel="normal" + /> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion4.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion4.xml new file mode 100644 index 000000000000..d0643cbb2aeb --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion4.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.dummy_app" + android:versionCode="4" + > + + <permission + android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION" + android:protectionLevel="normal" + /> + +</manifest> diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index fde40aa77a0e..cdafd32cbbb5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -63,6 +63,7 @@ import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.answer; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyLong; @@ -170,6 +171,7 @@ public class MockingOomAdjusterTests { mock(OomAdjProfiler.class)); doReturn(new ActivityManagerService.ProcessChangeItem()).when(sService) .enqueueProcessChangeItemLocked(anyInt(), anyInt()); + doReturn(true).when(sService).containsTopUiOrRunningRemoteAnimOrEmptyLocked(any()); sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, mock(ActiveUids.class)); sService.mOomAdjuster.mAdjSeq = 10000; @@ -266,6 +268,21 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test + public void testUpdateOomAdj_DoOne_TopApp_PreemptedByTopUi() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); + doReturn(app).when(sService).getTopAppLocked(); + doReturn(false).when(sService).containsTopUiOrRunningRemoteAnimOrEmptyLocked(eq(app)); + sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE; + sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE); + doReturn(null).when(sService).getTopAppLocked(); + + assertProcStates(app, PROCESS_STATE_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT); + } + + @SuppressWarnings("GuardedBy") + @Test public void testUpdateOomAdj_DoOne_RunningInstrumentation() { ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 3b38d948b121..6db3233b0266 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -80,6 +80,7 @@ <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/> + <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID"/> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index c34b8e19a41d..ac44ccea2106 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -264,6 +264,19 @@ public class HdmiControlServiceTest { } @Test + public void disableAndReenableCec_volumeControlReturnsToOriginalValue() { + boolean volumeControlEnabled = true; + mHdmiControlService.setHdmiCecVolumeControlEnabled(volumeControlEnabled); + + mHdmiControlService.setControlEnabled(false); + assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isFalse(); + + mHdmiControlService.setControlEnabled(true); + assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isEqualTo( + volumeControlEnabled); + } + + @Test public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_enabled() { mHdmiControlService.setHdmiCecVolumeControlEnabled(true); VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index 4127fece17bd..c4d121170624 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -43,6 +43,7 @@ import android.content.ContextWrapper; import android.content.pm.UserInfo; import android.hardware.rebootescrow.IRebootEscrow; import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; @@ -178,6 +179,13 @@ public class RebootEscrowManagerTests { } @Test + public void clearCredentials_HalFailure_NonFatal() throws Exception { + doThrow(ServiceSpecificException.class).when(mRebootEscrow).storeKey(any()); + mService.clearRebootEscrow(); + verify(mRebootEscrow).storeKey(eq(new byte[32])); + } + + @Test public void armService_Success() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); @@ -200,6 +208,24 @@ public class RebootEscrowManagerTests { } @Test + public void armService_HalFailure_NonFatal() throws Exception { + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mRebootEscrow); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mRebootEscrow, never()).storeKey(any()); + + assertNull( + mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); + doThrow(ServiceSpecificException.class).when(mRebootEscrow).storeKey(any()); + assertFalse(mService.armRebootEscrowIfNeeded()); + verify(mRebootEscrow).storeKey(any()); + } + + @Test public void armService_MultipleUsers_Success() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java index 60104d390eb7..b09a3c374e86 100644 --- a/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java +++ b/services/tests/servicestests/src/com/android/server/people/prediction/ShareTargetPredictorTest.java @@ -117,10 +117,10 @@ public final class ShareTargetPredictorTest { @Test public void testPredictTargets() { - mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4", 0)); when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class)); when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class)); @@ -165,12 +165,12 @@ public final class ShareTargetPredictorTest { @Test public void testPredictTargets_reachTargetsLimit() { - mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc5")); - mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc6")); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc5", 0)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc6", 0)); when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class)); when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class)); @@ -250,6 +250,41 @@ public final class ShareTargetPredictorTest { } @Test + public void testPredictTargets_noSharingHistoryRankedByShortcutRank() { + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc1", 3)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_1, CLASS_1, "sc2", 2)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc3", 1)); + mShareShortcuts.add(buildShareShortcut(PACKAGE_2, CLASS_2, "sc4", 0)); + + when(mPackageData1.getConversationInfo("sc1")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData1.getConversationInfo("sc2")).thenReturn(mock(ConversationInfo.class)); + when(mPackageData2.getConversationInfo("sc3")).thenReturn(mock(ConversationInfo.class)); + // "sc4" does not have a ConversationInfo. + + mPredictor.predictTargets(); + + verify(mUpdatePredictionsMethod).accept(mAppTargetCaptor.capture()); + List<AppTarget> res = mAppTargetCaptor.getValue(); + assertEquals(4, res.size()); + + assertEquals("sc4", res.get(0).getId().getId()); + assertEquals(CLASS_2, res.get(0).getClassName()); + assertEquals(PACKAGE_2, res.get(0).getPackageName()); + + assertEquals("sc3", res.get(1).getId().getId()); + assertEquals(CLASS_2, res.get(1).getClassName()); + assertEquals(PACKAGE_2, res.get(1).getPackageName()); + + assertEquals("sc2", res.get(2).getId().getId()); + assertEquals(CLASS_1, res.get(2).getClassName()); + assertEquals(PACKAGE_1, res.get(2).getPackageName()); + + assertEquals("sc1", res.get(3).getId().getId()); + assertEquals(CLASS_1, res.get(3).getClassName()); + assertEquals(PACKAGE_1, res.get(3).getPackageName()); + } + + @Test public void testSortTargets() { AppTarget appTarget1 = new AppTarget.Builder( new AppTargetId("cls1#pkg1"), PACKAGE_1, UserHandle.of(USER_ID)) @@ -348,19 +383,20 @@ public final class ShareTargetPredictorTest { } private static ShareShortcutInfo buildShareShortcut( - String packageName, String className, String shortcutId) { - ShortcutInfo shortcutInfo = buildShortcut(packageName, shortcutId); + String packageName, String className, String shortcutId, int rank) { + ShortcutInfo shortcutInfo = buildShortcut(packageName, shortcutId, rank); ComponentName componentName = new ComponentName(packageName, className); return new ShareShortcutInfo(shortcutInfo, componentName); } - private static ShortcutInfo buildShortcut(String packageName, String shortcutId) { + private static ShortcutInfo buildShortcut(String packageName, String shortcutId, int rank) { Context mockContext = mock(Context.class); when(mockContext.getPackageName()).thenReturn(packageName); when(mockContext.getUserId()).thenReturn(USER_ID); when(mockContext.getUser()).thenReturn(UserHandle.of(USER_ID)); ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mockContext, shortcutId) .setShortLabel(shortcutId) + .setRank(rank) .setIntent(new Intent("TestIntent")); return builder.build(); } diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index f205fde88c0d..26230949cda6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.empty; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,6 +33,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageParser; import android.content.pm.Signature; +import android.content.pm.UserInfo; import android.content.pm.parsing.ParsingPackage; import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.parsing.component.ParsedInstrumentation; @@ -39,9 +41,11 @@ import android.content.pm.parsing.component.ParsedIntentInfo; import android.content.pm.parsing.component.ParsedProvider; import android.os.Build; import android.os.Process; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.SparseArray; import androidx.annotation.NonNull; @@ -57,26 +61,36 @@ import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; import java.security.cert.CertificateException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.IntFunction; +import java.util.stream.Collectors; @Presubmit @RunWith(JUnit4.class) public class AppsFilterTest { - private static final int DUMMY_CALLING_UID = 10345; - private static final int DUMMY_TARGET_UID = 10556; - private static final int DUMMY_ACTOR_UID = 10656; - private static final int DUMMY_OVERLAY_UID = 10756; - private static final int DUMMY_ACTOR_TWO_UID = 10856; + private static final int DUMMY_CALLING_APPID = 10345; + private static final int DUMMY_TARGET_APPID = 10556; + private static final int DUMMY_ACTOR_APPID = 10656; + private static final int DUMMY_OVERLAY_APPID = 10756; + private static final int SYSTEM_USER = 0; + private static final int SECONDARY_USER = 10; + private static final int[] USER_ARRAY = {SYSTEM_USER, SECONDARY_USER}; + private static final UserInfo[] USER_INFO_LIST = Arrays.stream(USER_ARRAY).mapToObj( + id -> new UserInfo(id, Integer.toString(id), 0)).toArray(UserInfo[]::new); @Mock AppsFilter.FeatureConfig mFeatureConfigMock; + @Mock + AppsFilter.StateProvider mStateProvider; private ArrayMap<String, PackageSetting> mExisting = new ArrayMap<>(); @@ -170,15 +184,24 @@ public class AppsFilterTest { mExisting = new ArrayMap<>(); MockitoAnnotations.initMocks(this); + doAnswer(invocation -> { + ((AppsFilter.StateProvider.CurrentStateCallback) invocation.getArgument(0)) + .currentState(mExisting, USER_INFO_LIST); + return null; + }).when(mStateProvider) + .runWithState(any(AppsFilter.StateProvider.CurrentStateCallback.class)); + when(mFeatureConfigMock.isGloballyEnabled()).thenReturn(true); - when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))) - .thenReturn(true); + when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))).thenAnswer( + (Answer<Boolean>) invocation -> + ((AndroidPackage)invocation.getArgument(SYSTEM_USER)).getTargetSdkVersion() + >= Build.VERSION_CODES.R); } @Test public void testSystemReadyPropogates() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); appsFilter.onSystemReady(); verify(mFeatureConfigMock).onSystemReady(); } @@ -186,22 +209,23 @@ public class AppsFilterTest { @Test public void testQueriesAction_FilterMatches() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_UID); + pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_UID); + pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testQueriesProtectedAction_FilterDoesNotMatch() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); final Signature frameworkSignature = Mockito.mock(Signature.class); final PackageParser.SigningDetails frameworkSigningDetails = new PackageParser.SigningDetails(new Signature[]{frameworkSignature}, 1); @@ -211,164 +235,174 @@ public class AppsFilterTest { b -> b.setSigningDetails(frameworkSigningDetails)); appsFilter.onSystemReady(); - final int activityUid = DUMMY_TARGET_UID; + final int activityUid = DUMMY_TARGET_APPID; PackageSetting targetActivity = simulateAddPackage(appsFilter, pkg("com.target.activity", new IntentFilter("TEST_ACTION")), activityUid); - final int receiverUid = DUMMY_TARGET_UID + 1; + final int receiverUid = DUMMY_TARGET_APPID + 1; PackageSetting targetReceiver = simulateAddPackage(appsFilter, pkgWithReceiver("com.target.receiver", new IntentFilter("TEST_ACTION")), receiverUid); - final int callingUid = DUMMY_CALLING_UID; + final int callingUid = DUMMY_CALLING_APPID; PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.calling.action", new Intent("TEST_ACTION")), callingUid); - final int wildcardUid = DUMMY_CALLING_UID + 1; + final int wildcardUid = DUMMY_CALLING_APPID + 1; PackageSetting callingWildCard = simulateAddPackage(appsFilter, pkg("com.calling.wildcard", new Intent("*")), wildcardUid); - assertFalse(appsFilter.shouldFilterApplication(callingUid, calling, targetActivity, 0)); - assertTrue(appsFilter.shouldFilterApplication(callingUid, calling, targetReceiver, 0)); + assertFalse(appsFilter.shouldFilterApplication(callingUid, calling, targetActivity, + SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(callingUid, calling, targetReceiver, + SYSTEM_USER)); assertFalse(appsFilter.shouldFilterApplication( - wildcardUid, callingWildCard, targetActivity, 0)); + wildcardUid, callingWildCard, targetActivity, SYSTEM_USER)); assertTrue(appsFilter.shouldFilterApplication( - wildcardUid, callingWildCard, targetReceiver, 0)); + wildcardUid, callingWildCard, targetReceiver, SYSTEM_USER)); } @Test public void testQueriesProvider_FilterMatches() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); + pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, pkgQueriesProvider("com.some.other.package", "com.some.authority"), - DUMMY_CALLING_UID); + DUMMY_CALLING_APPID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testQueriesDifferentProvider_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); + pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, pkgQueriesProvider("com.some.other.package", "com.some.other.authority"), - DUMMY_CALLING_UID); + DUMMY_CALLING_APPID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testQueriesProviderWithSemiColon_FilterMatches() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"), - DUMMY_TARGET_UID); + DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, pkgQueriesProvider("com.some.other.package", "com.some.authority"), - DUMMY_CALLING_UID); + DUMMY_CALLING_APPID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testQueriesAction_NoMatchingAction_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_UID); + pkg("com.some.package"), DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_UID); + pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_UID); - PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", - new Intent("TEST_ACTION")) - .setTargetSdkVersion(Build.VERSION_CODES.P), - DUMMY_CALLING_UID); + pkg("com.some.package"), DUMMY_TARGET_APPID); + ParsingPackage callingPkg = pkg("com.some.other.package", + new Intent("TEST_ACTION")) + .setTargetSdkVersion(Build.VERSION_CODES.P); + PackageSetting calling = simulateAddPackage(appsFilter, callingPkg, + DUMMY_CALLING_APPID); - when(mFeatureConfigMock.packageIsEnabled(calling.pkg)).thenReturn(false); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testNoQueries_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_UID); + pkg("com.some.package"), DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_UID); + pkg("com.some.other.package"), DUMMY_CALLING_APPID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testForceQueryable_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID); + pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_UID); + pkg("com.some.other.package"), DUMMY_CALLING_APPID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testForceQueryableByDevice_SystemCaller_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"}, + false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_UID, + pkg("com.some.package"), DUMMY_TARGET_APPID, setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_UID); + pkg("com.some.other.package"), DUMMY_CALLING_APPID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testSystemSignedTarget_DoesntFilter() throws CertificateException { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); appsFilter.onSystemReady(); final Signature frameworkSignature = Mockito.mock(Signature.class); @@ -382,62 +416,67 @@ public class AppsFilterTest { simulateAddPackage(appsFilter, pkg("android"), 1000, b -> b.setSigningDetails(frameworkSigningDetails)); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_UID, + DUMMY_TARGET_APPID, b -> b.setSigningDetails(frameworkSigningDetails) .setPkgFlags(ApplicationInfo.FLAG_SYSTEM)); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_UID, + pkg("com.some.other.package"), DUMMY_CALLING_APPID, b -> b.setSigningDetails(otherSigningDetails)); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testForceQueryableByDevice_NonSystemCaller_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{"com.some.package"}, + false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_UID); + pkg("com.some.package"), DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_UID); + pkg("com.some.other.package"), DUMMY_CALLING_APPID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testSystemQueryable_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, true /* system force queryable */, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_UID, + pkg("com.some.package"), DUMMY_TARGET_APPID, setting -> setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package"), DUMMY_CALLING_UID); + pkg("com.some.other.package"), DUMMY_CALLING_APPID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testQueriesPackage_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_UID); + pkg("com.some.package"), DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", "com.some.package"), DUMMY_CALLING_UID); + pkg("com.some.other.package", "com.some.package"), DUMMY_CALLING_APPID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test @@ -445,63 +484,83 @@ public class AppsFilterTest { when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class))) .thenReturn(false); final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage( - appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID); + appsFilter, pkg("com.some.package"), DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage( - appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_UID); + appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_APPID); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testSystemUid_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_UID); + pkg("com.some.package"), DUMMY_TARGET_APPID); - assertFalse(appsFilter.shouldFilterApplication(0, null, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(SYSTEM_USER, null, target, SYSTEM_USER)); assertFalse(appsFilter.shouldFilterApplication(Process.FIRST_APPLICATION_UID - 1, - null, target, 0)); + null, target, SYSTEM_USER)); + } + + @Test + public void testSystemUidSecondaryUser_DoesntFilter() throws Exception { + final AppsFilter appsFilter = + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); + simulateAddBasicAndroid(appsFilter); + appsFilter.onSystemReady(); + + PackageSetting target = simulateAddPackage(appsFilter, + pkg("com.some.package"), DUMMY_TARGET_APPID); + + assertFalse(appsFilter.shouldFilterApplication(0, null, target, SECONDARY_USER)); + assertFalse(appsFilter.shouldFilterApplication( + UserHandle.getUid(SECONDARY_USER, Process.FIRST_APPLICATION_UID - 1), + null, target, SECONDARY_USER)); } @Test public void testNonSystemUid_NoCallingSetting_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package"), DUMMY_TARGET_UID); + pkg("com.some.package"), DUMMY_TARGET_APPID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, null, target, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, null, target, + SYSTEM_USER)); } @Test public void testNoTargetPackage_filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = new PackageSettingBuilder() + .setAppId(DUMMY_TARGET_APPID) .setName("com.some.package") .setCodePath("/") .setResourcePath("/") .setPVersionCode(1L) .build(); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_UID); + pkg("com.some.other.package", new Intent("TEST_ACTION")), DUMMY_CALLING_APPID); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test @@ -516,7 +575,11 @@ public class AppsFilterTest { .setOverlayTargetName("overlayableName"); ParsingPackage actor = pkg("com.some.package.actor"); - final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false, + final AppsFilter appsFilter = new AppsFilter( + mStateProvider, + mFeatureConfigMock, + new String[]{}, + false, new OverlayReferenceMapper.Provider() { @Nullable @Override @@ -544,31 +607,34 @@ public class AppsFilterTest { simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); - PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID); - PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID); - PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_UID); + PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID); + PackageSetting overlaySetting = + simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID); + PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID); // Actor can see both target and overlay - assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting, - targetSetting, 0)); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting, - overlaySetting, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting, + targetSetting, SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting, + overlaySetting, SYSTEM_USER)); // But target/overlay can't see each other - assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting, - overlaySetting, 0)); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting, - targetSetting, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, targetSetting, + overlaySetting, SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting, + targetSetting, SYSTEM_USER)); // And can't see the actor - assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting, - actorSetting, 0)); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting, - actorSetting, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, targetSetting, + actorSetting, SYSTEM_USER)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting, + actorSetting, SYSTEM_USER)); } @Test public void testActsOnTargetOfOverlayThroughSharedUser() throws Exception { +// Debug.waitForDebugger(); + final String actorName = "overlay://test/actorName"; ParsingPackage target = pkg("com.some.package.target") @@ -580,7 +646,11 @@ public class AppsFilterTest { ParsingPackage actorOne = pkg("com.some.package.actor.one"); ParsingPackage actorTwo = pkg("com.some.package.actor.two"); - final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false, + final AppsFilter appsFilter = new AppsFilter( + mStateProvider, + mFeatureConfigMock, + new String[]{}, + false, new OverlayReferenceMapper.Provider() { @Nullable @Override @@ -609,108 +679,114 @@ public class AppsFilterTest { simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); - PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID); - PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID); - PackageSetting actorOneSetting = simulateAddPackage(appsFilter, actorOne, DUMMY_ACTOR_UID); - PackageSetting actorTwoSetting = simulateAddPackage(appsFilter, actorTwo, - DUMMY_ACTOR_TWO_UID); - + PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID); SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser", - actorOneSetting.pkgFlags, actorOneSetting.pkgPrivateFlags); - actorSharedSetting.addPackage(actorOneSetting); - actorSharedSetting.addPackage(actorTwoSetting); + targetSetting.pkgFlags, targetSetting.pkgPrivateFlags); + PackageSetting overlaySetting = + simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID); + simulateAddPackage(appsFilter, actorOne, DUMMY_ACTOR_APPID, + null /*settingBuilder*/, actorSharedSetting); + simulateAddPackage(appsFilter, actorTwo, DUMMY_ACTOR_APPID, + null /*settingBuilder*/, actorSharedSetting); + // actorTwo can see both target and overlay - assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting, - targetSetting, 0)); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting, - overlaySetting, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSharedSetting, + targetSetting, SYSTEM_USER)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSharedSetting, + overlaySetting, SYSTEM_USER)); } @Test public void testInitiatingApp_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_UID); + DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), - DUMMY_CALLING_UID, withInstallSource(target.name, null, null, false)); + DUMMY_CALLING_APPID, withInstallSource(target.name, null, null, false)); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testUninstalledInitiatingApp_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_UID); + DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), - DUMMY_CALLING_UID, withInstallSource(target.name, null, null, true)); + DUMMY_CALLING_APPID, withInstallSource(target.name, null, null, true)); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testOriginatingApp_Filters() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_UID); + DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), - DUMMY_CALLING_UID, withInstallSource(null, target.name, null, false)); + DUMMY_CALLING_APPID, withInstallSource(null, target.name, null, false)); - assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testInstallingApp_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_UID); + DUMMY_TARGET_APPID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), - DUMMY_CALLING_UID, withInstallSource(null, null, target.name, false)); + DUMMY_CALLING_APPID, withInstallSource(null, null, target.name, false)); - assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, calling, target, + SYSTEM_USER)); } @Test public void testInstrumentation_DoesntFilter() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), - DUMMY_TARGET_UID); + DUMMY_TARGET_APPID); PackageSetting instrumentation = simulateAddPackage(appsFilter, pkgWithInstrumentation("com.some.other.package", "com.some.package"), - DUMMY_CALLING_UID); + DUMMY_CALLING_APPID); assertFalse( - appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, instrumentation, target, 0)); + appsFilter.shouldFilterApplication(DUMMY_CALLING_APPID, instrumentation, target, + SYSTEM_USER)); assertFalse( - appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, target, instrumentation, 0)); + appsFilter.shouldFilterApplication(DUMMY_TARGET_APPID, target, instrumentation, + SYSTEM_USER)); } @Test public void testWhoCanSee() throws Exception { final AppsFilter appsFilter = - new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + new AppsFilter(mStateProvider, mFeatureConfigMock, new String[]{}, false, null); simulateAddBasicAndroid(appsFilter); appsFilter.onSystemReady(); @@ -718,6 +794,7 @@ public class AppsFilterTest { final int seesNothingAppId = Process.FIRST_APPLICATION_UID; final int hasProviderAppId = Process.FIRST_APPLICATION_UID + 1; final int queriesProviderAppId = Process.FIRST_APPLICATION_UID + 2; + PackageSetting system = simulateAddPackage(appsFilter, pkg("some.system.pkg"), systemAppId); PackageSetting seesNothing = simulateAddPackage(appsFilter, pkg("com.some.package"), seesNothingAppId); @@ -727,23 +804,26 @@ public class AppsFilterTest { pkgQueriesProvider("com.yet.some.other.package", "com.some.authority"), queriesProviderAppId); - final int[] systemFilter = - appsFilter.getVisibilityWhitelist(system, new int[]{0}, mExisting).get(0); - assertThat(toList(systemFilter), empty()); + final SparseArray<int[]> systemFilter = + appsFilter.getVisibilityWhitelist(system, USER_ARRAY, mExisting); + assertThat(toList(systemFilter.get(SYSTEM_USER)), + contains(seesNothingAppId, hasProviderAppId, queriesProviderAppId)); - final int[] seesNothingFilter = - appsFilter.getVisibilityWhitelist(seesNothing, new int[]{0}, mExisting).get(0); - assertThat(toList(seesNothingFilter), + final SparseArray<int[]> seesNothingFilter = + appsFilter.getVisibilityWhitelist(seesNothing, USER_ARRAY, mExisting); + assertThat(toList(seesNothingFilter.get(SYSTEM_USER)), + contains(seesNothingAppId)); + assertThat(toList(seesNothingFilter.get(SECONDARY_USER)), contains(seesNothingAppId)); - final int[] hasProviderFilter = - appsFilter.getVisibilityWhitelist(hasProvider, new int[]{0}, mExisting).get(0); - assertThat(toList(hasProviderFilter), + final SparseArray<int[]> hasProviderFilter = + appsFilter.getVisibilityWhitelist(hasProvider, USER_ARRAY, mExisting); + assertThat(toList(hasProviderFilter.get(SYSTEM_USER)), contains(hasProviderAppId, queriesProviderAppId)); - int[] queriesProviderFilter = - appsFilter.getVisibilityWhitelist(queriesProvider, new int[]{0}, mExisting).get(0); - assertThat(toList(queriesProviderFilter), + SparseArray<int[]> queriesProviderFilter = + appsFilter.getVisibilityWhitelist(queriesProvider, USER_ARRAY, mExisting); + assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(queriesProviderAppId)); // provider read @@ -751,8 +831,8 @@ public class AppsFilterTest { // ensure implicit access is included in the filter queriesProviderFilter = - appsFilter.getVisibilityWhitelist(queriesProvider, new int[]{0}, mExisting).get(0); - assertThat(toList(queriesProviderFilter), + appsFilter.getVisibilityWhitelist(queriesProvider, USER_ARRAY, mExisting); + assertThat(toList(queriesProviderFilter.get(SYSTEM_USER)), contains(hasProviderAppId, queriesProviderAppId)); } @@ -779,11 +859,17 @@ public class AppsFilterTest { private PackageSetting simulateAddPackage(AppsFilter filter, ParsingPackage newPkgBuilder, int appId) { - return simulateAddPackage(filter, newPkgBuilder, appId, null); + return simulateAddPackage(filter, newPkgBuilder, appId, null /*settingBuilder*/); } private PackageSetting simulateAddPackage(AppsFilter filter, ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action) { + return simulateAddPackage(filter, newPkgBuilder, appId, action, null /*sharedUserSetting*/); + } + + private PackageSetting simulateAddPackage(AppsFilter filter, + ParsingPackage newPkgBuilder, int appId, @Nullable WithSettingBuilder action, + @Nullable SharedUserSetting sharedUserSetting) { AndroidPackage newPkg = ((ParsedPackage) newPkgBuilder.hideAsParsed()).hideAsFinal(); final PackageSettingBuilder settingBuilder = new PackageSettingBuilder() @@ -795,8 +881,12 @@ public class AppsFilterTest { .setPVersionCode(1L); final PackageSetting setting = (action == null ? settingBuilder : action.withBuilder(settingBuilder)).build(); - filter.addPackage(setting, mExisting); mExisting.put(newPkg.getPackageName(), setting); + if (sharedUserSetting != null) { + sharedUserSetting.addPackage(setting); + setting.sharedUser = sharedUserSetting; + } + filter.addPackage(setting); return setting; } @@ -809,4 +899,3 @@ public class AppsFilterTest { return setting -> setting.setInstallSource(installSource); } } - diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java index efc1c057d8f4..a550b27a62a2 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java @@ -52,7 +52,7 @@ public class SELinuxMMACTest { @Test public void getSeInfoOptInToLatest() { AndroidPackage pkg = makePackage(Build.VERSION_CODES.P); - when(mMockCompatibility.isChangeEnabled(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES), + when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES), argThat(argument -> argument.packageName.equals(pkg.getPackageName())))) .thenReturn(true); assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility), @@ -62,7 +62,7 @@ public class SELinuxMMACTest { @Test public void getSeInfoNoOptIn() { AndroidPackage pkg = makePackage(Build.VERSION_CODES.P); - when(mMockCompatibility.isChangeEnabled(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES), + when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES), argThat(argument -> argument.packageName.equals(pkg.getPackageName())))) .thenReturn(false); assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility), @@ -72,7 +72,7 @@ public class SELinuxMMACTest { @Test public void getSeInfoNoOptInButAlreadyR() { AndroidPackage pkg = makePackage(OPT_IN_VERSION); - when(mMockCompatibility.isChangeEnabled(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES), + when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES), argThat(argument -> argument.packageName.equals(pkg.getPackageName())))) .thenReturn(false); assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility), diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 2d45f9ea40c7..7d20da198371 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -213,7 +213,7 @@ public class AppStandbyControllerTests { } @Override - boolean isNonIdleWhitelisted(String packageName) throws RemoteException { + boolean isNonIdleWhitelisted(String packageName) { return mNonIdleWhitelistApps.contains(packageName); } diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index 3062584aee20..b100c8482bf8 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -31,9 +31,10 @@ import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.RemoteException; -import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; + +import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; import com.android.server.wm.WindowManagerInternal; @@ -55,7 +56,6 @@ import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -65,6 +65,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -100,6 +101,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { private BroadcastReceiver mTimeChangedCallback; private AlarmManager.OnAlarmListener mCustomListener; private Consumer<PowerSaveState> mPowerSaveConsumer; + private TwilightListener mTwilightListener; @Before public void setUp() { @@ -107,6 +109,10 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { when(mContext.checkCallingOrSelfPermission(anyString())) .thenReturn(PackageManager.PERMISSION_GRANTED); doAnswer(inv -> { + mTwilightListener = (TwilightListener) inv.getArgument(0); + return null; + }).when(mTwilightManager).registerListener(any(), any()); + doAnswer(inv -> { mPowerSaveConsumer = (Consumer<PowerSaveState>) inv.getArgument(1); return null; }).when(mLocalPowerManager).registerLowPowerModeObserver(anyInt(), any()); @@ -160,6 +166,37 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test + public void setNightMoveActivated_overridesFunctionCorrectly() throws RemoteException { + // set up + when(mPowerManager.isInteractive()).thenReturn(false); + mService.setNightMode(MODE_NIGHT_NO); + assertFalse(mUiManagerService.getConfiguration().isNightModeActive()); + + // assume it is day time + doReturn(false).when(mTwilightState).isNight(); + + // set mode to auto + mService.setNightMode(MODE_NIGHT_AUTO); + + // set night mode on overriding current config + mService.setNightModeActivated(true); + + assertTrue(mUiManagerService.getConfiguration().isNightModeActive()); + + // now it is night time + doReturn(true).when(mTwilightState).isNight(); + mTwilightListener.onTwilightStateChanged(mTwilightState); + + assertTrue(mUiManagerService.getConfiguration().isNightModeActive()); + + // now it is next day mid day + doReturn(false).when(mTwilightState).isNight(); + mTwilightListener.onTwilightStateChanged(mTwilightState); + + assertFalse(mUiManagerService.getConfiguration().isNightModeActive()); + } + + @Test public void setAutoMode_screenOffRegistered() throws RemoteException { try { mService.setNightMode(MODE_NIGHT_NO); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java index b6ea063ccc14..f609306e44b0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java @@ -51,4 +51,9 @@ public class NotificationChannelLoggerFake implements NotificationChannelLogger NotificationChannelGroup channelGroup, int uid, String pkg, boolean wasBlocked) { mCalls.add(new CallRecord(event)); } + + @Override + public void logAppEvent(NotificationChannelEvent event, int uid, String pkg) { + mCalls.add(new CallRecord(event)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 622a203c5242..2e49929ec032 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -2266,6 +2266,14 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testAppBlockedLogging() { + mHelper.setEnabled(PKG_N_MR1, 1020, false); + assertEquals(1, mLogger.getCalls().size()); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent.APP_NOTIFICATIONS_BLOCKED, + mLogger.get(0).event); + } + @Test public void testXml_statusBarIcons_default() throws Exception { String preQXml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 30df0d4b4ad9..4040fa6a675e 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -54,6 +54,8 @@ <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityViewTestActivity" /> <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityInActivityView" android:resizeableActivity="true" /> + <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityLaunchesNewActivityInActivityView" + android:resizeableActivity="true" /> <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$LandscapeActivity" android:screenOrientation="sensorLandscape" android:showWhenLocked="true" diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 4ee933a0a5a5..29b96ebdc090 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -794,6 +794,39 @@ public class ActivityRecordTests extends ActivityTestsBase { } /** + * Verify that when top focused activity is on secondary display, when finishing the top focused + * activity on default display, the preferred top stack on default display should be changed by + * adjusting focus. + */ + @Test + public void testFinishActivityIfPossible_PreferredTopStackChanged() { + final ActivityRecord topActivityOnNonTopDisplay = + createActivityOnDisplay(true /* defaultDisplay */, null /* process */); + ActivityStack topRootableTask = topActivityOnNonTopDisplay.getRootTask(); + topRootableTask.moveToFront("test"); + assertTrue(topRootableTask.isTopStackInDisplayArea()); + assertEquals(topRootableTask, topActivityOnNonTopDisplay.getDisplayArea() + .mPreferredTopFocusableStack); + + final ActivityRecord secondaryDisplayActivity = + createActivityOnDisplay(false /* defaultDisplay */, null /* process */); + topRootableTask = secondaryDisplayActivity.getRootTask(); + topRootableTask.moveToFront("test"); + assertTrue(topRootableTask.isTopStackInDisplayArea()); + assertEquals(topRootableTask, + secondaryDisplayActivity.getDisplayArea().mPreferredTopFocusableStack); + + // The global top focus activity is on secondary display now. + // Finish top activity on default display and verify the next preferred top focusable stack + // on default display has changed. + topActivityOnNonTopDisplay.setState(RESUMED, "test"); + topActivityOnNonTopDisplay.finishIfPossible(0 /* resultCode */, null /* resultData */, + null /* resultGrants */, "test", false /* oomAdj */); + assertEquals(mTask, mStack.getTopMostTask()); + assertEquals(mStack, mActivity.getDisplayArea().mPreferredTopFocusableStack); + } + + /** * Verify that resumed activity is paused due to finish request. */ @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java index 02de408343c5..a16bd2a72a83 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java @@ -38,7 +38,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; @@ -65,12 +65,12 @@ import android.view.WindowManager; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; -import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; + /** * Tests for the {@link ActivityRecord} class. * @@ -406,12 +406,11 @@ public class AppWindowTokenTests extends WindowTestsBase { assertHasStartingWindow(activity2); // Assert that bottom activity is allowed to do animation. + ArrayList<WindowContainer> sources = new ArrayList<>(); + sources.add(activity2); doReturn(true).when(activity2).okToAnimate(); doReturn(true).when(activity2).isAnimating(); - final OnAnimationFinishedCallback onAnimationFinishedCallback = - mock(OnAnimationFinishedCallback.class); - assertTrue(activity2.applyAnimation(null, TRANSIT_ACTIVITY_OPEN, true, false, - onAnimationFinishedCallback)); + assertTrue(activity2.applyAnimation(null, TRANSIT_ACTIVITY_OPEN, true, false, sources)); } @Test @@ -454,6 +453,32 @@ public class AppWindowTokenTests extends WindowTestsBase { assertFalse(middle.isVisible()); } + @Test + public void testTransferStartingWindowSetFixedRotation() { + mWm.mIsFixedRotationTransformEnabled = true; + final ActivityRecord topActivity = createTestActivityRecordForGivenTask(mTask); + mTask.positionChildAt(topActivity, POSITION_TOP); + mActivity.addStartingWindow(mPackageName, + android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true, + false); + waitUntilHandlersIdle(); + + // Make activities to have different rotation from it display and set fixed rotation + // transform to activity1. + int rotation = (mDisplayContent.getRotation() + 1) % 4; + mDisplayContent.setFixedRotationLaunchingApp(mActivity, rotation); + doReturn(rotation).when(mDisplayContent) + .rotationForActivityInDifferentOrientation(topActivity); + + // Make sure the fixed rotation transform linked to activity2 when adding starting window + // on activity2. + topActivity.addStartingWindow(mPackageName, + android.R.style.Theme, null, "Test", 0, 0, 0, 0, mActivity.appToken.asBinder(), + false, false, false, true, false); + waitUntilHandlersIdle(); + assertTrue(topActivity.hasFixedRotationTransform()); + } + private ActivityRecord createIsolatedTestActivityRecord() { final ActivityStack taskStack = createTaskStackOnDisplay(mDisplayContent); final Task task = createTaskInStack(taskStack, 0 /* userId */); diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index 77ceeedae1ac..f7beb74688d6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -29,18 +29,14 @@ import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; -import android.annotation.Nullable; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.view.SurfaceSession; import com.android.server.wm.SurfaceAnimator.AnimationType; -import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import org.junit.Before; import org.junit.Test; @@ -119,12 +115,8 @@ public class DimmerTests extends WindowTestsBase { private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter { @Override public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, - AnimationAdapter anim, boolean hidden, @AnimationType int type, - @Nullable OnAnimationFinishedCallback animationFinishedCallback) { + AnimationAdapter anim, boolean hidden, @AnimationType int type) { surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim); - if (animationFinishedCallback != null) { - animationFinishedCallback.onAnimationFinished(type, anim); - } } } @@ -229,7 +221,7 @@ public class DimmerTests extends WindowTestsBase { mDimmer.updateDims(mTransaction, new Rect()); verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any( SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), - eq(ANIMATION_TYPE_DIMMER), isNull()); + eq(ANIMATION_TYPE_DIMMER)); verify(mHost.getPendingTransaction()).remove(dimLayer); } @@ -287,7 +279,7 @@ public class DimmerTests extends WindowTestsBase { mDimmer.updateDims(mTransaction, new Rect()); verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any( SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), - eq(ANIMATION_TYPE_DIMMER), isNull()); + eq(ANIMATION_TYPE_DIMMER)); verify(mTransaction).remove(dimLayer); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 4ad7dff87072..8cf850736cb2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -79,6 +80,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; import android.annotation.SuppressLint; import android.app.ActivityTaskManager; @@ -1144,7 +1146,14 @@ public class DisplayContentTests extends WindowTestsBase { assertTrue(app.hasFixedRotationTransform(app2)); assertTrue(mDisplayContent.isFixedRotationLaunchingApp(app2)); + // The fixed rotation transform can only be finished when all animation finished. + doReturn(false).when(app2).isAnimating(anyInt(), anyInt()); + mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app2.token); + assertTrue(app.hasFixedRotationTransform()); + assertTrue(app2.hasFixedRotationTransform()); + // The display should be rotated after the launch is finished. + doReturn(false).when(app).isAnimating(anyInt(), anyInt()); mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token); // The fixed rotation should be cleared and the new rotation is applied to display. @@ -1224,11 +1233,29 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testRecentsNotRotatingWithFixedRotation() { + final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation(); + doCallRealMethod().when(displayRotation).updateRotationUnchecked(anyBoolean()); + doCallRealMethod().when(displayRotation).updateOrientation(anyInt(), anyBoolean()); + + final ActivityRecord recentsActivity = createActivityRecord(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS); + recentsActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); + + mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity); + displayRotation.setRotation((displayRotation.getRotation() + 1) % 4); + assertFalse(displayRotation.updateRotationUnchecked(false)); + + mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(false); + assertTrue(displayRotation.updateRotationUnchecked(false)); + } + + @Test public void testRemoteRotation() { DisplayContent dc = createNewDisplay(); final DisplayRotation dr = dc.getDisplayRotation(); - Mockito.doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean()); + doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean()); Mockito.doReturn(ROTATION_90).when(dr).rotationForOrientation(anyInt(), anyInt()); final boolean[] continued = new boolean[1]; // TODO(display-merge): Remove cast @@ -1280,7 +1307,6 @@ public class DisplayContentTests extends WindowTestsBase { public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() { DisplayContent display = createNewDisplay(); doReturn(true).when(display).supportsSystemDecorations(); - doReturn(false).when(display).isUntrustedVirtualDisplay(); // Remove the current home stack if it exists so a new one can be created below. TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); @@ -1304,10 +1330,10 @@ public class DisplayContentTests extends WindowTestsBase { } @Test - public void testGetOrCreateRootHomeTask_untrustedVirtualDisplay() { + public void testGetOrCreateRootHomeTask_untrustedDisplay() { DisplayContent display = createNewDisplay(); TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea(); - doReturn(true).when(display).isUntrustedVirtualDisplay(); + doReturn(false).when(display).isTrusted(); assertNull(taskDisplayArea.getRootHomeTask()); assertNull(taskDisplayArea.getOrCreateRootHomeTask()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 27c4e9ba8641..1922351ac1eb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -57,6 +57,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.Pair; +import android.util.SparseArray; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.InsetsState; @@ -776,15 +777,15 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } private void assertSimulateLayoutSameDisplayFrames() { - final int uiMode = 0; final String prefix = ""; final InsetsState simulatedInsetsState = new InsetsState(); final DisplayFrames simulatedDisplayFrames = createDisplayFrames(); - mDisplayPolicy.beginLayoutLw(mFrames, uiMode); + mDisplayPolicy.beginLayoutLw(mFrames, mDisplayContent.getConfiguration().uiMode); // Force the display bounds because it is not synced with display frames in policy test. mDisplayContent.getWindowConfiguration().setBounds(mFrames.mUnrestricted); mDisplayContent.getInsetsStateController().onPostLayout(); - mDisplayPolicy.simulateLayoutDisplay(simulatedDisplayFrames, simulatedInsetsState, uiMode); + mDisplayPolicy.simulateLayoutDisplay(simulatedDisplayFrames, simulatedInsetsState, + new SparseArray<>() /* barContentFrames */); final StringWriter realFramesDump = new StringWriter(); mFrames.dump(prefix, new PrintWriter(realFramesDump)); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index ca6679d1eece..243468aba8e8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -349,11 +349,19 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { assertEquals(Configuration.ORIENTATION_PORTRAIT, homeActivity.getConfiguration().orientation); - // Home activity won't become top (return to landActivity), so its fixed rotation and the - // top rotated record should be cleared. + // Home activity won't become top (return to landActivity), so the top rotated record should + // be cleared. mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION); - assertFalse(homeActivity.hasFixedRotationTransform()); + assertFalse(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity)); assertFalse(mDefaultDisplay.hasTopFixedRotationLaunchingApp()); + // The transform should keep until the transition is done, so the restored configuration + // won't be sent to activity and cause unnecessary configuration change. + assertTrue(homeActivity.hasFixedRotationTransform()); + + // In real case the transition will be executed from RecentsAnimation#finishAnimation. + mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked( + homeActivity.token); + assertFalse(homeActivity.hasFixedRotationTransform()); } @Test 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 665cf83cd33c..e742b32ff4b8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -24,6 +24,7 @@ import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; @@ -250,6 +251,13 @@ public class SizeCompatTests extends ActivityTestsBase { mActivity.mDisplayContent.mInputMethodTarget = addWindowToActivity(mActivity); // Make sure IME cannot attach to the app, otherwise IME window will also be shifted. assertFalse(mActivity.mDisplayContent.isImeAttachedToApp()); + + // Recompute the natural configuration without resolving size compat configuration. + mActivity.clearSizeCompatMode(); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + // It should keep non-attachable because the resolved bounds will be computed according to + // the aspect ratio that won't match its parent bounds. + assertFalse(mActivity.mDisplayContent.isImeAttachedToApp()); } @Test @@ -288,14 +296,29 @@ public class SizeCompatTests extends ActivityTestsBase { // Move the non-resizable activity to the new display. mStack.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */); - // The configuration bounds should keep the same. + // The configuration bounds [820, 0 - 1820, 2500] should keep the same. assertEquals(origWidth, configBounds.width()); assertEquals(origHeight, configBounds.height()); assertScaled(); + final Rect newDisplayBounds = newDisplay.getWindowConfiguration().getBounds(); // The scaled bounds should exclude notch area (1000 - 100 == 360 * 2500 / 1000 = 900). - assertEquals(newDisplay.getBounds().height() - notchHeight, + assertEquals(newDisplayBounds.height() - notchHeight, (int) ((float) mActivity.getBounds().width() * origHeight / origWidth)); + + // Recompute the natural configuration in the new display. + mActivity.clearSizeCompatMode(); + mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); + // Because the display cannot rotate, the portrait activity will fit the short side of + // display with keeping portrait bounds [200, 0 - 700, 1000] in center. + assertEquals(newDisplayBounds.height(), configBounds.height()); + assertEquals(configBounds.height() * newDisplayBounds.height() / newDisplayBounds.width(), + configBounds.width()); + assertFitted(); + // The appBounds should be [200, 100 - 700, 1000]. + final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds(); + assertEquals(configBounds.width(), appBounds.width()); + assertEquals(configBounds.height() - notchHeight, appBounds.height()); } @Test @@ -491,7 +514,10 @@ public class SizeCompatTests extends ActivityTestsBase { mService.mWindowManager.mIsFixedRotationTransformEnabled = true; final int dw = 1000; final int dh = 2500; - setUpDisplaySizeWithApp(dw, dh); + final int notchHeight = 200; + setUpApp(new TestDisplayContent.Builder(mService, dw, dh).setNotch(notchHeight).build()); + addStatusBar(mActivity.mDisplayContent); + mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */); mActivity.mDisplayContent.mOpeningApps.add(mActivity); @@ -503,31 +529,76 @@ public class SizeCompatTests extends ActivityTestsBase { // Display keeps in original orientation. assertEquals(Configuration.ORIENTATION_PORTRAIT, mActivity.mDisplayContent.getConfiguration().orientation); - // Activity bounds should be [350, 0 - 2150, 1000] in landscape. Its width=1000*1.8=1800. + // The width should be restricted by the max aspect ratio = 1000 * 1.8 = 1800. assertEquals((int) (dw * maxAspect), mActivity.getBounds().width()); - // The bounds should be horizontal centered: (2500-1900)/2=350. - assertEquals((dh - mActivity.getBounds().width()) / 2, mActivity.getBounds().left); + // The notch is at the left side of the landscape activity. The bounds should be horizontal + // centered in the remaining area [200, 0 - 2500, 1000], so its left should be + // 200 + (2300 - 1800) / 2 = 450. The bounds should be [450, 0 - 2250, 1000]. + assertEquals(notchHeight + (dh - notchHeight - mActivity.getBounds().width()) / 2, + mActivity.getBounds().left); // The letterbox needs a main window to layout. - addWindowToActivity(mActivity); + final WindowState w = addWindowToActivity(mActivity); // Compute the frames of the window and invoke {@link ActivityRecord#layoutLetterbox}. mActivity.mRootWindowContainer.performSurfacePlacement(); - // The letterbox insets should be [350, 0 - 350, 0]. + // The letterbox insets should be [450, 0 - 250, 0]. assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0), mActivity.getLetterboxInsets()); + + final StatusBarController statusBarController = + mActivity.mDisplayContent.getDisplayPolicy().getStatusBarController(); + // The activity doesn't fill the display, so the letterbox of the rotated activity is + // overlapped with the rotated content frame of status bar. Hence the status bar shouldn't + // be transparent. + assertFalse(statusBarController.isTransparentAllowed(w)); + + // Make the activity fill the display. + prepareUnresizable(10 /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE); + w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; + // Refresh the letterbox. + mActivity.mRootWindowContainer.performSurfacePlacement(); + + // The letterbox should only cover the notch area, so status bar can be transparent. + assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets()); + assertTrue(statusBarController.isTransparentAllowed(w)); } - private WindowState addWindowToActivity(ActivityRecord activity) { + private static WindowState addWindowToActivity(ActivityRecord activity) { final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState( - mService.mWindowManager, mock(Session.class), new TestIWindow(), params, mActivity); + activity.mWmService, mock(Session.class), new TestIWindow(), params, activity); WindowTestsBase.makeWindowVisible(w); w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; - mActivity.addWindow(w); + activity.addWindow(w); return w; } + private static void addStatusBar(DisplayContent displayContent) { + final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); + doReturn(true).when(displayPolicy).hasStatusBar(); + displayPolicy.onConfigurationChanged(); + + final WindowTestUtils.TestWindowToken token = WindowTestUtils.createTestWindowToken( + WindowManager.LayoutParams.TYPE_STATUS_BAR, displayContent); + final WindowManager.LayoutParams attrs = + new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_STATUS_BAR); + attrs.gravity = android.view.Gravity.TOP; + attrs.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + attrs.setFitInsetsTypes(0 /* types */); + final WindowTestUtils.TestWindowState statusBar = new WindowTestUtils.TestWindowState( + displayContent.mWmService, mock(Session.class), new TestIWindow(), attrs, token); + token.addWindow(statusBar); + statusBar.setRequestedSize(displayContent.mBaseDisplayWidth, + displayContent.getDisplayUiContext().getResources().getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height)); + + displayPolicy.addWindowLw(statusBar, attrs); + displayPolicy.beginLayoutLw(displayContent.mDisplayFrames, + displayContent.getConfiguration().uiMode); + } + /** * Setup {@link #mActivity} as a size-compat-mode-able activity with fixed aspect and/or * orientation. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index 512042cdf7b9..786f8d8af024 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -150,9 +150,9 @@ public class TaskDisplayAreaTests extends WindowTestsBase { @Test public void testDisplayPositionWithPinnedStack() { - // Make sure the display is system owned display which capable to move the stack to top. + // Make sure the display is trusted display which capable to move the stack to top. spyOn(mDisplayContent); - doReturn(false).when(mDisplayContent).isUntrustedVirtualDisplay(); + doReturn(true).when(mDisplayContent).isTrusted(); // The display contains pinned stack that was added in {@link #setUp}. final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index 4907bdc5e1f0..d6ec78837f7d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -190,7 +190,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testCalculateSnapshotFrame() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 0, 10); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); assertEquals(new Rect(0, 0, 100, 80), mSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90))); } @@ -199,7 +199,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testCalculateSnapshotFrame_navBarLeft() { setupSurface(100, 100); final Rect insets = new Rect(10, 10, 0, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); assertEquals(new Rect(10, 0, 100, 90), mSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100))); } @@ -208,7 +208,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testCalculateSnapshotFrame_waterfall() { setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100)); final Rect insets = new Rect(0, 10, 0, 10); - mSurface.setFrames(new Rect(5, 0, 95, 100), insets, insets); + mSurface.setFrames(new Rect(5, 0, 95, 100), insets); assertEquals(new Rect(0, 0, 90, 90), mSurface.calculateSnapshotFrame(new Rect(5, 0, 95, 90))); } @@ -217,7 +217,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testDrawStatusBarBackground() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -230,7 +230,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testDrawStatusBarBackground_nullFrame() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -243,7 +243,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testDrawStatusBarBackground_nope() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -257,7 +257,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final Rect insets = new Rect(0, 10, 0, 10); setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -270,7 +270,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final Rect insets = new Rect(10, 10, 0, 0); setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -283,7 +283,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final Rect insets = new Rect(0, 10, 10, 0); setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java index 2c17bbeae498..2bd342420e2c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java @@ -299,6 +299,20 @@ public class TaskStackChangedListenerTest { waitForCallback(singleTaskDisplayDrawnLatch); } + public static class ActivityLaunchesNewActivityInActivityView extends TestActivity { + private boolean mActivityBLaunched = false; + + @Override + protected void onPostResume() { + super.onPostResume(); + if (mActivityBLaunched) { + return; + } + mActivityBLaunched = true; + startActivity(new Intent(this, ActivityB.class)); + } + } + @Test public void testSingleTaskDisplayEmpty() throws Exception { final Instrumentation instrumentation = getInstrumentation(); @@ -335,13 +349,20 @@ public class TaskStackChangedListenerTest { }); waitForCallback(activityViewReadyLatch); + // 1. start ActivityLaunchesNewActivityInActivityView in an ActivityView + // 2. ActivityLaunchesNewActivityInActivityView launches ActivityB + // 3. ActivityB finishes self. + // 4. Verify ITaskStackListener#onSingleTaskDisplayEmpty is not called yet. final Context context = instrumentation.getContext(); - Intent intent = new Intent(context, ActivityInActivityView.class); + Intent intent = new Intent(context, ActivityLaunchesNewActivityInActivityView.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); activityView.startActivity(intent); waitForCallback(singleTaskDisplayDrawnLatch); + UiDevice.getInstance(getInstrumentation()).waitForIdle(); assertEquals(1, singleTaskDisplayEmptyLatch.getCount()); + // 5. Release the container, and ActivityLaunchesNewActivityInActivityView finishes. + // 6. Verify ITaskStackListener#onSingleTaskDisplayEmpty is called. activityView.release(); waitForCallback(activityViewDestroyedLatch); waitForCallback(singleTaskDisplayEmptyLatch); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java index d65b084ca8c1..f1dbde066125 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java @@ -153,7 +153,8 @@ public class TaskStackTests extends WindowTestsBase { // Stack removal is deferred if one of its child is animating. doReturn(true).when(stack).hasWindowsAlive(); - doReturn(true).when(task).isAnimating(eq(TRANSITION | CHILDREN), anyInt()); + doReturn(stack).when(task).getAnimatingContainer( + eq(TRANSITION | CHILDREN), anyInt()); stack.removeIfPossible(); // For the case of deferred removal the task controller will still be connected to the its diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 00439f84702d..87485eac3412 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -66,14 +66,14 @@ import android.view.SurfaceSession; import androidx.test.filters.SmallTest; -import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; - import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import java.util.ArrayList; import java.util.Comparator; + /** * Test class for {@link WindowContainer}. * @@ -828,17 +828,21 @@ public class WindowContainerTests extends WindowTestsBase { public void testTaskCanApplyAnimation() { final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); final Task task = createTaskInStack(stack, 0 /* userId */); - final ActivityRecord activity = + final ActivityRecord activity2 = + WindowTestUtils.createActivityRecordInTask(mDisplayContent, task); + final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(mDisplayContent, task); - verifyWindowContainerApplyAnimation(task, activity); + verifyWindowContainerApplyAnimation(task, activity1, activity2); } @Test public void testStackCanApplyAnimation() { final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); - final ActivityRecord activity = WindowTestUtils.createActivityRecordInTask(mDisplayContent, + final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(mDisplayContent, createTaskInStack(stack, 0 /* userId */)); - verifyWindowContainerApplyAnimation(stack, activity); + final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(mDisplayContent, + createTaskInStack(stack, 0 /* userId */)); + verifyWindowContainerApplyAnimation(stack, activity1, activity2); } @Test @@ -871,7 +875,8 @@ public class WindowContainerTests extends WindowTestsBase { assertEquals(displayArea, displayArea.getDisplayArea()); } - private void verifyWindowContainerApplyAnimation(WindowContainer wc, ActivityRecord act) { + private void verifyWindowContainerApplyAnimation(WindowContainer wc, ActivityRecord act, + ActivityRecord act2) { // Initial remote animation for app transition. final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( new IRemoteAnimationRunner.Stub() { @@ -895,17 +900,23 @@ public class WindowContainerTests extends WindowTestsBase { wc.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(adapter); spyOn(wc); doReturn(true).when(wc).okToAnimate(); - final OnAnimationFinishedCallback onAnimationFinishedCallback = - mock(OnAnimationFinishedCallback.class); // Make sure animating state is as expected after applied animation. - assertTrue(wc.applyAnimation(null, TRANSIT_TASK_OPEN, true, false, - onAnimationFinishedCallback)); - assertEquals(wc.getTopMostActivity(), act); + + // Animation target is promoted from act to wc. act2 is a descendant of wc, but not a source + // of the animation. + ArrayList<WindowContainer<WindowState>> sources = new ArrayList<>(); + sources.add(act); + assertTrue(wc.applyAnimation(null, TRANSIT_TASK_OPEN, true, false, sources)); + + assertEquals(act, wc.getTopMostActivity()); assertTrue(wc.isAnimating()); + assertTrue(wc.isAnimating(0, ANIMATION_TYPE_APP_TRANSITION)); + assertTrue(wc.getAnimationSources().contains(act)); + assertFalse(wc.getAnimationSources().contains(act2)); assertTrue(act.isAnimating(PARENTS)); - verify(onAnimationFinishedCallback, times(0)).onAnimationFinished( - eq(ANIMATION_TYPE_APP_TRANSITION), any()); + assertTrue(act.isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)); + assertEquals(wc, act.getAnimatingContainer(PARENTS, ANIMATION_TYPE_APP_TRANSITION)); // Make sure animation finish callback will be received and reset animating state after // animation finish. @@ -914,8 +925,6 @@ public class WindowContainerTests extends WindowTestsBase { verify(wc).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), any()); assertFalse(wc.isAnimating()); assertFalse(act.isAnimating(PARENTS)); - verify(onAnimationFinishedCallback, times(1)).onAnimationFinished( - eq(ANIMATION_TYPE_APP_TRANSITION), any()); } /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index e9ed20bd9683..4a0f48cf2ccb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.hardware.camera2.params.OutputConfiguration.ROTATION_90; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -244,6 +245,12 @@ public class WindowStateTests extends WindowTestsBase { appWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE; assertTrue(appWindow.canBeImeTarget()); + // Verify PINNED windows can't be IME target. + int initialMode = appWindow.mActivityRecord.getWindowingMode(); + appWindow.mActivityRecord.setWindowingMode(WINDOWING_MODE_PINNED); + assertFalse(appWindow.canBeImeTarget()); + appWindow.mActivityRecord.setWindowingMode(initialMode); + // Make windows invisible appWindow.hideLw(false /* doAnimation */); imeWindow.hideLw(false /* doAnimation */); @@ -646,6 +653,7 @@ public class WindowStateTests extends WindowTestsBase { final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1"); win1.mHasSurface = true; win1.mSurfaceControl = mock(SurfaceControl.class); + win1.mAttrs.surfaceInsets.set(1, 2, 3, 4); win1.getFrameLw().offsetTo(WINDOW_OFFSET, 0); win1.updateSurfacePosition(t); win1.getTransformationMatrix(values, matrix); @@ -692,4 +700,14 @@ public class WindowStateTests extends WindowTestsBase { sameTokenWindow.removeImmediately(); assertFalse(sameTokenWindow.needsRelativeLayeringToIme()); } + + @Test + public void testNeedsRelativeLayeringToIme_startingWindow() { + WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING, + mAppWindow.mToken, "SameTokenWindow"); + mDisplayContent.mInputMethodTarget = mAppWindow; + sameTokenWindow.mActivityRecord.getStack().setWindowingMode( + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + assertFalse(sameTokenWindow.needsRelativeLayeringToIme()); + } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 060ed51951e4..a98da959aff9 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -780,6 +780,22 @@ public class UsageStatsService extends SystemService implements * Called by the Binder stub. */ void reportEvent(Event event, int userId) { + final int uid; + // Acquire uid outside of mLock for events that need it + switch (event.mEventType) { + case Event.ACTIVITY_RESUMED: + case Event.ACTIVITY_PAUSED: + uid = mPackageManagerInternal.getPackageUid(event.mPackage, 0, userId); + break; + default: + uid = 0; + } + + if (event.mPackage != null + && mPackageManagerInternal.isPackageEphemeral(userId, event.mPackage)) { + event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP; + } + synchronized (mLock) { // This should never be called directly when the user is locked if (!mUserUnlockedStates.get(userId)) { @@ -792,13 +808,15 @@ public class UsageStatsService extends SystemService implements final long elapsedRealtime = SystemClock.elapsedRealtime(); - if (event.mPackage != null - && mPackageManagerInternal.isPackageEphemeral(userId, event.mPackage)) { - event.mFlags |= Event.FLAG_IS_PACKAGE_INSTANT_APP; - } - switch (event.mEventType) { case Event.ACTIVITY_RESUMED: + FrameworkStatsLog.write( + FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED, + uid, + event.mPackage, + event.mClass, + FrameworkStatsLog + .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_FOREGROUND); // check if this activity has already been resumed if (mVisibleActivities.get(event.mInstanceId) != null) break; mVisibleActivities.put(event.mInstanceId, @@ -816,13 +834,6 @@ public class UsageStatsService extends SystemService implements } catch (IllegalArgumentException iae) { Slog.e(TAG, "Failed to note usage start", iae); } - FrameworkStatsLog.write( - FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED, - mPackageManagerInternal.getPackageUid(event.mPackage, 0, userId), - event.mPackage, - event.mClass, - FrameworkStatsLog - .APP_USAGE_EVENT_OCCURRED__EVENT_TYPE__MOVE_TO_FOREGROUND); break; case Event.ACTIVITY_PAUSED: if (event.mTaskRootPackage == null) { @@ -839,7 +850,7 @@ public class UsageStatsService extends SystemService implements } FrameworkStatsLog.write( FrameworkStatsLog.APP_USAGE_EVENT_OCCURRED, - mPackageManagerInternal.getPackageUid(event.mPackage, 0, userId), + uid, event.mPackage, event.mClass, FrameworkStatsLog diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index a85bd060d65e..53aad2351a32 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2829,14 +2829,12 @@ public class CarrierConfigManager { /** * A list of 4 customized LTE Reference Signal Signal to Noise Ratio (RSSNR) thresholds. * - * 4 threshold integers must be within the boundaries [-200, 300], and the levels are: - * "NONE: [-200, threshold1)" + * 4 threshold integers must be within the boundaries [-20 dB, 30 dB], and the levels are: + * "NONE: [-20, threshold1)" * "POOR: [threshold1, threshold2)" * "MODERATE: [threshold2, threshold3)" * "GOOD: [threshold3, threshold4)" - * "EXCELLENT: [threshold4, 300]" - * Note: the unit of the values is 10*db; it is derived by multiplying 10 on the original dB - * value reported by modem. + * "EXCELLENT: [threshold4, 30]" * * This key is considered invalid if the format is violated. If the key is invalid or * not configured, a default value set will apply. @@ -4198,10 +4196,10 @@ public class CarrierConfigManager { }); sDefaults.putIntArray(KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY, new int[] { - -30, /* SIGNAL_STRENGTH_POOR */ - 10, /* SIGNAL_STRENGTH_MODERATE */ - 45, /* SIGNAL_STRENGTH_GOOD */ - 130 /* SIGNAL_STRENGTH_GREAT */ + -3, /* SIGNAL_STRENGTH_POOR */ + 1, /* SIGNAL_STRENGTH_MODERATE */ + 5, /* SIGNAL_STRENGTH_GOOD */ + 13 /* SIGNAL_STRENGTH_GREAT */ }); sDefaults.putIntArray(KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY, new int[] { diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index 1993550d52b8..e6279dc977ee 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -366,7 +366,7 @@ public final class CellIdentityLte extends CellIdentity { .append(" mPci=").append(mPci) .append(" mTac=").append(mTac) .append(" mEarfcn=").append(mEarfcn) - .append(" mBands=").append(mBands) + .append(" mBands=").append(Arrays.toString(mBands)) .append(" mBandwidth=").append(mBandwidth) .append(" mMcc=").append(mMccStr) .append(" mMnc=").append(mMncStr) diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java index 8dd7bdd57841..e34bbfcde492 100644 --- a/telephony/java/android/telephony/CellIdentityNr.java +++ b/telephony/java/android/telephony/CellIdentityNr.java @@ -242,7 +242,7 @@ public final class CellIdentityNr extends CellIdentity { .append(" mPci = ").append(mPci) .append(" mTac = ").append(mTac) .append(" mNrArfcn = ").append(mNrArfcn) - .append(" mBands = ").append(mBands) + .append(" mBands = ").append(Arrays.toString(mBands)) .append(" mMcc = ").append(mMccStr) .append(" mMnc = ").append(mMncStr) .append(" mNci = ").append(mNci) diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index 2529387b19b3..c26936e4bdd0 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -118,7 +118,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P * @param rssi in dBm [-113,-51], UNKNOWN * @param rsrp in dBm [-140,-43], UNKNOWN * @param rsrq in dB [-34, 3], UNKNOWN - * @param rssnr in 10*dB [-200, +300], UNKNOWN + * @param rssnr in dB [-20, +30], UNKNOWN * @param cqi [0, 15], UNKNOWN * @param timingAdvance [0, 1282], UNKNOWN * @@ -131,7 +131,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P mSignalStrength = mRssi; mRsrp = inRangeOrUnavailable(rsrp, -140, -43); mRsrq = inRangeOrUnavailable(rsrq, -34, 3); - mRssnr = inRangeOrUnavailable(rssnr, -200, 300); + mRssnr = inRangeOrUnavailable(rssnr, -20, 30); mCqi = inRangeOrUnavailable(cqi, 0, 15); mTimingAdvance = inRangeOrUnavailable(timingAdvance, 0, 1282); updateLevel(null, null); @@ -143,7 +143,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P this(convertRssiAsuToDBm(lte.signalStrength), lte.rsrp != CellInfo.UNAVAILABLE ? -lte.rsrp : lte.rsrp, lte.rsrq != CellInfo.UNAVAILABLE ? -lte.rsrq : lte.rsrq, - lte.rssnr, lte.cqi, lte.timingAdvance); + convertRssnrUnitFromTenDbToDB(lte.rssnr), lte.cqi, lte.timingAdvance); } /** @hide */ @@ -208,10 +208,10 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P }; // Lifted from Default carrier configs and max range of RSSNR private static final int[] sRssnrThresholds = new int[] { - -30, /* SIGNAL_STRENGTH_POOR */ - 10, /* SIGNAL_STRENGTH_MODERATE */ - 45, /* SIGNAL_STRENGTH_GOOD */ - 130 /* SIGNAL_STRENGTH_GREAT */ + -3, /* SIGNAL_STRENGTH_POOR */ + 1, /* SIGNAL_STRENGTH_MODERATE */ + 5, /* SIGNAL_STRENGTH_GOOD */ + 13 /* SIGNAL_STRENGTH_GREAT */ }; private static final int sRsrpBoost = 0; @@ -556,6 +556,10 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P Rlog.w(LOG_TAG, s); } + private static int convertRssnrUnitFromTenDbToDB(int rssnr) { + return rssnr / 10; + } + private static int convertRssiAsuToDBm(int rssiAsu) { if (rssiAsu == SIGNAL_STRENGTH_LTE_RSSI_ASU_UNKNOWN) { return CellInfo.UNAVAILABLE; diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java index 4273f5a4a16e..af62ba4b93a1 100644 --- a/telephony/java/android/telephony/PhysicalChannelConfig.java +++ b/telephony/java/android/telephony/PhysicalChannelConfig.java @@ -19,8 +19,8 @@ package android.telephony; import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; - import android.telephony.Annotation.NetworkType; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; @@ -241,13 +241,13 @@ public final class PhysicalChannelConfig implements Parcelable { .append(",mCellBandwidthDownlinkKhz=") .append(mCellBandwidthDownlinkKhz) .append(",mRat=") - .append(mRat) + .append(TelephonyManager.getNetworkTypeName(mRat)) .append(",mFrequencyRange=") - .append(mFrequencyRange) + .append(ServiceState.frequencyRangeToString(mFrequencyRange)) .append(",mChannelNumber=") .append(mChannelNumber) .append(",mContextIds=") - .append(mContextIds.toString()) + .append(Arrays.toString(mContextIds)) .append(",mPhysicalCellId=") .append(mPhysicalCellId) .append("}") diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index c6b06b467782..9e2ba6875577 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -1033,6 +1033,26 @@ public class ServiceState implements Parcelable { } /** + * Convert frequency range into string + * + * @param range The cellular frequency range + * @return Frequency range in string format + * + * @hide + */ + public static @NonNull String frequencyRangeToString(@FrequencyRange int range) { + switch (range) { + case FREQUENCY_RANGE_UNKNOWN: return "UNKNOWN"; + case FREQUENCY_RANGE_LOW: return "LOW"; + case FREQUENCY_RANGE_MID: return "MID"; + case FREQUENCY_RANGE_HIGH: return "HIGH"; + case FREQUENCY_RANGE_MMWAVE: return "MMWAVE"; + default: + return Integer.toString(range); + } + } + + /** * Convert RIL Service State to String * * @param serviceState diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index fadebaa7bb8a..ee146089b852 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -8197,6 +8197,140 @@ public class TelephonyManager { return false; } + /** @hide */ + @IntDef({ + ALLOWED_NETWORK_TYPES_REASON_POWER + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AllowedNetworkTypesReason{} + + /** + * To indicate allowed network type change is requested by power manager. + * Power Manger configuration won't affect the settings configured through + * {@link setAllowedNetworkTypes} and will result in allowing network types that are in both + * configurations (i.e intersection of both sets). + * @hide + */ + public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 0; + + /** + * Set the allowed network types of the device and + * provide the reason triggering the allowed network change. + * This can be called for following reasons + * <ol> + * <li>Allowed network types control by power manager + * {@link #ALLOWED_NETWORK_TYPES_REASON_POWER} + * </ol> + * This API will result in allowing an intersection of allowed network types for all reasons, + * including the configuration done through {@link setAllowedNetworkTypes}. + * While this API and {@link setAllowedNetworkTypes} is controlling allowed network types + * on device, user preference will still be set through {@link #setPreferredNetworkTypeBitmask}. + * Thus resultant network type configured on modem will be an intersection of the network types + * from setAllowedNetworkTypesForReason, {@link setAllowedNetworkTypes} + * and {@link #setPreferredNetworkTypeBitmask}. + * + * @param reason the reason the allowed network type change is taking place + * @param allowedNetworkTypes The bitmask of allowed network types. + * @throws IllegalStateException if the Telephony process is not currently available. + * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setAllowedNetworkTypesForReason(@AllowedNetworkTypesReason int reason, + @NetworkTypeBitMask long allowedNetworkTypes) { + if (reason != ALLOWED_NETWORK_TYPES_REASON_POWER) { + throw new IllegalArgumentException("invalid AllowedNetworkTypesReason."); + } + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.setAllowedNetworkTypesForReason(getSubId(), reason, + allowedNetworkTypes); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "setAllowedNetworkTypesForReason RemoteException", ex); + ex.rethrowFromSystemServer(); + } + } + + /** + * Get the allowed network types for certain reason. + * + * {@link #getAllowedNetworkTypesForReason} returns allowed network type for a + * specific reason. For effective allowed network types configured on device, + * query {@link getEffectiveAllowedNetworkTypes} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + *s + * @param reason the reason the allowed network type change is taking place + * @return the allowed network type bitmask + * @throws IllegalStateException if the Telephony process is not currently available. + * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @NetworkTypeBitMask long getAllowedNetworkTypesForReason( + @AllowedNetworkTypesReason int reason) { + if (reason != ALLOWED_NETWORK_TYPES_REASON_POWER) { + throw new IllegalArgumentException("invalid AllowedNetworkTypesReason."); + } + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getAllowedNetworkTypesForReason(getSubId(), reason); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getAllowedNetworkTypesForReason RemoteException", ex); + ex.rethrowFromSystemServer(); + } + return -1; + } + + /** + * Get bit mask of all network types. + * + * @return bit mask of all network types + * @hide + */ + public static @NetworkTypeBitMask long getAllNetworkTypesBitmask() { + return NETWORK_STANDARDS_FAMILY_BITMASK_3GPP | NETWORK_STANDARDS_FAMILY_BITMASK_3GPP2; + } + + /** + * Get the allowed network types configured on the device. + * This API will return an intersection of allowed network types for all reasons, + * including the configuration done through setAllowedNetworkTypes + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @return the allowed network type bitmask + * @throws IllegalStateException if the Telephony process is not currently available. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @NetworkTypeBitMask long getEffectiveAllowedNetworkTypes() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getEffectiveAllowedNetworkTypes(getSubId()); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getEffectiveAllowedNetworkTypes RemoteException", ex); + ex.rethrowFromSystemServer(); + } + return -1; + } + /** * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA. * diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 369020033a59..b70937cee8a1 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -956,6 +956,35 @@ interface ITelephony { boolean setAllowedNetworkTypes(int subId, long allowedNetworkTypes); /** + * Get the allowed network types for certain reason. + * + * @param subId the id of the subscription. + * @param reason the reason the allowed network type change is taking place + * @return allowedNetworkTypes the allowed network types. + */ + long getAllowedNetworkTypesForReason(int subId, int reason); + + /** + * Get the effective allowed network types on the device. This API will + * return an intersection of allowed network types for all reasons, + * including the configuration done through setAllowedNetworkTypes + * + * @param subId the id of the subscription. + * @return allowedNetworkTypes the allowed network types. + */ + long getEffectiveAllowedNetworkTypes(int subId); + + /** + * Set the allowed network types and provide the reason triggering the allowed network change. + * + * @param subId the id of the subscription. + * @param reason the reason the allowed network type change is taking place + * @param allowedNetworkTypes the allowed network types. + * @return true on success; false on any failure. + */ + boolean setAllowedNetworkTypesForReason(int subId, int reason, long allowedNetworkTypes); + + /** * Set the preferred network type. * Used for device configuration by some CDMA operators. * diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index 0fc9be32f4cf..6eba62e63740 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -16,6 +16,8 @@ package android.net; +import static android.net.RouteInfo.RTN_THROW; +import static android.net.RouteInfo.RTN_UNICAST; import static android.net.RouteInfo.RTN_UNREACHABLE; import static com.android.testutils.ParcelUtilsKt.assertParcelSane; @@ -1282,4 +1284,20 @@ public class LinkPropertiesTest { assertTrue(lp.hasIpv6UnreachableDefaultRoute()); assertFalse(lp.hasIpv4UnreachableDefaultRoute()); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testRouteAddWithSameKey() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan0"); + final IpPrefix v6 = new IpPrefix("64:ff9b::/96"); + lp.addRoute(new RouteInfo(v6, address("fe80::1"), "wlan0", RTN_UNICAST, 1280)); + assertEquals(1, lp.getRoutes().size()); + lp.addRoute(new RouteInfo(v6, address("fe80::1"), "wlan0", RTN_UNICAST, 1500)); + assertEquals(1, lp.getRoutes().size()); + final IpPrefix v4 = new IpPrefix("192.0.2.128/25"); + lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_UNICAST, 1460)); + assertEquals(2, lp.getRoutes().size()); + lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_THROW, 1460)); + assertEquals(2, lp.getRoutes().size()); + } } diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java index 8204b494bbb8..60cac0b6b0f5 100644 --- a/tests/net/common/java/android/net/RouteInfoTest.java +++ b/tests/net/common/java/android/net/RouteInfoTest.java @@ -25,6 +25,7 @@ import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -56,7 +57,7 @@ public class RouteInfoTest { private static final int INVALID_ROUTE_TYPE = -1; private InetAddress Address(String addr) { - return InetAddress.parseNumericAddress(addr); + return InetAddresses.parseNumericAddress(addr); } private IpPrefix Prefix(String prefix) { @@ -391,4 +392,43 @@ public class RouteInfoTest { r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0"); assertEquals(0, r.getMtu()); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testRouteKey() { + RouteInfo.RouteKey k1, k2; + // Only prefix, null gateway and null interface + k1 = new RouteInfo(Prefix("2001:db8::/128"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("2001:db8::/128"), null).getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // With prefix, gateway and interface. Type and MTU does not affect RouteKey equality + k1 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0", + RTN_UNREACHABLE, 1450).getRouteKey(); + k2 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0", + RouteInfo.RTN_UNICAST, 1400).getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // Different scope IDs are ignored by the kernel, so we consider them equal here too. + k1 = new RouteInfo(Prefix("2001:db8::/64"), Address("fe80::1%1"), "wlan0").getRouteKey(); + k2 = new RouteInfo(Prefix("2001:db8::/64"), Address("fe80::1%2"), "wlan0").getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // Different prefix + k1 = new RouteInfo(Prefix("192.0.2.0/24"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("192.0.3.0/24"), null).getRouteKey(); + assertNotEquals(k1, k2); + + // Different gateway + k1 = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::1"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::2"), null).getRouteKey(); + assertNotEquals(k1, k2); + + // Different interface + k1 = new RouteInfo(Prefix("ff02::1/128"), null, "tun0").getRouteKey(); + k2 = new RouteInfo(Prefix("ff02::1/128"), null, "tun1").getRouteKey(); + assertNotEquals(k1, k2); + } } diff --git a/tests/net/java/android/net/DnsPacketTest.java b/tests/net/java/android/net/DnsPacketTest.java deleted file mode 100644 index 975abf416944..000000000000 --- a/tests/net/java/android/net/DnsPacketTest.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class DnsPacketTest { - private void assertHeaderParses(DnsPacket.DnsHeader header, int id, int flag, - int qCount, int aCount, int nsCount, int arCount) { - assertEquals(header.id, id); - assertEquals(header.flags, flag); - assertEquals(header.getRecordCount(DnsPacket.QDSECTION), qCount); - assertEquals(header.getRecordCount(DnsPacket.ANSECTION), aCount); - assertEquals(header.getRecordCount(DnsPacket.NSSECTION), nsCount); - assertEquals(header.getRecordCount(DnsPacket.ARSECTION), arCount); - } - - private void assertRecordParses(DnsPacket.DnsRecord record, String dname, - int dtype, int dclass, int ttl, byte[] rr) { - assertEquals(record.dName, dname); - assertEquals(record.nsType, dtype); - assertEquals(record.nsClass, dclass); - assertEquals(record.ttl, ttl); - assertTrue(Arrays.equals(record.getRR(), rr)); - } - - class TestDnsPacket extends DnsPacket { - TestDnsPacket(byte[] data) throws ParseException { - super(data); - } - - public DnsHeader getHeader() { - return mHeader; - } - public List<DnsRecord> getRecordList(int secType) { - return mRecords[secType]; - } - } - - @Test - public void testNullDisallowed() { - try { - new TestDnsPacket(null); - fail("Exception not thrown for null byte array"); - } catch (ParseException e) { - } - } - - @Test - public void testV4Answer() throws Exception { - final byte[] v4blob = new byte[] { - /* Header */ - 0x55, 0x66, /* Transaction ID */ - (byte) 0x81, (byte) 0x80, /* Flags */ - 0x00, 0x01, /* Questions */ - 0x00, 0x01, /* Answer RRs */ - 0x00, 0x00, /* Authority RRs */ - 0x00, 0x00, /* Additional RRs */ - /* Queries */ - 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, - 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ - 0x00, 0x01, /* Type */ - 0x00, 0x01, /* Class */ - /* Answers */ - (byte) 0xc0, 0x0c, /* Name */ - 0x00, 0x01, /* Type */ - 0x00, 0x01, /* Class */ - 0x00, 0x00, 0x01, 0x2b, /* TTL */ - 0x00, 0x04, /* Data length */ - (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */ - }; - TestDnsPacket packet = new TestDnsPacket(v4blob); - - // Header part - assertHeaderParses(packet.getHeader(), 0x5566, 0x8180, 1, 1, 0, 0); - - // Record part - List<DnsPacket.DnsRecord> qdRecordList = - packet.getRecordList(DnsPacket.QDSECTION); - assertEquals(qdRecordList.size(), 1); - assertRecordParses(qdRecordList.get(0), "www.google.com", 1, 1, 0, null); - - List<DnsPacket.DnsRecord> anRecordList = - packet.getRecordList(DnsPacket.ANSECTION); - assertEquals(anRecordList.size(), 1); - assertRecordParses(anRecordList.get(0), "www.google.com", 1, 1, 0x12b, - new byte[]{ (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 }); - } - - @Test - public void testV6Answer() throws Exception { - final byte[] v6blob = new byte[] { - /* Header */ - 0x77, 0x22, /* Transaction ID */ - (byte) 0x81, (byte) 0x80, /* Flags */ - 0x00, 0x01, /* Questions */ - 0x00, 0x01, /* Answer RRs */ - 0x00, 0x00, /* Authority RRs */ - 0x00, 0x00, /* Additional RRs */ - /* Queries */ - 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65, - 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */ - 0x00, 0x1c, /* Type */ - 0x00, 0x01, /* Class */ - /* Answers */ - (byte) 0xc0, 0x0c, /* Name */ - 0x00, 0x1c, /* Type */ - 0x00, 0x01, /* Class */ - 0x00, 0x00, 0x00, 0x37, /* TTL */ - 0x00, 0x10, /* Data length */ - 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 /* Address */ - }; - TestDnsPacket packet = new TestDnsPacket(v6blob); - - // Header part - assertHeaderParses(packet.getHeader(), 0x7722, 0x8180, 1, 1, 0, 0); - - // Record part - List<DnsPacket.DnsRecord> qdRecordList = - packet.getRecordList(DnsPacket.QDSECTION); - assertEquals(qdRecordList.size(), 1); - assertRecordParses(qdRecordList.get(0), "www.google.com", 28, 1, 0, null); - - List<DnsPacket.DnsRecord> anRecordList = - packet.getRecordList(DnsPacket.ANSECTION); - assertEquals(anRecordList.size(), 1); - assertRecordParses(anRecordList.get(0), "www.google.com", 28, 1, 0x37, - new byte[]{ 0x24, 0x04, 0x68, 0x00, 0x40, 0x05, 0x08, 0x0d, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04 }); - } -} diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java index 7748288aeb05..3158cc8637e4 100644 --- a/tests/net/java/android/net/NetworkUtilsTest.java +++ b/tests/net/java/android/net/NetworkUtilsTest.java @@ -16,10 +16,24 @@ package android.net; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.EPERM; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_STREAM; + import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.system.ErrnoException; +import android.system.Os; + import androidx.test.runner.AndroidJUnit4; +import libcore.io.IoUtils; + import org.junit.Test; import org.junit.runner.RunWith; @@ -125,4 +139,50 @@ public class NetworkUtilsTest { assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536), NetworkUtils.routedIPv6AddressCount(set)); } + + private static void expectSocketSuccess(String msg, int domain, int type) { + try { + IoUtils.closeQuietly(Os.socket(domain, type, 0)); + } catch (ErrnoException e) { + fail(msg + e.getMessage()); + } + } + + private static void expectSocketPemissionError(String msg, int domain, int type) { + try { + IoUtils.closeQuietly(Os.socket(domain, type, 0)); + fail(msg); + } catch (ErrnoException e) { + assertEquals(msg, e.errno, EPERM); + } + } + + private static void expectHasNetworking() { + expectSocketSuccess("Creating a UNIX socket should not have thrown ErrnoException", + AF_UNIX, SOCK_STREAM); + expectSocketSuccess("Creating a AF_INET socket shouldn't have thrown ErrnoException", + AF_INET, SOCK_DGRAM); + expectSocketSuccess("Creating a AF_INET6 socket shouldn't have thrown ErrnoException", + AF_INET6, SOCK_DGRAM); + } + + private static void expectNoNetworking() { + expectSocketSuccess("Creating a UNIX socket should not have thrown ErrnoException", + AF_UNIX, SOCK_STREAM); + expectSocketPemissionError( + "Creating a AF_INET socket should have thrown ErrnoException(EPERM)", + AF_INET, SOCK_DGRAM); + expectSocketPemissionError( + "Creating a AF_INET6 socket should have thrown ErrnoException(EPERM)", + AF_INET6, SOCK_DGRAM); + } + + @Test + public void testSetAllowNetworkingForProcess() { + expectHasNetworking(); + NetworkUtils.setAllowNetworkingForProcess(false); + expectNoNetworking(); + NetworkUtils.setAllowNetworkingForProcess(true); + expectHasNetworking(); + } } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index c86d388a3db9..385005f90c3b 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -100,6 +100,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.any; @@ -164,6 +165,8 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MatchAllNetworkSpecifier; import android.net.Network; +import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkFactory; import android.net.NetworkInfo; @@ -6800,6 +6803,30 @@ public class ConnectivityServiceTest { assertEquals(wifiLp, mService.getActiveLinkProperties()); } + @Test + public void testLegacyExtraInfoSentToNetworkMonitor() throws Exception { + class TestNetworkAgent extends NetworkAgent { + TestNetworkAgent(Context context, Looper looper, NetworkAgentConfig config) { + super(context, looper, "MockAgent", new NetworkCapabilities(), + new LinkProperties(), 40 , config, null /* provider */); + } + } + final NetworkAgent naNoExtraInfo = new TestNetworkAgent( + mServiceContext, mCsHandlerThread.getLooper(), new NetworkAgentConfig()); + naNoExtraInfo.register(); + verify(mNetworkStack).makeNetworkMonitor(any(), isNull(String.class), any()); + naNoExtraInfo.unregister(); + + reset(mNetworkStack); + final NetworkAgentConfig config = + new NetworkAgentConfig.Builder().setLegacyExtraInfo("legacyinfo").build(); + final NetworkAgent naExtraInfo = new TestNetworkAgent( + mServiceContext, mCsHandlerThread.getLooper(), config); + naExtraInfo.register(); + verify(mNetworkStack).makeNetworkMonitor(any(), eq("legacyinfo"), any()); + naExtraInfo.unregister(); + } + private void setupLocationPermissions( int targetSdk, boolean locationToggle, String op, String perm) throws Exception { final ApplicationInfo applicationInfo = new ApplicationInfo(); diff --git a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java new file mode 100644 index 000000000000..058856dcd6fb --- /dev/null +++ b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Looper; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import com.android.internal.util.CollectionUtils; +import com.android.server.net.NetworkStatsSubscriptionsMonitor.Delegate; +import com.android.server.net.NetworkStatsSubscriptionsMonitor.RatTypeListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +@RunWith(JUnit4.class) +public final class NetworkStatsSubscriptionsMonitorTest { + private static final int TEST_SUBID1 = 3; + private static final int TEST_SUBID2 = 5; + private static final String TEST_IMSI1 = "466921234567890"; + private static final String TEST_IMSI2 = "466920987654321"; + private static final String TEST_IMSI3 = "466929999999999"; + + @Mock private Context mContext; + @Mock private PhoneStateListener mPhoneStateListener; + @Mock private SubscriptionManager mSubscriptionManager; + @Mock private TelephonyManager mTelephonyManager; + @Mock private Delegate mDelegate; + private final List<Integer> mTestSubList = new ArrayList<>(); + + private final Executor mExecutor = Executors.newSingleThreadExecutor(); + private NetworkStatsSubscriptionsMonitor mMonitor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); + + when(mContext.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE))) + .thenReturn(mSubscriptionManager); + when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE))) + .thenReturn(mTelephonyManager); + + mMonitor = new NetworkStatsSubscriptionsMonitor(mContext, mExecutor, mDelegate); + } + + @Test + public void testStartStop() { + // Verify that addOnSubscriptionsChangedListener() is never called before start(). + verify(mSubscriptionManager, never()) + .addOnSubscriptionsChangedListener(mExecutor, mMonitor); + mMonitor.start(); + verify(mSubscriptionManager).addOnSubscriptionsChangedListener(mExecutor, mMonitor); + + // Verify that removeOnSubscriptionsChangedListener() is never called before stop() + verify(mSubscriptionManager, never()).removeOnSubscriptionsChangedListener(mMonitor); + mMonitor.stop(); + verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(mMonitor); + } + + @NonNull + private static int[] convertArrayListToIntArray(@NonNull List<Integer> arrayList) { + final int[] list = new int[arrayList.size()]; + for (int i = 0; i < arrayList.size(); i++) { + list[i] = arrayList.get(i); + } + return list; + } + + private void setRatTypeForSub(List<RatTypeListener> listeners, + int subId, int type) { + final ServiceState serviceState = mock(ServiceState.class); + when(serviceState.getDataNetworkType()).thenReturn(type); + final RatTypeListener match = CollectionUtils + .find(listeners, it -> it.getSubId() == subId); + if (match != null) { + match.onServiceStateChanged(serviceState); + } + } + + private void addTestSub(int subId, String subscriberId) { + // add SubId to TestSubList. + if (!mTestSubList.contains(subId)) { + mTestSubList.add(subId); + } + final int[] subList = convertArrayListToIntArray(mTestSubList); + when(mSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(subList); + when(mTelephonyManager.getSubscriberId(subId)).thenReturn(subscriberId); + mMonitor.onSubscriptionsChanged(); + } + + private void removeTestSub(int subId) { + // Remove subId from TestSubList. + mTestSubList.removeIf(it -> it == subId); + final int[] subList = convertArrayListToIntArray(mTestSubList); + when(mSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(subList); + mMonitor.onSubscriptionsChanged(); + } + + private void assertRatTypeChangedForSub(String subscriberId, int ratType) { + assertEquals(mMonitor.getRatTypeForSubscriberId(subscriberId), ratType); + final ArgumentCaptor<Integer> typeCaptor = ArgumentCaptor.forClass(Integer.class); + // Verify callback with the subscriberId and the RAT type should be as expected. + // It will fail if get a callback with an unexpected RAT type. + verify(mDelegate).onCollapsedRatTypeChanged(eq(subscriberId), typeCaptor.capture()); + final int type = typeCaptor.getValue(); + assertEquals(ratType, type); + } + + private void assertRatTypeNotChangedForSub(String subscriberId, int ratType) { + assertEquals(mMonitor.getRatTypeForSubscriberId(subscriberId), ratType); + // Should never get callback with any RAT type. + verify(mDelegate, never()).onCollapsedRatTypeChanged(eq(subscriberId), anyInt()); + } + + @Test + public void testSubChangedAndRatTypeChanged() { + final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor = + ArgumentCaptor.forClass(RatTypeListener.class); + + mMonitor.start(); + // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback + // before changing RAT type. + addTestSub(TEST_SUBID1, TEST_IMSI1); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + + // Insert sim2. + addTestSub(TEST_SUBID2, TEST_IMSI2); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + verify(mTelephonyManager, times(2)).listen(ratTypeListenerCaptor.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + reset(mDelegate); + + // Set RAT type of sim1 to UMTS. + // Verify RAT type of sim1 after subscription gets onCollapsedRatTypeChanged() callback + // and others remain untouched. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Set RAT type of sim2 to LTE. + // Verify RAT type of sim2 after subscription gets onCollapsedRatTypeChanged() callback + // and others remain untouched. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID2, + TelephonyManager.NETWORK_TYPE_LTE); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_LTE); + assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Remove sim2 and verify that callbacks are fired and RAT type is correct for sim2. + // while the other two remain untouched. + removeTestSub(TEST_SUBID2); + verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Set RAT type of sim1 to UNKNOWN. Then stop monitoring subscription changes + // and verify that the listener for sim1 is removed. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + mMonitor.stop(); + verify(mTelephonyManager, times(2)).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + } +} diff --git a/wifi/Android.bp b/wifi/Android.bp index 9c5b7b66f2a3..941ff61b3ba5 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -12,6 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +java_defaults { + name: "wifi-module-sdk-version-defaults", + min_sdk_version: "30", + target_sdk_version: "30", +} + filegroup { name: "framework-wifi-updatable-exported-aidl-sources", srcs: ["aidl-export/**/*.aidl"], @@ -73,6 +79,7 @@ test_access_hidden_api_whitelist = [ // classes before they are renamed. java_library { name: "framework-wifi-pre-jarjar", + defaults: ["wifi-module-sdk-version-defaults"], sdk_version: "module_current", static_libs: [ "framework-wifi-util-lib", @@ -98,7 +105,10 @@ java_library { // post-jarjar version of framework-wifi java_sdk_library { name: "framework-wifi", - defaults: ["framework-module-defaults"], + defaults: [ + "framework-module-defaults", + "wifi-module-sdk-version-defaults", + ], static_libs: [ "framework-wifi-util-lib", "android.hardware.wifi-V1.0-java-constants", diff --git a/wifi/java/android/net/wifi/SoftApCapability.java b/wifi/java/android/net/wifi/SoftApCapability.java index 18b26db1b020..dcb57ecc933f 100644 --- a/wifi/java/android/net/wifi/SoftApCapability.java +++ b/wifi/java/android/net/wifi/SoftApCapability.java @@ -102,7 +102,9 @@ public final class SoftApCapability implements Parcelable { /** * Returns true when all of the queried features are supported, otherwise false. * - * @param features One or combination of the features from {@link @HotspotFeatures} + * @param features One or combination of the following features: + * {@link #SOFTAP_FEATURE_ACS_OFFLOAD}, {@link #SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT} or + * {@link #SOFTAP_FEATURE_WPA3_SAE}. */ public boolean areFeaturesSupported(@HotspotFeatures long features) { return (mSupportedFeatures & features) == features; @@ -122,7 +124,9 @@ public final class SoftApCapability implements Parcelable { * Constructor with combination of the feature. * Zero to no supported feature. * - * @param features One or combination of the features from {@link @HotspotFeatures}. + * @param features One or combination of the following features: + * {@link #SOFTAP_FEATURE_ACS_OFFLOAD}, {@link #SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT} or + * {@link #SOFTAP_FEATURE_WPA3_SAE}. * @hide */ public SoftApCapability(@HotspotFeatures long features) { diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index 457e0db9dc54..2bcd4f4241a6 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -165,7 +165,8 @@ public final class SoftApConfiguration implements Parcelable { /** * The operating band of the AP. - * One of the band types from {@link @BandType}. + * One or combination of the following band type: + * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}. */ private final @BandType int mBand; @@ -181,7 +182,11 @@ public final class SoftApConfiguration implements Parcelable { /** * The operating security type of the AP. - * One of the security types from {@link @SecurityType} + * One of the following security types: + * {@link #SECURITY_TYPE_OPEN}, + * {@link #SECURITY_TYPE_WPA2_PSK}, + * {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION}, + * {@link #SECURITY_TYPE_WPA3_SAE} */ private final @SecurityType int mSecurityType; @@ -393,8 +398,12 @@ public final class SoftApConfiguration implements Parcelable { } /** - * Returns {@link BandType} set to be the band for the AP. - * {@link Builder#setBand(@BandType int)}. + * Returns band type set to be the band for the AP. + * + * One or combination of the following band type: + * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}. + * + * {@link Builder#setBand(int)}. * * @hide */ @@ -679,15 +688,19 @@ public final class SoftApConfiguration implements Parcelable { /** * Specifies that this AP should use specific security type with the given ASCII passphrase. * - * @param securityType one of the security types from {@link @SecurityType}. - * @param passphrase The passphrase to use for sepcific {@link @SecurityType} configuration - * or null with {@link @SecurityType#SECURITY_TYPE_OPEN}. + * @param securityType One of the following security types: + * {@link #SECURITY_TYPE_OPEN}, + * {@link #SECURITY_TYPE_WPA2_PSK}, + * {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION}, + * {@link #SECURITY_TYPE_WPA3_SAE}. + * @param passphrase The passphrase to use for sepcific {@code securityType} configuration + * or null with {@link #SECURITY_TYPE_OPEN}. * * @return Builder for chaining. * @throws IllegalArgumentException when the passphrase length is invalid and - * {@code securityType} is not {@link @SecurityType#SECURITY_TYPE_OPEN} + * {@code securityType} is not {@link #SECURITY_TYPE_OPEN} * or non-null passphrase and {@code securityType} is - * {@link @SecurityType#SECURITY_TYPE_OPEN}. + * {@link #SECURITY_TYPE_OPEN}. */ @NonNull public Builder setPassphrase(@Nullable String passphrase, @SecurityType int securityType) { @@ -735,9 +748,10 @@ public final class SoftApConfiguration implements Parcelable { /** * Specifies the band for the AP. * <p> - * <li>If not set, defaults to BAND_2GHZ {@link @BandType}.</li> + * <li>If not set, defaults to {@link #BAND_2GHZ}.</li> * - * @param band One or combination of the band types from {@link @BandType}. + * @param band One or combination of the following band type: + * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}. * @return Builder for chaining. */ @NonNull @@ -758,7 +772,7 @@ public final class SoftApConfiguration implements Parcelable { * <p> * The default for the channel is a the special value 0 to have the framework * auto-select a valid channel from the band configured with - * {@link #setBand(@BandType int)}. + * {@link #setBand(int)}. * * The channel auto selection will offload to driver when * {@link SoftApCapability#areFeaturesSupported( diff --git a/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java index 3215246a9c1f..4116234c4c8d 100644 --- a/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -1039,11 +1039,11 @@ public class WifiNl80211Manager { * The result depends on the on the country code that has been set. * * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants. - * The following bands are supported {@link @WifiScanner.WifiBandBasic}: - * WifiScanner.WIFI_BAND_24_GHZ - * WifiScanner.WIFI_BAND_5_GHZ - * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY - * WifiScanner.WIFI_BAND_6_GHZ + * The following bands are supported: + * {@link WifiScanner#WIFI_BAND_24_GHZ}, + * {@link WifiScanner#WIFI_BAND_5_GHZ}, + * {@link WifiScanner#WIFI_BAND_5_GHZ_DFS_ONLY}, + * {@link WifiScanner#WIFI_BAND_6_GHZ} * @return frequencies vector of valid frequencies (MHz), or an empty array for error. * @throws IllegalArgumentException if band is not recognized. */ |