diff options
307 files changed, 6199 insertions, 2332 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..7e8c90632fd9 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -54,6 +54,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 +63,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; @@ -349,14 +353,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,6 +416,49 @@ class BlobMetadata { return true; } + 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); + 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); + } + final byte[] committersBytes = proto.getBytes(); + + 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); + 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(); + } + } + void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { fout.println("blobHandle:"); fout.increaseIndent(); 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..381efc10b416 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()); @@ -1492,7 +1555,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 +1747,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..77ca4aa7a9e6 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -53,12 +53,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 +100,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 +210,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 +251,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 +266,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 +420,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } mState = state; - revokeAllFdsLocked(); + revokeAllFds(); if (sendCallback) { mListener.onStateChanged(this); @@ -427,26 +451,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 +479,6 @@ class BlobStoreSession extends IBlobStoreSession.Stub { mRevocableFds.remove(revocableFd); } }); - return revocableFd.getRevocableFileDescriptor(); } @Nullable 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..062108757349 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -233,7 +233,7 @@ 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") + @GuardedBy("mHeadlessSystemApps") private final ArrayMap<String, Boolean> mHeadlessSystemApps = new ArrayMap<>(); private final CountDownLatch mAdminDataAvailableLatch = new CountDownLatch(1); @@ -447,7 +447,8 @@ public class AppStandbyController implements AppStandbyInternal { 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); @@ -1121,7 +1122,9 @@ public class AppStandbyController implements AppStandbyInternal { } private boolean isHeadlessSystemApp(String packageName) { - return mHeadlessSystemApps.containsKey(packageName); + synchronized (mHeadlessSystemApps) { + return mHeadlessSystemApps.containsKey(packageName); + } } @Override @@ -1697,19 +1700,24 @@ public class AppStandbyController implements AppStandbyInternal { 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()) { + return false; + } + synchronized (mHeadlessSystemApps) { + if (pkgInfo.activities == null || pkgInfo.activities.length == 0) { + // Headless system app. + return mHeadlessSystemApps.put(pkgInfo.packageName, true) == null; + } else { + return mHeadlessSystemApps.remove(pkgInfo.packageName) != null; } } } @@ -1754,7 +1762,12 @@ public class AppStandbyController implements AppStandbyInternal { 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(); + } } } @@ -1852,9 +1865,12 @@ 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.keyAt(i)); + pw.println(","); + } } pw.println("]"); pw.println(); 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/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 2bbce11da2ff..278278fc18c4 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -470,13 +470,24 @@ message Atom { 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"]; // 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"]; @@ -567,7 +578,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"]; @@ -4897,6 +4908,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 // ////////////////////////////////////////////////////////////////////// @@ -10549,3 +10648,306 @@ 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; +} + +// 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/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/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 13d977fa2563..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; @@ -1836,6 +1835,11 @@ TEST(StatsLogProcessorTest, TestDumpReportWithoutErasingDataDoesNotUpdateTimesta 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); 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/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..e620f1641acd 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(); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 1f90e401dee5..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> 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/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/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/provider/Settings.java b/core/java/android/provider/Settings.java index e10fceaa5bc7..4f0a9728fcf8 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; @@ -6238,6 +6239,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/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/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 9a9396c45b66..de786bed3144 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 @@ -485,6 +492,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** Set of inset types for which an animation was started since last resetting this field */ private @InsetsType int mLastStartedAnimTypes; + /** Set of inset types which cannot be controlled by the user animation */ + private @InsetsType int mLastDisabledUserAnimationInsetsTypes; + + private Runnable mInvokeControllableInsetsChangedListeners = + this::invokeControllableInsetsChangedListeners; + public InsetsController(Host host) { this(host, (controller, type) -> { if (type == ITYPE_IME) { @@ -599,9 +612,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void updateState(InsetsState newState) { mState.setDisplayFrame(newState.getDisplayFrame()); + @InsetsType int disabledUserAnimationTypes = 0; + @InsetsType int[] cancelledUserAnimationTypes = {0}; for (int i = newState.getSourcesCount() - 1; i >= 0; i--) { InsetsSource source = newState.sourceAt(i); - getSourceConsumer(source.getType()).updateSource(source); + @InternalInsetsType int internalInsetsType = source.getType(); + @AnimationType int animationType = getAnimationType(internalInsetsType); + if (source.isVisibleFrameEmpty()) { + @InsetsType int insetsType = toPublicType(internalInsetsType); + // The user animation is not allowed when visible frame is empty. + disabledUserAnimationTypes |= insetsType; + if (animationType == ANIMATION_TYPE_USER) { + // Existing user animation needs to be cancelled. + animationType = ANIMATION_TYPE_NONE; + cancelledUserAnimationTypes[0] |= insetsType; + } + } + getSourceConsumer(internalInsetsType).updateSource(source, animationType); } for (int i = mState.getSourcesCount() - 1; i >= 0; i--) { InsetsSource source = mState.sourceAt(i); @@ -613,6 +640,27 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top, mFrame.right, mFrame.top + mCaptionInsetsHeight)); } + + updateDisabledUserAnimationTypes(disabledUserAnimationTypes); + + if (cancelledUserAnimationTypes[0] != 0) { + mHandler.post(() -> show(cancelledUserAnimationTypes[0])); + } + } + + private void updateDisabledUserAnimationTypes(@InsetsType int disabledUserAnimationTypes) { + @InsetsType int diff = mLastDisabledUserAnimationInsetsTypes ^ disabledUserAnimationTypes; + if (diff != 0) { + for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { + InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); + if (consumer.getControl() != null && (toPublicType(consumer.mType) & diff) != 0) { + mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners); + mHandler.post(mInvokeControllableInsetsChangedListeners); + break; + } + } + mLastDisabledUserAnimationInsetsTypes = disabledUserAnimationTypes; + } } private boolean captionInsetsUnchanged() { @@ -896,6 +944,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean imeReady = true; for (int i = internalTypes.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); + if (animationType == ANIMATION_TYPE_USER) { + final InsetsSource source = mState.peekSource(consumer.getType()); + if (source != null && source.isVisibleFrameEmpty()) { + if (WARN) Log.w(TAG, String.format( + "collectSourceControls can't run user animation for type: %s", + InsetsState.typeToString(consumer.getType()))); + continue; + } + } boolean show = animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_USER; boolean canRun = false; @@ -1163,8 +1220,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. @@ -1279,11 +1336,17 @@ 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--) { InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); - if (consumer.getControl() != null) { + InsetsSource source = mState.peekSource(consumer.mType); + if (consumer.getControl() != null && source != null && !source.isVisibleFrameEmpty()) { result |= toPublicType(consumer.mType); } } @@ -1294,6 +1357,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * @return The types that are now animating due to a listener invoking control/show/hide */ private @InsetsType int invokeControllableInsetsChangedListeners() { + mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners); mLastStartedAnimTypes = 0; @InsetsType int types = calculateControllableTypes(); int size = mControllableInsetsChangedListeners.size(); diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 15b9a9330392..c8394d25ef47 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -92,6 +92,10 @@ public class InsetsSource implements Parcelable { return mVisible; } + public boolean isVisibleFrameEmpty() { + return mVisibleFrame != null && mVisibleFrame.isEmpty(); + } + /** * Calculates the insets this source will cause to a client window. * diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 3aa246441dbc..8d92d7b5ab20 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -275,9 +275,9 @@ public class InsetsSourceConsumer { } @VisibleForTesting(visibility = PACKAGE) - public void updateSource(InsetsSource newSource) { + public void updateSource(InsetsSource newSource, @AnimationType int animationType) { InsetsSource source = mState.peekSource(mType); - if (source == null || mController.getAnimationType(mType) == ANIMATION_TYPE_NONE + if (source == null || animationType == ANIMATION_TYPE_NONE || source.getFrame().equals(newSource.getFrame())) { mPendingFrame = null; mPendingVisibleFrame = null; @@ -286,7 +286,7 @@ public class InsetsSourceConsumer { } // Frame is changing while animating. Keep note of the new frame but keep existing frame - // until animaition is finished. + // until animation is finished. newSource = new InsetsSource(newSource); mPendingFrame = new Rect(newSource.getFrame()); mPendingVisibleFrame = newSource.getVisibleFrame() != null diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 397b04e9c023..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; } 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/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/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/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/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/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/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/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 8e1dc8950155..08d1182172f0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4008,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/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index bf7f339a8484..1b3272572db0 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -26,13 +26,11 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; import android.app.Instrumentation; import android.content.Context; @@ -135,37 +133,29 @@ public class InsetsSourceConsumerTest { InsetsSourceConsumer consumer = new InsetsSourceConsumer( ITYPE_IME, state, null, controller); - when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE); - InsetsSource source = new InsetsSource(ITYPE_IME); source.setFrame(0, 1, 2, 3); - consumer.updateSource(new InsetsSource(source)); - - when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_USER); + consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_NONE); // While we're animating, updates are delayed source.setFrame(4, 5, 6, 7); - consumer.updateSource(new InsetsSource(source)); + consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER); assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_IME).getFrame()); // Finish the animation, now the pending frame should be applied - when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE); assertTrue(consumer.notifyAnimationFinished()); assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame()); - when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_USER); - // Animating again, updates are delayed source.setFrame(8, 9, 10, 11); - consumer.updateSource(new InsetsSource(source)); + consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER); assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame()); // Updating with the current frame triggers a different code path, verify this clears // the pending 8, 9, 10, 11 frame: source.setFrame(4, 5, 6, 7); - consumer.updateSource(new InsetsSource(source)); + consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER); - when(controller.getAnimationType(anyInt())).thenReturn(ANIMATION_TYPE_NONE); assertFalse(consumer.notifyAnimationFinished()); assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame()); } 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/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index a18cfccb6cb2..5a7c87e0235d 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -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/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/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/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/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-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-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/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/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/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/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/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-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index fd475b9ee4de..bfa21071d815 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -522,8 +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> - <!-- no translation found for notification_section_header_incoming (850925217908095197) --> - <skip /> + <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> @@ -608,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> @@ -1023,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-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index aad8eec3e35e..7a9fecad5f9d 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 6cdc1d79b00d..49fae626fa5a 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 22a0e05e2ea5..f1bbbff3d7b6 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 171b6f61cff4..ac20c8477d47 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 5fb6236d6dcd..233664e5fbc7 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 171b6f61cff4..ac20c8477d47 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 171b6f61cff4..ac20c8477d47 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 8e244a2a37ae..cb6f7967d3e8 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -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> @@ -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> @@ -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> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 4ebfa797edf4..ae98244cf415 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index c4b8ae2ea421..473f6de00c89 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -510,8 +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> - <!-- no translation found for notification_section_header_incoming (850925217908095197) --> - <skip /> + <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> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 22f908011455..00389073626b 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 0dc7256f985d..64e7011a63a9 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index dd6c363abb4c..72fac719f1f1 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 21434bac9807..b90ae3e5834a 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -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,7 +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> - <string name="bubble_dismiss_text" msgid="1314082410868930066">"Ignora bolle"</string> + <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 24dae2745b8c..9d2d0cc973aa 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 0f248a1332b2..4f6c7858379c 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 9b8fa78213bc..74f43e870da7 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -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> 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 47353fd85e03..0fe47aab6e7c 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 0b629a329336..09e7ff17714f 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index b24518daa31b..e4d4ba407732 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -1003,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> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 9587e71e6136..d49f46d840b2 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -1003,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> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 8c06f6768590..eaf4157ff4e1 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 812477ad33d2..fda85ccb542c 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -596,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> @@ -1003,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> 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 4e4cc773d69f..87f072b9e7dd 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 13bc85ae689d..426bd2ec1ada 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -596,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> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index fe4dfb2fba54..ccafc49614bb 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -510,8 +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> - <!-- no translation found for notification_section_header_incoming (850925217908095197) --> - <skip /> + <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> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index b2aaa05724e1..cf63b1122da7 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -516,8 +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> - <!-- no translation found for notification_section_header_incoming (850925217908095197) --> - <skip /> + <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> @@ -605,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> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 24e7912be621..55b5af121f37 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 6b18dc41932f..0f0f598cebf4 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 916b2c7923e7..d2bda6e4e259 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 684e29645978..c776f2e7a3f0 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index ab6c93e94fc9..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> @@ -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> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 73eb647303ba..f7d19ac3472d 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -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> 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-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 585228d6411e..ba16febe18c3 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -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> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index b163c0c49dfe..e192c120922f 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -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> @@ -1018,7 +1018,7 @@ <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 8246048c51fa..d382f4237c47 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -510,8 +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> - <!-- no translation found for notification_section_header_incoming (850925217908095197) --> - <skip /> + <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> @@ -596,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> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index ab564864c737..cc6a8fd25826 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -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> 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-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 84611483e883..9847871c2efc 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -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> 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 44d3b02f0db7..9bcfdc43fbbf 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -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/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 34c85877577b..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; } @@ -1904,11 +1916,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean allowedOnBouncer = !(mFingerprintLockedOut && mBouncer && mCredentialAttempted); 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 isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT); // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. @@ -1917,7 +1924,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming)) && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser()) && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser - && allowedOnBouncer && !isLockDown && !isEncrypted; + && allowedOnBouncer && !isUserEncryptedOrLockdown(user); return shouldListen; } @@ -1931,11 +1938,6 @@ 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 isEncrypted = - containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT); final boolean isTimedOut = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); @@ -1958,7 +1960,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && !mSwitchingUser && !isFaceDisabled(user) && becauseCannotSkipBouncer && !mKeyguardGoingAway && mFaceSettingEnabledForUser.get(user) && !mLockIconPressed && strongAuthAllowsScanning && mIsPrimaryUser - && !mSecureCameraLaunched && !isLockDown && !isEncrypted; + && !mSecureCameraLaunched && !isUserEncryptedOrLockdown(user); // Aggregate relevant fields for debug logging. if (DEBUG_FACE || DEBUG_SPEW) { @@ -2031,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); @@ -2049,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/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 47e6645d347f..4acfbcea81ad 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -163,6 +163,11 @@ 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()) { options.setApplyActivityFlagsForBubbles(true); @@ -432,10 +437,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); @@ -741,11 +746,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/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..b644079be565 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -27,6 +27,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; @@ -100,7 +101,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 +141,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); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 015079ff900a..23bd174ac4a5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -49,6 +49,7 @@ 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.util.Log; import android.view.Choreographer; @@ -160,6 +161,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} @@ -293,6 +300,7 @@ public class BubbleStackView extends FrameLayout 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. */ @@ -470,6 +478,13 @@ public class BubbleStackView extends FrameLayout private OnClickListener mBubbleClickListener = new OnClickListener() { @Override public void onClick(View view) { + // 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. @@ -534,6 +549,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 { @@ -1098,13 +1115,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. @@ -1730,6 +1751,8 @@ public class BubbleStackView extends FrameLayout } private void animateExpansion() { + cancelDelayedExpandCollapseSwitchAnimations(); + mIsExpanded = true; hideStackUserEducation(true /* fromExpansion */); beforeExpandedViewAnimation(); @@ -1778,37 +1801,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) -> { - 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); - + 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(); @@ -1822,14 +1851,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(); @@ -1878,12 +1909,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(); @@ -1909,8 +1949,9 @@ public class BubbleStackView extends FrameLayout 0f, 0f, expandingFromBubbleDestinationX + mBubbleSize / 2f, getExpandedViewY()); mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); - mExpandedViewContainer.postDelayed(() -> { + mDelayedAnimationHandler.postDelayed(() -> { if (!mIsExpanded) { + mIsBubbleSwitchAnimating = false; return; } @@ -1928,11 +1969,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()); 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..cb8995a72dc3 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; @@ -501,12 +521,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 +558,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/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/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/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index bc95a2514c0b..915092134cc5 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -247,7 +247,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; @@ -405,12 +404,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, }); } - // 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 +437,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 +462,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) * 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 a2086e8d3ac6..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; } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 50081001e65a..2df0507a8864 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -96,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(); } @@ -257,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); @@ -282,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); } @@ -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 6ea36ab3af2f..88bdaece5971 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -67,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()) { @@ -88,12 +88,12 @@ 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, - private val broadcastDispatcher: BroadcastDispatcher + broadcastDispatcher: BroadcastDispatcher, + mediaTimeoutListener: MediaTimeoutListener, + mediaResumeListener: MediaResumeListener ) { private val listeners: MutableSet<Listener> = mutableSetOf() @@ -109,11 +109,28 @@ class MediaDataManager @Inject constructor( } } + 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, @@ -129,6 +146,17 @@ class MediaDataManager @Inject constructor( 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) { @@ -160,6 +188,18 @@ class MediaDataManager @Inject constructor( 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, @@ -174,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) } } @@ -214,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 { + notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */, + UNDEFINED_DISMISS_REASON) + } } } - private fun loadMediaDataInBg( + private fun loadMediaDataInBgForResumption( desc: MediaDescription, resumeAction: Runnable, token: MediaSession.Token, @@ -231,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 @@ -257,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)) } } @@ -389,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)) } } @@ -487,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) } @@ -501,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/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 3c3f4a977ee7..e0155a1a558f 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) { @@ -112,11 +117,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/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/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index d077666f8184..856c19290af6 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -491,10 +491,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); } /** 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/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/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 7babe2f4f2db..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; @@ -215,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; @@ -234,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; @@ -378,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); @@ -408,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(); }); @@ -450,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(); } @@ -670,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); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index e4e253e71fb3..fbcd6ba0ff47 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -148,11 +148,43 @@ public class ScreenshotNotificationsController { } /** - * Shows a silent notification with the saved screenshot and actions that can be taken with it. + * Shows a notification to inform the user that a screenshot is currently being saved. + */ + public void showSavingScreenshotNotification() { + final long now = System.currentTimeMillis(); + + mPublicNotificationBuilder + .setContentTitle(mResources.getString(R.string.screenshot_saving_title)) + .setSmallIcon(R.drawable.stat_notify_image) + .setCategory(Notification.CATEGORY_PROGRESS) + .setWhen(now) + .setShowWhen(true) + .setColor(mResources.getColor( + com.android.internal.R.color.system_notification_accent_color)); + SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true); + + mNotificationBuilder + .setContentTitle(mResources.getString(R.string.screenshot_saving_title)) + .setSmallIcon(R.drawable.stat_notify_image) + .setWhen(now) + .setShowWhen(true) + .setColor(mResources.getColor( + com.android.internal.R.color.system_notification_accent_color)) + .setStyle(mNotificationStyle) + .setPublicVersion(mPublicNotificationBuilder.build()); + mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true); + SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true); + + mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, + mNotificationBuilder.build()); + } + + /** + * Shows a 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( + public void showScreenshotActionsNotification( GlobalScreenshot.SavedImageData actionData) { mNotificationBuilder.addAction(actionData.shareAction); mNotificationBuilder.addAction(actionData.editAction); @@ -174,34 +206,20 @@ public class ScreenshotNotificationsController { .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); + com.android.internal.R.color.system_notification_accent_color)); 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); + .setFlag(Notification.FLAG_NO_CLEAR, false); mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, mNotificationBuilder.build()); diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index 166934599aeb..21810c0e7cf5 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -253,9 +253,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary); mRotateSplitLayout = null; } - if (isSplitActive()) { - update(newConfig); - } + update(newConfig); } Handler getHandler() { @@ -330,6 +328,11 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, mHandler.post(this::removeDivider); } + void onTasksReady() { + mHandler.post(() -> update(mDisplayController.getDisplayContext( + mContext.getDisplayId()).getResources().getConfiguration())); + } + private void updateVisibility(final boolean visible) { if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible); if (mVisible != visible) { @@ -521,7 +524,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, void ensureMinimizedSplit() { setHomeMinimized(true /* minimized */, mSplits.mSecondary.isResizable()); - if (mView != null && !isDividerVisible()) { + if (!isDividerVisible()) { // Wasn't in split-mode yet, so enter now. if (DEBUG) { Slog.d(TAG, " entering split mode with minimized=true"); @@ -532,7 +535,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, void ensureNormalSplit() { setHomeMinimized(false /* minimized */, mHomeStackResizable); - if (mView != null && !isDividerVisible()) { + if (!isDividerVisible()) { // Wasn't in split-mode, so enter now. if (DEBUG) { Slog.d(TAG, " enter split mode unminimized "); diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java index 44674df3b865..db324822994b 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java @@ -113,6 +113,8 @@ class SplitScreenTaskOrganizer extends TaskOrganizer { t.setColor(mSecondaryDim, new float[]{0f, 0f, 0f}); t.apply(); releaseTransaction(t); + + mDivider.onTasksReady(); } } } 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/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/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 11022bbcb7ea..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 = 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 c929243085d6..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,7 +28,7 @@ import android.graphics.PixelFormat; import android.graphics.RecordingCanvas; import android.graphics.drawable.Drawable; import android.os.Handler; -import android.util.Log; +import android.os.Trace; import android.view.RenderNodeAnimator; import android.view.View; import android.view.ViewConfiguration; @@ -74,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 @@ -221,7 +226,7 @@ public class KeyButtonRipple extends Drawable { @Override public void jumpToCurrentState() { - cancelAnimations("jumpToCurrentState"); + endAnimations("jumpToCurrentState", false /* cancel */); } @Override @@ -235,7 +240,6 @@ public class KeyButtonRipple extends Drawable { } public void setPressed(boolean pressed) { - Log.d("b/63783866", "KeyButtonRipple.setPressed: pressed=" + pressed); if (mDark != mLastDark && pressed) { mRipplePaint = null; mLastDark = mDark; @@ -255,14 +259,19 @@ public class KeyButtonRipple extends Drawable { mHandler.removeCallbacksAndMessages(null); } - private void cancelAnimations(String reason) { - Log.d("b/63783866", "KeyButtonRipple.cancelAnimations: reason=" + reason); + 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(); @@ -287,7 +296,7 @@ public class KeyButtonRipple extends Drawable { } private void enterSoftware() { - cancelAnimations("enterSoftware"); + endAnimations("enterSoftware", true /* cancel */); mVisible = true; mGlowAlpha = getMaxGlowAlpha(); ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale", @@ -373,8 +382,7 @@ public class KeyButtonRipple extends Drawable { } private void enterHardware() { - Log.d("b/63783866", "enterHardware"); - cancelAnimations("enterHardware"); + endAnimations("enterHardware", true /* cancel */); mVisible = true; mDrawingHardwareGlow = true; setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2)); @@ -391,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()) { @@ -426,13 +435,13 @@ public class KeyButtonRipple extends Drawable { } private void exitHardware() { - Log.d("b/63783866", "exitHardware"); mPaintProp = CanvasProperty.createPaint(getRipplePaint()); final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp, RenderNodeAnimator.PAINT_ALPHA, 0); opacityAnim.setDuration(ANIMATION_DURATION_FADE); opacityAnim.setInterpolator(Interpolators.ALPHA_OUT); opacityAnim.addListener(mAnimatorListener); + opacityAnim.addListener(mExitHwTraceAnimator); opacityAnim.setTarget(mTargetView); opacityAnim.start(); @@ -443,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/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/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt index 701ff5ecf8a1..eeca1e38abb0 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,7 @@ package com.android.systemui.util.animation import android.content.Context +import android.graphics.PointF import android.graphics.Rect import android.util.AttributeSet import android.view.View @@ -37,6 +38,7 @@ class TransitionLayout @JvmOverloads constructor( ) : ConstraintLayout(context, attrs, defStyleAttr) { 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 +69,7 @@ class TransitionLayout @JvmOverloads constructor( if (child.visibility == GONE) { originalGoneChildrenSet.add(child.id) } + originalViewAlphas[child.id] = child.alpha } } @@ -149,6 +152,11 @@ class TransitionLayout @JvmOverloads constructor( val layoutTop = top setLeftTopRightBottom(layoutLeft, layoutTop, layoutLeft + currentState.width, layoutTop + currentState.height) + val bounds = clipBounds ?: Rect() + bounds.set(left, top, right, bottom) + clipBounds = bounds + translationX = currentState.translation.x + translationY = currentState.translation.y } /** @@ -198,6 +206,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 +240,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 +264,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/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/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 1e48b990b19d..59f8c4e329a4 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; @@ -407,6 +408,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()); 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..58e06b5178c6 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; @@ -364,6 +365,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()); 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..3c1cc232f5c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -438,6 +438,9 @@ 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 @@ -455,6 +458,9 @@ 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 @@ -473,6 +479,9 @@ 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 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..082c8ba8744f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -93,7 +93,8 @@ 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 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/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 f14def6a3a02..fd6f171487a9 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 and TetherProvisioningActivity. - * Includes the type of tethering to enable if any. + * Extra used for communicating with the TetherService. Includes the type of tethering to + * enable if any. */ public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; /** @@ -56,38 +56,8 @@ public final class TetheringConstants { */ public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; /** - * Extra used for communicating with the TetherService and TetherProvisioningActivity. - * Contains the {@link ResultReceiver} which will receive provisioning results. - * Can not be empty. + * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} + * which will receive provisioning results. Can be left 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/src/com/android/networkstack/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java index 9dace709d734..3c6e8d88ed13 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java @@ -19,10 +19,6 @@ 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; @@ -73,6 +69,7 @@ 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; @@ -200,9 +197,9 @@ public class EntitlementManager { // till upstream change to cellular. if (mUsingCellularAsUpstream) { if (showProvisioningUi) { - runUiTetherProvisioning(downstreamType, config); + runUiTetherProvisioning(downstreamType, config.activeDataSubId); } else { - runSilentTetherProvisioning(downstreamType, config); + runSilentTetherProvisioning(downstreamType, config.activeDataSubId); } mNeedReRunProvisioningUi = false; } else { @@ -265,9 +262,9 @@ public class EntitlementManager { if (mCurrentEntitlementResults.indexOfKey(downstream) < 0) { if (mNeedReRunProvisioningUi) { mNeedReRunProvisioningUi = false; - runUiTetherProvisioning(downstream, config); + runUiTetherProvisioning(downstream, config.activeDataSubId); } else { - runSilentTetherProvisioning(downstream, config); + runSilentTetherProvisioning(downstream, config.activeDataSubId); } } } @@ -364,7 +361,7 @@ public class EntitlementManager { * @param subId default data subscription ID. */ @VisibleForTesting - protected Intent runSilentTetherProvisioning(int type, final TetheringConfiguration config) { + protected void runSilentTetherProvisioning(int type, int subId) { if (DBG) mLog.i("runSilentTetherProvisioning: " + type); // For silent provisioning, settings would stop tethering when entitlement fail. ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null); @@ -372,20 +369,17 @@ 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_TETHER_SUBID, config.activeDataSubId); + intent.putExtra(EXTRA_SUBID, subId); 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, final TetheringConfiguration config) { + private void runUiTetherProvisioning(int type, int subId) { ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null); - runUiTetherProvisioning(type, config, receiver); + runUiTetherProvisioning(type, subId, receiver); } /** @@ -395,20 +389,17 @@ public class EntitlementManager { * @param receiver to receive entitlement check result. */ @VisibleForTesting - protected Intent runUiTetherProvisioning(int type, final TetheringConfiguration config, - ResultReceiver receiver) { + protected void runUiTetherProvisioning(int type, int subId, 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_TETHER_SUBID, config.activeDataSubId); + intent.putExtra(EXTRA_SUBID, subId); 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. @@ -640,7 +631,7 @@ public class EntitlementManager { receiver.send(cacheValue, null); } else { ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver); - runUiTetherProvisioning(downstream, config, proxy); + runUiTetherProvisioning(downstream, config.activeDataSubId, proxy); } } } diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java index 1d45f129b51b..48a600dfe6e1 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java @@ -107,7 +107,6 @@ public class TetheringConfiguration { public final String[] provisioningApp; public final String provisioningAppNoUi; public final int provisioningCheckPeriod; - public final String provisioningResponse; public final int activeDataSubId; @@ -142,13 +141,10 @@ public class TetheringConfiguration { enableLegacyDhcpServer = getEnableLegacyDhcpServer(res); provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app); - provisioningAppNoUi = getResourceString(res, - R.string.config_mobile_hotspot_provision_app_no_ui); + provisioningAppNoUi = getProvisioningAppNoUi(res); 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, @@ -341,9 +337,9 @@ public class TetheringConfiguration { return copy(LEGACY_DHCP_DEFAULT_RANGE); } - private static String getResourceString(Resources res, final int resId) { + private static String getProvisioningAppNoUi(Resources res) { try { - return res.getString(resId); + return res.getString(R.string.config_mobile_hotspot_provision_app_no_ui); } catch (Resources.NotFoundException e) { return ""; } 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 354e75356e9f..72fa916b9e42 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,16 +16,8 @@ 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; @@ -52,7 +44,6 @@ 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; @@ -62,7 +53,6 @@ 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; @@ -86,7 +76,6 @@ 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; @@ -133,51 +122,15 @@ public final class EntitlementManagerTest { } @Override - protected Intent runUiTetherProvisioning(int type, - final TetheringConfiguration config, final ResultReceiver receiver) { - Intent intent = super.runUiTetherProvisioning(type, config, receiver); - assertUiTetherProvisioningIntent(type, config, receiver, intent); + protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { 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 Intent runSilentTetherProvisioning(int type, - final TetheringConfiguration config) { - Intent intent = super.runSilentTetherProvisioning(type, config); - assertSilentTetherProvisioning(type, config, intent); + protected void runSilentTetherProvisioning(int type, int subId) { 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)); } } @@ -234,8 +187,6 @@ 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 312186391d5f..1999ad786ed4 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,8 +61,6 @@ 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; @@ -390,8 +388,6 @@ 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() { @@ -407,10 +403,6 @@ 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/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/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java index 0ec8654f2a20..398ece4c1836 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java @@ -77,8 +77,8 @@ 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, @@ -87,6 +87,22 @@ final class AutofillInlineSessionController { } /** + * 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/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index a9a0ab69f633..c7490b3b991c 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -3702,6 +3702,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/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/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index a3c164d63605..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; 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/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/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/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index d025b0f4ece5..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. @@ -1496,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/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 9a2aee3d8df6..93a27f2d17a9 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -598,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)); @@ -613,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)); @@ -1954,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; @@ -1967,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/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/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/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 4b6ee71803a7..5b9db64dd9b1 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -32,7 +32,6 @@ 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; @@ -239,20 +238,13 @@ public class AppsFilter { } 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()); } } 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/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/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/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 01eb9c5cb3d9..5668454b7bb6 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2198,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() { @@ -2370,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; } /** @@ -3355,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; @@ -7717,24 +7727,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/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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4e19a5224bb4..d55883968b56 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2175,6 +2175,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 +3526,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } boolean canShowIme() { - if (isUntrustedVirtualDisplay()) { + if (!isTrusted()) { return false; } return mWmService.mDisplayWindowSettings.shouldShowImeLocked(this) @@ -4753,15 +4757,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 +5643,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 @@ -5671,7 +5671,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..3c4a9ad08199 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; } @@ -1900,7 +1915,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 +2389,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 +3205,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/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/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/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/Task.java b/services/core/java/com/android/server/wm/Task.java index 56ad9643e619..c749125ec531 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2638,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 */ @@ -2661,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, @@ -2676,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; } @@ -2917,7 +2924,12 @@ class Task extends WindowContainer<WindowContainer> { } boolean cropWindowsToStackBounds() { - return isResizeable(); + // 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. + final boolean isTopHomeOrRecents = (isActivityTypeHome() || isActivityTypeRecents()) + && getRootTask().getTopMostTask() == this; + return isResizeable() && !isTopHomeOrRecents; } /** 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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0590288a7f8b..10d07573f8c6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7150,9 +7150,6 @@ public class WindowManagerService extends IWindowManager.Stub + "not exist: %d", displayId); return false; } - if (displayContent.isUntrustedVirtualDisplay()) { - return false; - } return displayContent.supportsSystemDecorations(); } } @@ -7171,7 +7168,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 +7216,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 32717d0e1e65..3532edf302c4 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); @@ -2793,7 +2830,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 @@ -3576,6 +3613,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 { @@ -5386,6 +5426,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/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/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/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/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/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 ef28a450695b..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,6 +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.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; @@ -452,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/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 4ad7dff87072..5a952b3e238f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1144,7 +1144,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. @@ -1280,7 +1287,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 +1310,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..15b395c8814e 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; @@ -288,14 +289,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 +507,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 +522,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/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index e9ed20bd9683..b51784d8d6e5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -692,4 +692,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/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/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/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(); |