diff options
194 files changed, 3965 insertions, 1206 deletions
diff --git a/Android.bp b/Android.bp index d4ca7066781a..03a6af5997bb 100644 --- a/Android.bp +++ b/Android.bp @@ -339,9 +339,7 @@ java_defaults { "sax/java", "telecomm/java", - // TODO(b/148660295): remove this - "apex/media/framework/java", - + "apex/media/aidl/stable", // TODO(b/147699819): remove this "telephony/java", ], diff --git a/StubLibraries.bp b/StubLibraries.bp index 60f6174740df..f06f2792e0d7 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -256,18 +256,16 @@ droidstubs { ///////////////////////////////////////////////////////////////////// java_defaults { - name: "framework-stubs-default", + name: "android_defaults_stubs_current", libs: [ "stub-annotations" ], static_libs: [ "private-stub-annotations-jar" ], - sdk_version: "core_current", errorprone: { javacflags: [ "-XepDisableAllChecks", ], }, - java_resources: [ - ":notices-for-framework-stubs", - ], + java_resources: [":notices-for-framework-stubs"], + sdk_version: "none", system_modules: "none", java_version: "1.8", compile_dex: true, @@ -276,25 +274,25 @@ java_defaults { java_library_static { name: "android_stubs_current", srcs: [ ":api-stubs-docs" ], - defaults: ["framework-stubs-default"], + defaults: ["android_defaults_stubs_current"], } java_library_static { name: "android_system_stubs_current", srcs: [ ":system-api-stubs-docs" ], - defaults: ["framework-stubs-default"], + defaults: ["android_defaults_stubs_current"], } java_library_static { name: "android_test_stubs_current", srcs: [ ":test-api-stubs-docs" ], - defaults: ["framework-stubs-default"], + defaults: ["android_defaults_stubs_current"], } java_library_static { name: "android_module_lib_stubs_current", srcs: [ ":module-lib-api-stubs-docs" ], - defaults: ["framework-stubs-default"], + defaults: ["android_defaults_stubs_current"], libs: ["android_system_stubs_current"], } diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java index 73b4a1914ad1..836e6b617395 100644 --- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java @@ -192,6 +192,11 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { Assume.assumeNoException( new AssertionError("onAnimationCanceled should not be called")); } + + @Override + public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException { + /* no-op */ + } }; recentsSemaphore.tryAcquire(); diff --git a/apex/Android.bp b/apex/Android.bp index 5f418d47f090..67cd0d7fcd1e 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -67,7 +67,7 @@ stubs_defaults { name: "framework-module-stubs-defaults-publicapi", args: mainline_framework_stubs_args, installable: false, - sdk_version: "current", + sdk_version: "module_current", filter_packages: framework_packages_to_document, check_api: { current: { @@ -86,7 +86,7 @@ stubs_defaults { args: mainline_framework_stubs_args + priv_apps, libs: ["framework-annotations-lib"], installable: false, - sdk_version: "system_current", + sdk_version: "module_current", filter_packages: framework_packages_to_document, check_api: { current: { diff --git a/apex/blobstore/framework/java/android/app/blob/XmlTags.java b/apex/blobstore/framework/java/android/app/blob/XmlTags.java index e64edc393769..dbfdcba05a73 100644 --- a/apex/blobstore/framework/java/android/app/blob/XmlTags.java +++ b/apex/blobstore/framework/java/android/app/blob/XmlTags.java @@ -48,6 +48,7 @@ public final class XmlTags { // For committer public static final String TAG_COMMITTER = "c"; + public static final String ATTR_COMMIT_TIME_MS = "cmt"; // For leasee public static final String TAG_LEASEE = "l"; 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 c8ca44b6ef74..49b3ec1d113b 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -15,6 +15,7 @@ */ package com.android.server.blob; +import static android.app.blob.XmlTags.ATTR_COMMIT_TIME_MS; import static android.app.blob.XmlTags.ATTR_DESCRIPTION; import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME; import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME; @@ -30,6 +31,7 @@ import static android.os.Process.INVALID_UID; import static android.system.OsConstants.O_RDONLY; import static com.android.server.blob.BlobStoreConfig.TAG; +import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC; import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed; @@ -54,6 +56,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import com.android.server.blob.BlobStoreManagerService.DumpArgs; @@ -125,7 +128,7 @@ class BlobMetadata { } } - void addCommitters(ArraySet<Committer> committers) { + void setCommitters(ArraySet<Committer> committers) { synchronized (mMetadataLock) { mCommitters.clear(); mCommitters.addAll(committers); @@ -153,11 +156,16 @@ class BlobMetadata { } @Nullable - Committer getExistingCommitter(@NonNull Committer newCommitter) { + Committer getExistingCommitter(@NonNull String packageName, int uid) { synchronized (mCommitters) { - final int index = mCommitters.indexOf(newCommitter); - return index >= 0 ? mCommitters.valueAt(index) : null; + for (int i = 0, size = mCommitters.size(); i < size; ++i) { + final Committer committer = mCommitters.valueAt(i); + if (committer.uid == uid && committer.packageName.equals(packageName)) { + return committer; + } + } } + return null; } void addOrReplaceLeasee(String callingPackage, int callingUid, int descriptionResId, @@ -172,7 +180,7 @@ class BlobMetadata { } } - void addLeasees(ArraySet<Leasee> leasees) { + void setLeasees(ArraySet<Leasee> leasees) { synchronized (mMetadataLock) { mLeasees.clear(); mLeasees.addAll(leasees); @@ -380,8 +388,7 @@ class BlobMetadata { } // Blobs with no active leases - // TODO: Track commit time instead of using last modified time. - if ((!respectLeaseWaitTime || hasLeaseWaitTimeElapsed(getBlobFile().lastModified())) + if ((!respectLeaseWaitTime || hasLeaseWaitTimeElapsedForAll()) && !hasLeases()) { return true; } @@ -389,6 +396,17 @@ class BlobMetadata { return false; } + @VisibleForTesting + boolean hasLeaseWaitTimeElapsedForAll() { + for (int i = 0, size = mCommitters.size(); i < size; ++i) { + final Committer committer = mCommitters.valueAt(i); + if (!hasLeaseWaitTimeElapsed(committer.getCommitTimeMs())) { + return false; + } + } + return true; + } + void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { fout.println("blobHandle:"); fout.increaseIndent(); @@ -492,20 +510,28 @@ class BlobMetadata { } final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle, userId); - blobMetadata.addCommitters(committers); - blobMetadata.addLeasees(leasees); + blobMetadata.setCommitters(committers); + blobMetadata.setLeasees(leasees); return blobMetadata; } static final class Committer extends Accessor { public final BlobAccessMode blobAccessMode; + public final long commitTimeMs; - Committer(String packageName, int uid, BlobAccessMode blobAccessMode) { + Committer(String packageName, int uid, BlobAccessMode blobAccessMode, long commitTimeMs) { super(packageName, uid); this.blobAccessMode = blobAccessMode; + this.commitTimeMs = commitTimeMs; + } + + long getCommitTimeMs() { + return commitTimeMs; } void dump(IndentingPrintWriter fout) { + fout.println("commit time: " + + (commitTimeMs == 0 ? "<null>" : BlobStoreUtils.formatTime(commitTimeMs))); fout.println("accessMode:"); fout.increaseIndent(); blobAccessMode.dump(fout); @@ -515,6 +541,7 @@ class BlobMetadata { void writeToXml(@NonNull XmlSerializer out) throws IOException { XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); XmlUtils.writeIntAttribute(out, ATTR_UID, uid); + XmlUtils.writeLongAttribute(out, ATTR_COMMIT_TIME_MS, commitTimeMs); out.startTag(null, TAG_ACCESS_MODE); blobAccessMode.writeToXml(out); @@ -526,6 +553,9 @@ class BlobMetadata { throws XmlPullParserException, IOException { final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); + final long commitTimeMs = version >= XML_VERSION_ADD_COMMIT_TIME + ? XmlUtils.readLongAttribute(in, ATTR_COMMIT_TIME_MS) + : 0; final int depth = in.getDepth(); BlobAccessMode blobAccessMode = null; @@ -538,7 +568,7 @@ class BlobMetadata { Slog.wtf(TAG, "blobAccessMode should be available"); return null; } - return new Committer(packageName, uid, blobAccessMode); + return new Committer(packageName, uid, blobAccessMode, commitTimeMs); } } 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 f2c158658562..6af1178b55f1 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -45,8 +45,9 @@ class BlobStoreConfig { // Added a string variant of lease description. public static final int XML_VERSION_ADD_STRING_DESC = 2; public static final int XML_VERSION_ADD_DESC_RES_NAME = 3; + public static final int XML_VERSION_ADD_COMMIT_TIME = 4; - public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_DESC_RES_NAME; + public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_COMMIT_TIME; private static final String ROOT_DIR_NAME = "blobstore"; private static final String BLOBS_DIR_NAME = "blobs"; @@ -100,6 +101,18 @@ class BlobStoreConfig { public static long LEASE_ACQUISITION_WAIT_DURATION_MS = DEFAULT_LEASE_ACQUISITION_WAIT_DURATION_MS; + /** + * Denotes the duration from the time a blob is committed that any new commits of the same + * data blob from the same committer will be treated as if they occurred at the earlier + * commit time. + */ + public static final String KEY_COMMIT_COOL_OFF_DURATION_MS = + "commit_cool_off_duration_ms"; + public static final long DEFAULT_COMMIT_COOL_OFF_DURATION_MS = + TimeUnit.HOURS.toMillis(48); + public static long COMMIT_COOL_OFF_DURATION_MS = + DEFAULT_COMMIT_COOL_OFF_DURATION_MS; + static void refresh(Properties properties) { if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) { return; @@ -163,6 +176,27 @@ class BlobStoreConfig { < System.currentTimeMillis(); } + /** + * Returns an adjusted commit time depending on whether commit cool-off period has elapsed. + * + * If this is the initial commit or the earlier commit cool-off period has elapsed, then + * the new commit time is used. Otherwise, the earlier commit time is used. + */ + public static long getAdjustedCommitTimeMs(long oldCommitTimeMs, long newCommitTimeMs) { + if (oldCommitTimeMs == 0 || hasCommitCoolOffPeriodElapsed(oldCommitTimeMs)) { + return newCommitTimeMs; + } + return oldCommitTimeMs; + } + + /** + * Returns whether the commit cool-off period has elapsed. + */ + private static boolean hasCommitCoolOffPeriodElapsed(long commitTimeMs) { + return commitTimeMs + DeviceConfigProperties.COMMIT_COOL_OFF_DURATION_MS + < System.currentTimeMillis(); + } + @Nullable public static File prepareBlobFile(long sessionId) { final File blobsDir = prepareBlobsDir(); 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 e472d052f32f..35a2436702da 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -31,6 +31,7 @@ import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT; +import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs; import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED; import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID; @@ -566,13 +567,18 @@ public class BlobStoreManagerService extends SystemService { userId); BlobMetadata blob = userBlobs.get(session.getBlobHandle()); if (blob == null) { - blob = new BlobMetadata(mContext, - session.getSessionId(), session.getBlobHandle(), userId); + blob = new BlobMetadata(mContext, session.getSessionId(), + session.getBlobHandle(), userId); addBlobForUserLocked(blob, userBlobs); } + final Committer existingCommitter = blob.getExistingCommitter( + session.getOwnerPackageName(), session.getOwnerUid()); + final long existingCommitTimeMs = + (existingCommitter == null) ? 0 : existingCommitter.getCommitTimeMs(); final Committer newCommitter = new Committer(session.getOwnerPackageName(), - session.getOwnerUid(), session.getBlobAccessMode()); - final Committer existingCommitter = blob.getExistingCommitter(newCommitter); + session.getOwnerUid(), session.getBlobAccessMode(), + getAdjustedCommitTimeMs(existingCommitTimeMs, + System.currentTimeMillis())); blob.addOrReplaceCommitter(newCommitter); try { writeBlobsInfoLocked(); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java index fabce766c237..1d07e88773c3 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.text.format.TimeMigrationUtils; import android.util.Slog; class BlobStoreUtils { @@ -56,4 +57,9 @@ class BlobStoreUtils { ? Resources.ID_NULL : getDescriptionResourceId(resources, resourceEntryName, packageName); } + + @NonNull + static String formatTime(long timeMs) { + return TimeMigrationUtils.formatMillisWithFixedFormat(timeMs); + } } diff --git a/apex/media/aidl/Android.bp b/apex/media/aidl/Android.bp new file mode 100644 index 000000000000..409a04897f56 --- /dev/null +++ b/apex/media/aidl/Android.bp @@ -0,0 +1,35 @@ +// +// 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. +// + +filegroup { + name: "stable-mediasession2-aidl-srcs", + srcs: ["stable/**/*.aidl"], + path: "stable", +} + +filegroup { + name: "private-mediasession2-aidl-srcs", + srcs: ["private/**/I*.aidl"], + path: "private", +} + +filegroup { + name: "mediasession2-aidl-srcs", + srcs: [ + ":private-mediasession2-aidl-srcs", + ":stable-mediasession2-aidl-srcs", + ], +} diff --git a/apex/media/framework/java/android/media/Controller2Link.aidl b/apex/media/aidl/private/android/media/Controller2Link.aidl index 64edafcb11fc..64edafcb11fc 100644 --- a/apex/media/framework/java/android/media/Controller2Link.aidl +++ b/apex/media/aidl/private/android/media/Controller2Link.aidl diff --git a/apex/media/framework/java/android/media/IMediaController2.aidl b/apex/media/aidl/private/android/media/IMediaController2.aidl index 42c6e70529ec..42c6e70529ec 100644 --- a/apex/media/framework/java/android/media/IMediaController2.aidl +++ b/apex/media/aidl/private/android/media/IMediaController2.aidl diff --git a/apex/media/framework/java/android/media/IMediaSession2.aidl b/apex/media/aidl/private/android/media/IMediaSession2.aidl index 26e717b39afc..26e717b39afc 100644 --- a/apex/media/framework/java/android/media/IMediaSession2.aidl +++ b/apex/media/aidl/private/android/media/IMediaSession2.aidl diff --git a/apex/media/framework/java/android/media/IMediaSession2Service.aidl b/apex/media/aidl/private/android/media/IMediaSession2Service.aidl index 10ac1be0a36e..10ac1be0a36e 100644 --- a/apex/media/framework/java/android/media/IMediaSession2Service.aidl +++ b/apex/media/aidl/private/android/media/IMediaSession2Service.aidl diff --git a/apex/media/framework/java/android/media/Session2Command.aidl b/apex/media/aidl/private/android/media/Session2Command.aidl index 43a7b123ed29..43a7b123ed29 100644 --- a/apex/media/framework/java/android/media/Session2Command.aidl +++ b/apex/media/aidl/private/android/media/Session2Command.aidl diff --git a/apex/media/framework/java/android/media/Session2Token.aidl b/apex/media/aidl/stable/android/media/Session2Token.aidl index c5980e9e77fd..c5980e9e77fd 100644 --- a/apex/media/framework/java/android/media/Session2Token.aidl +++ b/apex/media/aidl/stable/android/media/Session2Token.aidl diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 579963b71cf7..34fe22879ca9 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -55,17 +55,15 @@ filegroup { name: "updatable-media-srcs", srcs: [ ":mediaparser-srcs", - ":mediasession2-srcs", + ":mediasession2-java-srcs", + ":mediasession2-aidl-srcs", ], } filegroup { - name: "mediasession2-srcs", + name: "mediasession2-java-srcs", srcs: [ "java/android/media/Controller2Link.java", - "java/android/media/IMediaController2.aidl", - "java/android/media/IMediaSession2.aidl", - "java/android/media/IMediaSession2Service.aidl", "java/android/media/MediaConstants.java", "java/android/media/MediaController2.java", "java/android/media/MediaSession2.java", @@ -83,7 +81,7 @@ filegroup { srcs: [ "java/android/media/MediaParser.java" ], - path: "java" + path: "java", } stubs_defaults { diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl index d5b5949cd032..80308d26a430 100644 --- a/apex/statsd/aidl/android/os/IStatsd.aidl +++ b/apex/statsd/aidl/android/os/IStatsd.aidl @@ -31,6 +31,11 @@ interface IStatsd { oneway void systemRunning(); /** + * Tell the stats daemon that the android system has finished booting. + */ + oneway void bootCompleted(); + + /** * Tell the stats daemon that the StatsCompanionService is up and running. * Two-way binder call so that caller knows message received. */ @@ -182,10 +187,15 @@ interface IStatsd { */ void sendAppBreadcrumbAtom(int label, int state); - /** - * Registers a puller callback function that, when invoked, pulls the data - * for the specified atom tag. - */ + /** + * Tell the stats daemon that all the pullers registered during boot have been sent. + */ + oneway void allPullersFromBootRegistered(); + + /** + * Registers a puller callback function that, when invoked, pulls the data + * for the specified atom tag. + */ oneway void registerPullAtomCallback(int uid, int atomTag, long coolDownMillis, long timeoutMillis,in int[] additiveFields, IPullAtomCallback pullerCallback); diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java index c1ba73f03c06..dc477a5590ea 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java @@ -87,6 +87,9 @@ public class StatsCompanion { if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { mStatsCompanionService.systemReady(); } + if (phase == PHASE_BOOT_COMPLETED) { + mStatsCompanionService.bootCompleted(); + } } } diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index 66e41cca96a7..ce5309e4df52 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -112,6 +112,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private final HashMap<Long, String> mDeletedFiles = new HashMap<>(); private final CompanionHandler mHandler; + // Flag that is set when PHASE_BOOT_COMPLETED is triggered in the StatsCompanion lifecycle. This + // and the flag mSentBootComplete below is used for synchronization to ensure that the boot + // complete signal is only ever sent once to statsd. Two signals are needed because + // #sayHiToStatsd can be called from both statsd and #onBootPhase + // PHASE_THIRD_PARTY_APPS_CAN_START. + @GuardedBy("sStatsdLock") + private boolean mBootCompleted = false; + // Flag that is set when IStatsd#bootCompleted is called. This flag ensures that boot complete + // signal is only ever sent once. + @GuardedBy("sStatsdLock") + private boolean mSentBootComplete = false; + public StatsCompanionService(Context context) { super(); mContext = context; @@ -688,6 +700,19 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { List.of(appUpdateReceiver, userUpdateReceiver, shutdownEventReceiver)); final long token = Binder.clearCallingIdentity(); + + // Used so we can call statsd.bootComplete() outside of the lock. + boolean shouldSendBootComplete = false; + synchronized (sStatsdLock) { + if (mBootCompleted && !mSentBootComplete) { + mSentBootComplete = true; + shouldSendBootComplete = true; + } + } + if (shouldSendBootComplete) { + statsd.bootCompleted(); + } + try { // Pull the latest state of UID->app name, version mapping when // statsd starts. @@ -749,6 +774,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { mContext.unregisterReceiver(receiver); } statsdNotReadyLocked(); + mSentBootComplete = false; } } } @@ -758,6 +784,28 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { mStatsManagerService.statsdNotReady(); } + void bootCompleted() { + IStatsd statsd = getStatsdNonblocking(); + synchronized (sStatsdLock) { + mBootCompleted = true; + if (mSentBootComplete) { + // do not send a boot complete a second time. + return; + } + if (statsd == null) { + // Statsd is not yet ready. + // Delay the boot completed ping to {@link #sayHiToStatsd()} + return; + } + mSentBootComplete = true; + } + try { + statsd.bootCompleted(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify statsd that boot completed"); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java index 58c78da5cea7..90764b0bd426 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java @@ -600,6 +600,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownMillis(), value.getTimeoutMillis(), value.getAdditiveFields(), value.getCallback()); } + statsd.allPullersFromBootRegistered(); } // Pre-condition: the Binder calling identity has already been cleared diff --git a/cmds/idmap2/TEST_MAPPING b/cmds/idmap2/TEST_MAPPING index 26ccf038cba2..9e0fb84c7949 100644 --- a/cmds/idmap2/TEST_MAPPING +++ b/cmds/idmap2/TEST_MAPPING @@ -3,5 +3,10 @@ { "name" : "idmap2_tests" } + ], + "imports": [ + { + "path": "frameworks/base/services/core/java/com/android/server/om" + } ] } diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index e55ea6c00545..75fc7f714ce3 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -149,15 +149,21 @@ Status Idmap2Service::createIdmap(const std::string& target_apk_path, return error(idmap.GetErrorMessage()); } + // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees + // that existing memory maps will continue to be valid and unaffected. + unlink(idmap_path.c_str()); + umask(kIdmapFilePermissionMask); std::ofstream fout(idmap_path); if (fout.fail()) { return error("failed to open idmap path " + idmap_path); } + BinaryStreamVisitor visitor(fout); (*idmap)->accept(&visitor); fout.close(); if (fout.fail()) { + unlink(idmap_path.c_str()); return error("failed to write to idmap path " + idmap_path); } diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 9169eb1778d9..dd1d40083a6b 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -1054,6 +1054,14 @@ Status StatsService::statsCompanionReady() { return Status::ok(); } +Status StatsService::bootCompleted() { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::bootCompleted was called"); + + return Status::ok(); +} + void StatsService::Startup() { mConfigManager->Startup(); mProcessor->LoadActiveConfigsFromDisk(); @@ -1215,6 +1223,14 @@ Status StatsService::sendAppBreadcrumbAtom(int32_t label, int32_t state) { return Status::ok(); } +Status StatsService::allPullersFromBootRegistered() { + ENFORCE_UID(AID_SYSTEM); + + VLOG("StatsService::allPullersFromBootRegistered was called"); + + return Status::ok(); +} + Status StatsService::registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis, const std::vector<int32_t>& additiveFields, diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 114c84f953c8..23d4c1bd199d 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -64,6 +64,7 @@ public: virtual Status systemRunning(); virtual Status statsCompanionReady(); + virtual Status bootCompleted(); virtual Status informAnomalyAlarmFired(); virtual Status informPollAlarmFired(); virtual Status informAlarmForSubscriberTriggeringFired(); @@ -165,6 +166,11 @@ public: virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override; /** + * Binder call to notify statsd that all pullers from boot have been registered. + */ + virtual Status allPullersFromBootRegistered(); + + /** * Binder call to register a callback function for a pulled atom. */ virtual Status registerPullAtomCallback( diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 38b421f70477..453ddeb332af 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -384,12 +384,12 @@ message Atom { PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"]; VmsClientConnectionStateChanged vms_client_connection_state_changed = 230 [(module) = "car"]; - MediaProviderScanEvent media_provider_scan_event = 233 [(module) = "mediaprovider"]; - MediaProviderDeletionEvent media_provider_deletion_event = 234 [(module) = "mediaprovider"]; - MediaProviderPermissionEvent media_provider_permission_event = + MediaProviderScanOccurred media_provider_scan_occurred = 233 [(module) = "mediaprovider"]; + MediaContentDeleted media_content_deleted = 234 [(module) = "mediaprovider"]; + MediaProviderPermissionRequested media_provider_permission_requested = 235 [(module) = "mediaprovider"]; - MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"]; - MediaProviderIdleMaintenance media_provider_idle_maintenance = + MediaProviderSchemaChanged media_provider_schema_changed = 236 [(module) = "mediaprovider"]; + MediaProviderIdleMaintenanceFinished media_provider_idle_maintenance_finished = 237 [(module) = "mediaprovider"]; RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238 [(module) = "framework"]; BootTimeEventDuration boot_time_event_duration_reported = 239 [(module) = "framework"]; @@ -4456,7 +4456,7 @@ message VmsClientConnectionStateChanged { * Logged from: * packages/providers/MediaProvider/src/com/android/providers/media/scan/ModernMediaScanner.java */ -message MediaProviderScanEvent { +message MediaProviderScanOccurred { enum Reason { // Scan triggered due to unknown reason UNKNOWN = 0; @@ -4490,15 +4490,13 @@ message MediaProviderScanEvent { * Logged from: * packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java */ -message MediaProviderDeletionEvent { +message MediaContentDeleted { // Volume type that this event pertains to optional android.stats.mediaprovider.VolumeType volume_type = 1; - // Device timestamp when this deletion event occurred - optional int64 timestamp_millis = 2; - // App that requested deletion - optional string package_name = 3; + // UID of app that requested deletion + optional int32 uid = 2 [(is_uid) = true]; // Number of items that were deleted - optional int32 item_count = 4; + optional int32 item_count = 3; } /** @@ -4507,7 +4505,7 @@ message MediaProviderDeletionEvent { * Logged from: * packages/providers/MediaProvider/src/com/android/providers/media/PermissionActivity.java */ -message MediaProviderPermissionEvent { +message MediaProviderPermissionRequested { enum Result { UNKNOWN = 0; USER_GRANTED = 1; @@ -4519,14 +4517,12 @@ message MediaProviderPermissionEvent { // Volume type that this event pertains to optional android.stats.mediaprovider.VolumeType volume_type = 1; - // Device timestamp when this permission event occurred - optional int64 timestamp_millis = 2; - // App that requested permission - optional string package_name = 3; + // UID of app that requested permission + optional int32 uid = 2 [(is_uid) = true]; // Number of items that were requested - optional int32 item_count = 4; + optional int32 item_count = 3; // Result of this request - optional Result result = 5; + optional Result result = 4; } /** @@ -4535,7 +4531,7 @@ message MediaProviderPermissionEvent { * Logged from: * packages/providers/MediaProvider/src/com/android/providers/media/DatabaseHelper.java */ -message MediaProviderSchemaChange { +message MediaProviderSchemaChanged { // Volume type that this event pertains to optional android.stats.mediaprovider.VolumeType volume_type = 1; // Old database version code @@ -4554,7 +4550,7 @@ message MediaProviderSchemaChange { * Logged from: * packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java */ -message MediaProviderIdleMaintenance { +message MediaProviderIdleMaintenanceFinished { // Volume type that this event pertains to optional android.stats.mediaprovider.VolumeType volume_type = 1; diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java index d5da0b42402c..54a744b654cb 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -64,7 +64,8 @@ public class TestDrive { "AID_LMKD", "com.android.managedprovisioning", "AID_MEDIA", - "AID_NETWORK_STACK" + "AID_NETWORK_STACK", + "com.google.android.providers.media.module", }; private static final String[] DEFAULT_PULL_SOURCES = { "AID_SYSTEM", diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index d00366bd38f4..47ccc2f0badb 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -344,7 +344,7 @@ public class ResourcesManager { ApkAssets apkAssets = null; if (mLoadedApkAssets != null) { apkAssets = mLoadedApkAssets.get(newKey); - if (apkAssets != null) { + if (apkAssets != null && apkAssets.isUpToDate()) { return apkAssets; } } @@ -353,7 +353,7 @@ public class ResourcesManager { final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey); if (apkAssetsRef != null) { apkAssets = apkAssetsRef.get(); - if (apkAssets != null) { + if (apkAssets != null && apkAssets.isUpToDate()) { if (mLoadedApkAssets != null) { mLoadedApkAssets.put(newKey, apkAssets); } @@ -1121,7 +1121,9 @@ public class ResourcesManager { daj = new DisplayAdjustments(daj); daj.setCompatibilityInfo(compat); } - daj.setConfiguration(config); + if (displayId == Display.DEFAULT_DISPLAY) { + daj.setConfiguration(config); + } DisplayMetrics dm = getDisplayMetrics(displayId, daj); if (displayId != Display.DEFAULT_DISPLAY) { applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index c94d428f4475..6bd8b1d9d7c0 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -21,7 +21,6 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; -import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; import static android.os.Build.VERSION_CODES.DONUT; @@ -253,10 +252,8 @@ public class ParsingPackageUtils { final File baseApk = new File(lite.baseCodePath); ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk, lite.codePath, assets, flags); - // TODO(b/135203078): Pass original error up? if (result.isError()) { - return input.error(INSTALL_PARSE_FAILED_NOT_APK, - "Failed to parse base APK: " + baseApk); + return input.error(result); } ParsingPackage pkg = result.getResult(); diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java index 2041cfb22ea8..c87b8279c4d6 100644 --- a/core/java/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -75,7 +75,7 @@ public class VpnManager { } /** - * Create an instance of the VpnManger with the given context. + * Create an instance of the VpnManager with the given context. * * <p>Internal only. Applications are expected to obtain an instance of the VpnManager via the * {@link Context.getSystemService()} method call. diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java index c047dc0d07c7..60373ace2d8a 100644 --- a/core/java/android/service/dataloader/DataLoaderService.java +++ b/core/java/android/service/dataloader/DataLoaderService.java @@ -34,6 +34,8 @@ import android.os.ParcelFileDescriptor; import android.util.ExceptionUtils; import android.util.Slog; +import libcore.io.IoUtils; + import java.io.IOException; import java.util.Collection; @@ -115,22 +117,10 @@ public abstract class DataLoaderService extends Service { destroy(id); throw new RuntimeException(ex); } finally { - // Closing FDs. if (control.incremental != null) { - if (control.incremental.cmd != null) { - try { - control.incremental.cmd.close(); - } catch (IOException e) { - Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e); - } - } - if (control.incremental.log != null) { - try { - control.incremental.log.close(); - } catch (IOException e) { - Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e); - } - } + IoUtils.closeQuietly(control.incremental.cmd); + IoUtils.closeQuietly(control.incremental.pendingReads); + IoUtils.closeQuietly(control.incremental.log); } } } diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl index a60a5cca08bd..983ab2eedf5c 100644 --- a/core/java/android/view/IRecentsAnimationController.aidl +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -114,4 +114,16 @@ interface IRecentsAnimationController { * animation is cancelled through fail safe mechanism. */ void setWillFinishToHome(boolean willFinishToHome); + + /** + * Stops controlling a task that is currently controlled by this recents animation. + * + * This method should be called when a task that has been received via {@link #onAnimationStart} + * or {@link #onTaskAppeared} is no longer needed. After calling this method, the task will + * either disappear from the screen, or jump to its final position in case it was the top task. + * + * @param taskId Id of the Task target to remove + * @return {@code true} when target removed successfully, {@code false} otherwise. + */ + boolean removeTask(int taskId); } diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl index 6eb90fc54286..925786f82e00 100644 --- a/core/java/android/view/IRecentsAnimationRunner.aidl +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -56,4 +56,10 @@ oneway interface IRecentsAnimationRunner { void onAnimationStart(in IRecentsAnimationController controller, in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers, in Rect homeContentInsets, in Rect minimizedHomeBounds) = 2; + + /** + * Called when the task of an activity that has been started while the recents animation + * was running becomes ready for control. + */ + void onTaskAppeared(in RemoteAnimationTarget app) = 3; } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 81bfcb07ab6d..a0527b7434fc 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -48,6 +48,11 @@ interface IWindowSession { out Rect outContentInsets, out Rect outStableInsets, out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel, out InsetsState insetsState, out InsetsSourceControl[] activeControls); + int addToDisplayAsUser(IWindow window, int seq, in WindowManager.LayoutParams attrs, + in int viewVisibility, in int layerStackId, in int userId, + out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets, + out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel, + out InsetsState insetsState, out InsetsSourceControl[] activeControls); int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out Rect outContentInsets, out Rect outStableInsets, out InsetsState insetsState); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index da186087a34a..8abe72fc91e8 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -14684,17 +14684,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } } - if (isAccessibilityPane()) { - if (isVisible != oldVisible) { + + if (isVisible != oldVisible) { + if (isAccessibilityPane()) { notifyViewAccessibilityStateChangedIfNeeded(isVisible ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); } - } - notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible); - if (!getSystemGestureExclusionRects().isEmpty() && isVisible != oldVisible) { - postUpdateSystemGestureExclusionRects(); + notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible); + + if (!getSystemGestureExclusionRects().isEmpty()) { + postUpdateSystemGestureExclusionRects(); + } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index dab11088b130..ed1edc3bd526 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -111,6 +111,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; +import android.os.UserHandle; import android.sysprop.DisplayProperties; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; @@ -893,6 +894,14 @@ public final class ViewRootImpl implements ViewParent, * We have one child */ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { + setView(view, attrs, panelParentView, UserHandle.myUserId()); + } + + /** + * We have one child + */ + public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, + int userId) { synchronized (this) { if (mView == null) { mView = view; @@ -1001,8 +1010,8 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); adjustLayoutParamsForCompatibility(mWindowAttributes); - res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, - getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, + res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes, + getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mDisplayCutout, inputChannel, mTempInsets, mTempControls); @@ -1075,6 +1084,9 @@ public final class ViewRootImpl implements ViewParent, throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified window type " + mWindowAttributes.type + " is not valid"); + case WindowManagerGlobal.ADD_INVALID_USER: + throw new WindowManager.BadTokenException("Unable to add Window " + + mWindow + " -- requested userId is not valid"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index ab968d798745..fba6a55ef6db 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -144,6 +144,7 @@ public final class WindowManagerGlobal { public static final int ADD_PERMISSION_DENIED = -8; public static final int ADD_INVALID_DISPLAY = -9; public static final int ADD_INVALID_TYPE = -10; + public static final int ADD_INVALID_USER = -11; @UnsupportedAppUsage private static WindowManagerGlobal sDefaultWindowManager; @@ -325,7 +326,7 @@ public final class WindowManagerGlobal { } public void addView(View view, ViewGroup.LayoutParams params, - Display display, Window parentWindow) { + Display display, Window parentWindow, int userId) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } @@ -402,7 +403,7 @@ public final class WindowManagerGlobal { // do this last because it fires off messages to start doing things try { - root.setView(view, wparams, panelParentView); + root.setView(view, wparams, panelParentView, userId); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 316a5f2c88d2..2975d5ee8e1c 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -104,7 +104,8 @@ public final class WindowManagerImpl implements WindowManager { @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); - mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow); + mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, + mContext.getUserId()); } @Override diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 39ed4018c65c..ec5130143086 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -130,6 +130,20 @@ public class WindowlessWindowManager implements IWindowSession { return WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE; } + /** + * IWindowSession implementation. Currently this class doesn't need to support for multi-user. + */ + @Override + public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs, + int viewVisibility, int displayId, int userId, Rect outFrame, + Rect outContentInsets, Rect outStableInsets, + DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { + return addToDisplay(window, seq, attrs, viewVisibility, displayId, + outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, + outInsetsState, outActiveControls); + } + @Override public int addToDisplayWithoutInputChannel(android.view.IWindow window, int seq, android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId, diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 482d5b25e9da..71dd6653f6a6 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1968,6 +1968,38 @@ public final class InputMethodManager { return true; } + /** + * An empty method only to avoid crashes of apps that call this method via reflection and do not + * handle {@link NoSuchMethodException} in a graceful manner. + * + * @deprecated This is an empty method. No framework method must call this method. + * @hide + */ + @Deprecated + @UnsupportedAppUsage(trackingBug = 37122102, maxTargetSdk = Build.VERSION_CODES.Q, + publicAlternatives = "{@code androidx.activity.ComponentActivity}") + public void windowDismissed(IBinder appWindowToken) { + // Intentionally empty. + // + // It seems that some applications call this method via reflection to null clear the + // following fields that used to exist in InputMethodManager: + // * InputMethodManager#mCurRootView + // * InputMethodManager#mServedView + // * InputMethodManager#mNextServedView + // so that these objects can be garbage-collected when an Activity gets dismissed. + // + // It is indeed true that older versions of InputMethodManager had issues that prevented + // these fields from being null-cleared when it should have been, but the understanding of + // the engineering team is that all known issues have already been fixed as of Android 10. + // + // For older devices, developers can work around the object leaks by using + // androidx.activity.ComponentActivity. + // See https://issuetracker.google.com/u/1/issues/37122102 for details. + // + // If you believe InputMethodManager is leaking objects in API 24 or any later version, + // please file a bug at https://issuetracker.google.com/issues/new?component=192705. + } + private int getStartInputFlags(View focusedView, int startInputFlags) { startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS; if (focusedView.onCheckIsTextEditor()) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 1c978bff0fd3..32a79f3ab8ae 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1099,9 +1099,9 @@ android:description="@string/permgroupdesc_phone" android:priority="500" /> - <!-- Allows read only access to phone state, including the phone number of the device, - current cellular network information, the status of any ongoing calls, and a list of any - {@link android.telecom.PhoneAccount}s registered on the device. + <!-- Allows read only access to phone state, including the current cellular network information, + the status of any ongoing calls, and a list of any {@link android.telecom.PhoneAccount}s + registered on the device. <p class="note"><strong>Note:</strong> If <em>both</em> your <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a> and <a diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp index a2fcef56b780..2b38cca2db36 100644 --- a/core/tests/overlaytests/host/Android.bp +++ b/core/tests/overlaytests/host/Android.bp @@ -16,7 +16,7 @@ java_test_host { name: "OverlayHostTests", srcs: ["src/**/*.java"], libs: ["tradefed"], - test_suites: ["general-tests"], + test_suites: ["device-tests"], target_required: [ "OverlayHostTests_NonPlatformSignatureOverlay", "OverlayHostTests_PlatformSignatureStaticOverlay", diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java index eec7be22c78b..d898d222b8de 100644 --- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java +++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java @@ -78,14 +78,9 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { } @Test - public void failToInstallPlatformSignedStaticOverlay() throws Exception { - try { - installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk"); - fail("installed a static overlay"); - } catch (Exception e) { - // Expected. - } - assertFalse(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME)); + public void installedIsStaticOverlayIsMutable() throws Exception { + installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk"); + assertTrue(isOverlayMutable(SIG_OVERLAY_PACKAGE_NAME)); } @Test @@ -229,6 +224,10 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { return shell("cmd overlay list").contains(pkg); } + private boolean isOverlayMutable(String pkg) throws Exception { + return shell("cmd overlay dump ismutable " + pkg).contains("true"); + } + private String shell(final String cmd) throws Exception { return getDevice().executeShellCommand(cmd); } diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk index cc7704b0ce98..f3c0abd8293f 100644 --- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk +++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk @@ -20,7 +20,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad include $(BUILD_PACKAGE) @@ -28,7 +28,8 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests +LOCAL_CERTIFICATE := platform LOCAL_MANIFEST_FILE := static/AndroidManifest.xml LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static include $(BUILD_PACKAGE) @@ -37,7 +38,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk index f8607f44bda6..878f05d57662 100644 --- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk +++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk @@ -19,7 +19,7 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules LOCAL_USE_AAPT2 := true LOCAL_AAPT_FLAGS := --no-resource-removal @@ -31,7 +31,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1 LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 @@ -43,7 +43,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2 LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2 LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2 @@ -57,7 +57,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1 LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res @@ -68,7 +68,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := tests LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2 LOCAL_SDK_VERSION := current -LOCAL_COMPATIBILITY_SUITE := general-tests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2 LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/Android.bp index 3825c55f3b4b..5757cfe75514 100644 --- a/core/tests/overlaytests/remount/host/Android.bp +++ b/core/tests/overlaytests/remount/Android.bp @@ -21,8 +21,12 @@ java_test_host { ], test_suites: ["general-tests"], java_resources: [ + ":com.android.overlaytest.overlaid", + ":com.android.overlaytest.overlay", ":OverlayRemountedTest_SharedLibrary", ":OverlayRemountedTest_SharedLibraryOverlay", ":OverlayRemountedTest_Target", + ":OverlayRemountedTest_TargetUpgrade", + ":OverlayRemountedTest_Overlay", ], } diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/AndroidTest.xml index 087b7313693d..087b7313693d 100644 --- a/core/tests/overlaytests/remount/host/AndroidTest.xml +++ b/core/tests/overlaytests/remount/AndroidTest.xml diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayApexTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayApexTest.java new file mode 100644 index 000000000000..3fa8bcd6cbf0 --- /dev/null +++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayApexTest.java @@ -0,0 +1,66 @@ +/* + * 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.overlaytest.remounted; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class OverlayApexTest extends OverlayRemountedTestBase { + private static final String OVERLAID_APEX = "com.android.overlaytest.overlaid.apex"; + private static final String OVERLAY_APEX = "com.android.overlaytest.overlay.apex"; + + @Test + public void testApkInApexCanBeOverlaid() throws Exception { + final String targetResource = resourceName(TARGET_PACKAGE, "bool", "target_overlaid"); + + // The target APK will be installed inside the overlaid APEX. + mPreparer.pushResourceFile(OVERLAID_APEX, + "/system/apex/com.android.overlaytest.overlaid.apex") + .installResourceApk(OVERLAY_APK, OVERLAY_PACKAGE) + .reboot() + .setOverlayEnabled(OVERLAY_PACKAGE, false); + + // The resource is not currently overlaid. + assertResource(targetResource, "false"); + + // Overlay the resource. + mPreparer.setOverlayEnabled(OVERLAY_PACKAGE, true); + assertResource(targetResource, "true"); + } + + @Test + public void testApkInApexCanOverlay() throws Exception { + final String targetResource = resourceName(TARGET_PACKAGE, "bool", "target_overlaid"); + + // The overlay APK will be installed inside the overlay APEX. + mPreparer.pushResourceFile(OVERLAY_APEX, + "/system/apex/com.android.overlaytest.overlay.apex") + .installResourceApk(TARGET_APK, TARGET_PACKAGE) + .reboot() + .setOverlayEnabled(OVERLAY_PACKAGE, false); + + // The resource is not currently overlaid. + assertResource(targetResource, "false"); + + // Overlay the resource. + mPreparer.setOverlayEnabled(OVERLAY_PACKAGE, true); + assertResource(targetResource, "true"); + } +} diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java new file mode 100644 index 000000000000..14b5bf6eacba --- /dev/null +++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java @@ -0,0 +1,74 @@ +/* + * 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.overlaytest.remounted; + +import static org.junit.Assert.fail; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.RuleChain; +import org.junit.rules.TemporaryFolder; + +public class OverlayRemountedTestBase extends BaseHostJUnit4Test { + private static final long ASSERT_RESOURCE_TIMEOUT_MS = 30000; + static final String TARGET_APK = "OverlayRemountedTest_Target.apk"; + static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target"; + static final String OVERLAY_APK = "OverlayRemountedTest_Overlay.apk"; + static final String OVERLAY_PACKAGE = "com.android.overlaytest.remounted.target.overlay"; + + private final TemporaryFolder mTemporaryFolder = new TemporaryFolder(); + protected final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, + this::getDevice); + + @Rule + public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer); + + @Before + public void startBefore() throws DeviceNotAvailableException { + getDevice().waitForDeviceAvailable(); + } + + /** Builds the full name of a resource in the form package:type/entry. */ + String resourceName(String pkg, String type, String entry) { + return String.format("%s:%s/%s", pkg, type, entry); + } + + void assertResource(String resourceName, String expectedValue) + throws DeviceNotAvailableException { + String result = null; + + final long endMillis = System.currentTimeMillis() + ASSERT_RESOURCE_TIMEOUT_MS; + while (System.currentTimeMillis() <= endMillis) { + result = getDevice().executeShellCommand( + String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName)); + if (result.equals(expectedValue + "\n") || + result.endsWith("-> " + expectedValue + "\n")) { + return; + } + + try { + Thread.sleep(200); + } catch (InterruptedException ignore) { + } + } + + fail(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result)); + } +} diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java index 06b2ac8f9e22..7f2c060b13fc 100644 --- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java +++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java @@ -16,23 +16,13 @@ package com.android.overlaytest.remounted; -import static org.junit.Assert.assertTrue; - -import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; -import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.RuleChain; -import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; @RunWith(DeviceJUnit4ClassRunner.class) -public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { - private static final String TARGET_APK = "OverlayRemountedTest_Target.apk"; - private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target"; +public class OverlaySharedLibraryTest extends OverlayRemountedTestBase { private static final String SHARED_LIBRARY_APK = "OverlayRemountedTest_SharedLibrary.apk"; private static final String SHARED_LIBRARY_PACKAGE = @@ -42,17 +32,6 @@ public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { private static final String SHARED_LIBRARY_OVERLAY_PACKAGE = "com.android.overlaytest.remounted.shared_library.overlay"; - public final TemporaryFolder temporaryFolder = new TemporaryFolder(); - public final SystemPreparer preparer = new SystemPreparer(temporaryFolder, this::getDevice); - - @Rule - public final RuleChain ruleChain = RuleChain.outerRule(temporaryFolder).around(preparer); - - @Before - public void startBefore() throws DeviceNotAvailableException { - getDevice().waitForDeviceAvailable(); - } - @Test public void testSharedLibrary() throws Exception { final String targetResource = resourceName(TARGET_PACKAGE, "bool", @@ -60,7 +39,7 @@ public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool", "shared_library_overlaid"); - preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") + mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE) .reboot() .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false) @@ -71,7 +50,7 @@ public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { assertResource(libraryResource, "false"); // Overlay the shared library resource. - preparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true); + mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true); assertResource(targetResource, "true"); assertResource(libraryResource, "true"); } @@ -83,7 +62,7 @@ public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool", "shared_library_overlaid"); - preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") + mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk") .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE) .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true) .reboot() @@ -92,18 +71,4 @@ public class OverlaySharedLibraryTest extends BaseHostJUnit4Test { assertResource(targetResource, "true"); assertResource(libraryResource, "true"); } - - /** Builds the full name of a resource in the form package:type/entry. */ - String resourceName(String pkg, String type, String entry) { - return String.format("%s:%s/%s", pkg, type, entry); - } - - void assertResource(String resourceName, String expectedValue) - throws DeviceNotAvailableException { - final String result = getDevice().executeShellCommand( - String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName)); - assertTrue(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result), - result.equals(expectedValue + "\n") || - result.endsWith("-> " + expectedValue + "\n")); - } } diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java new file mode 100644 index 000000000000..70e342370545 --- /dev/null +++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java @@ -0,0 +1,66 @@ +/* + * 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.overlaytest.remounted; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class PackagedUpgradedTest extends OverlayRemountedTestBase { + private static final String TARGET_UPGRADE_APK = "OverlayRemountedTest_TargetUpgrade.apk"; + + @Test + public void testTargetUpgrade() throws Exception { + final String targetOverlaid = resourceName(TARGET_PACKAGE, "bool", "target_overlaid"); + final String targetReference = resourceName(TARGET_PACKAGE, "bool", "target_reference"); + + mPreparer.pushResourceFile(TARGET_APK, "/product/app/OverlayTarget.apk") + .reboot() + .installResourceApk(OVERLAY_APK, OVERLAY_PACKAGE) + .setOverlayEnabled(OVERLAY_PACKAGE, true); + + assertResource(targetReference, "@" + 0x7f010000 + " -> true"); + assertResource(targetOverlaid, "true"); + + mPreparer.installResourceApk(TARGET_UPGRADE_APK, TARGET_PACKAGE); + + assertResource(targetReference, "@" + 0x7f0100ff + " -> true"); + assertResource(targetOverlaid, "true"); + } + + @Test + public void testTargetRelocated() throws Exception { + final String targetOverlaid = resourceName(TARGET_PACKAGE, "bool", "target_overlaid"); + final String originalPath = "/product/app/OverlayTarget.apk"; + + mPreparer.pushResourceFile(TARGET_APK, originalPath) + .reboot() + .installResourceApk(OVERLAY_APK, OVERLAY_PACKAGE) + .setOverlayEnabled(OVERLAY_PACKAGE, true); + + assertResource(targetOverlaid, "true"); + + mPreparer.remount(); + getDevice().deleteFile(originalPath); + mPreparer.pushResourceFile(TARGET_UPGRADE_APK, "/product/app/OverlayTarget2.apk") + .reboot(); + + assertResource(targetOverlaid, "true"); + } +} diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java index 8696091239c2..bb72d0ee1d03 100644 --- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java +++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java @@ -18,8 +18,6 @@ package com.android.overlaytest.remounted; import static org.junit.Assert.assertTrue; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; @@ -32,10 +30,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeoutException; class SystemPreparer extends ExternalResource { private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000; @@ -58,7 +52,7 @@ class SystemPreparer extends ExternalResource { SystemPreparer pushResourceFile(String resourcePath, String outputPath) throws DeviceNotAvailableException, IOException { final ITestDevice device = mDeviceProvider.getDevice(); - device.executeAdbCommand("remount"); + remount(); assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath)); mPushedFiles.add(outputPath); return this; @@ -69,42 +63,37 @@ class SystemPreparer extends ExternalResource { throws DeviceNotAvailableException, IOException { final ITestDevice device = mDeviceProvider.getDevice(); final File tmpFile = copyResourceToTemp(resourcePath); - final String result = device.installPackage(tmpFile, true); + final String result = device.installPackage(tmpFile, true /* reinstall */); Assert.assertNull(result); mInstalledPackages.add(packageName); return this; } - /** Sets the enable state of an overlay pacakage. */ + /** Sets the enable state of an overlay package. */ SystemPreparer setOverlayEnabled(String packageName, boolean enabled) - throws ExecutionException, DeviceNotAvailableException { + throws DeviceNotAvailableException { final ITestDevice device = mDeviceProvider.getDevice(); + final String enable = enabled ? "enable" : "disable"; // Wait for the overlay to change its enabled state. - final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> { - while (true) { - device.executeShellCommand(String.format("cmd overlay %s %s", - enabled ? "enable" : "disable", packageName)); - - final String result = device.executeShellCommand("cmd overlay dump " + packageName); - final int startIndex = result.indexOf("mIsEnabled"); - final int endIndex = result.indexOf('\n', startIndex); - if (result.substring(startIndex, endIndex).contains((enabled) ? "true" : "false")) { - return true; - } + final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS; + String result; + while (System.currentTimeMillis() <= endMillis) { + device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName)); + result = device.executeShellCommand("cmd overlay dump isenabled " + + packageName); + if (((enabled) ? "true\n" : "false\n").equals(result)) { + return this; } - }); - final Executor executor = (cmd) -> new Thread(cmd).start(); - executor.execute(enabledListener); - try { - enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS); - } catch (InterruptedException ignored) { - } catch (TimeoutException e) { - throw new IllegalStateException(device.executeShellCommand("cmd overlay list")); + try { + Thread.sleep(200); + } catch (InterruptedException ignore) { + } } - return this; + throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable, + packageName, device.executeShellCommand("cmd overlay list"))); } /** Restarts the device and waits until after boot is completed. */ @@ -114,6 +103,11 @@ class SystemPreparer extends ExternalResource { return this; } + SystemPreparer remount() throws DeviceNotAvailableException { + mDeviceProvider.getDevice().executeAdbCommand("remount"); + return this; + } + /** Copies a file within the host test jar to a temporary file on the host machine. */ private File copyResourceToTemp(String resourcePath) throws IOException { final File tempFile = mHostTempFolder.newFile(resourcePath); @@ -138,7 +132,7 @@ class SystemPreparer extends ExternalResource { protected void after() { final ITestDevice device = mDeviceProvider.getDevice(); try { - device.executeAdbCommand("remount"); + remount(); for (final String file : mPushedFiles) { device.deleteFile(file); } diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp index 83f9f28b3f48..a1fdbfd3542c 100644 --- a/core/tests/overlaytests/remount/target/Android.bp +++ b/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp @@ -1,4 +1,4 @@ -// Copyright (C) 2018 The Android Open Source Project +// Copyright (C) 2019 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,8 +13,9 @@ // limitations under the License. android_test_helper_app { - name: "OverlayRemountedTest_Target", - srcs: ["src/**/*.java"], - sdk_version: "test_current", - libs: ["OverlayRemountedTest_SharedLibrary"], + name: "OverlayRemountedTest_Overlay", + sdk_version: "current", + apex_available: [ + "com.android.overlaytest.overlay", + ], } diff --git a/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml new file mode 100644 index 000000000000..d6d706c2da62 --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.remounted.target.overlay"> + <application android:hasCode="false" /> + <overlay android:targetPackage="com.android.overlaytest.remounted.target" + android:targetName="TestResources" /> +</manifest>
\ No newline at end of file diff --git a/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml new file mode 100644 index 000000000000..675e44f19f95 --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml @@ -0,0 +1,20 @@ +<?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> + <bool name="target_overlaid">true</bool> +</resources> diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/test-apps/SharedLibrary/Android.bp index ffb0572c3fd0..ffb0572c3fd0 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp +++ b/core/tests/overlaytests/remount/test-apps/SharedLibrary/Android.bp diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/AndroidManifest.xml index 06e3f6a99410..06e3f6a99410 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibrary/AndroidManifest.xml diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/overlayable.xml index 1b06f6d7530b..1b06f6d7530b 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/overlayable.xml diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/public.xml index 5b9db163a274..5b9db163a274 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/public.xml diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/values.xml index 2dc47a7ecf61..2dc47a7ecf61 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/values.xml diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/Android.bp index 0d29aec909d5..0d29aec909d5 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp +++ b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/Android.bp diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/AndroidManifest.xml index 53a4e61949da..53a4e61949da 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/AndroidManifest.xml diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/res/values/values.xml index f66448a8d991..f66448a8d991 100644 --- a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml +++ b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/res/values/values.xml diff --git a/core/tests/overlaytests/remount/test-apps/Target/Android.bp b/core/tests/overlaytests/remount/test-apps/Target/Android.bp new file mode 100644 index 000000000000..19947b1ea51a --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Target/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test_helper_app { + name: "OverlayRemountedTest_Target", + sdk_version: "test_current", + apex_available: [ + "com.android.overlaytest.overlaid", + ], + libs: ["OverlayRemountedTest_SharedLibrary"], +} + +android_test_helper_app { + name: "OverlayRemountedTest_TargetUpgrade", + resource_dirs: ["res_upgrade"], + sdk_version: "test_current", +} diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml index dc07dca16718..d1c7b7e8bb9d 100644 --- a/core/tests/overlaytests/remount/target/AndroidManifest.xml +++ b/core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml @@ -19,8 +19,7 @@ package="com.android.overlaytest.remounted.target"> <application> - <uses-library android:name="android.test.runner" /> <uses-library android:name="com.android.overlaytest.remounted.shared_library" - android:required="true" /> + android:required="false" /> </application> </manifest> diff --git a/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml new file mode 100644 index 000000000000..4aa5bcee8f3d --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml @@ -0,0 +1,24 @@ +<?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> + <overlayable name="TestResources"> + <policy type="public"> + <item type="bool" name="target_overlaid" /> + </policy> + </overlayable> +</resources> diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml index b5f444a5eb72..76253a95b766 100644 --- a/core/tests/overlaytests/remount/target/res/values/values.xml +++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml @@ -17,4 +17,10 @@ <resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library"> <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool> + + <!-- This resource has a different id in the updated version of this target app to test that the + idmap is regenerated when the target is updated. --> + <bool name="target_overlaid">false</bool> + <public type="bool" name="target_overlaid" id="0x7f010000" /> + <bool name="target_reference">@bool/target_overlaid</bool> </resources> diff --git a/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml new file mode 100644 index 000000000000..4aa5bcee8f3d --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml @@ -0,0 +1,24 @@ +<?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> + <overlayable name="TestResources"> + <policy type="public"> + <item type="bool" name="target_overlaid" /> + </policy> + </overlayable> +</resources> diff --git a/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml new file mode 100644 index 000000000000..f552cb0776ab --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml @@ -0,0 +1,23 @@ +<?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> + <!-- This resource has a different id in the updated target app than the base target app to test + that the idmap is regenerated when the target is updated. --> + <bool name="target_overlaid">false</bool> + <public type="bool" name="target_overlaid" id="0x7f0100ff" /> + <bool name="target_reference">@bool/target_overlaid</bool> +</resources>
\ No newline at end of file diff --git a/core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp b/core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp new file mode 100644 index 000000000000..e6ebd5ea76d8 --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp @@ -0,0 +1,42 @@ +// 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. + +genrule { + name: "com.android.overlaytest.overlaid.pem", + out: ["com.android.overlaytest.overlaid.pem"], + cmd: "openssl genrsa -out $(out) 4096", +} + +genrule { + name: "com.android.overlaytest.overlaid.pubkey", + srcs: [":com.android.overlaytest.overlaid.pem"], + out: ["com.android.overlaytest.overlaid.pubkey"], + tools: ["avbtool"], + cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)", +} + +apex_key { + name: "com.android.overlaytest.overlaid.key", + public_key: ":com.android.overlaytest.overlaid.pubkey", + private_key: ":com.android.overlaytest.overlaid.pem", +} + +apex { + name: "com.android.overlaytest.overlaid", + manifest: "manifest.json", + file_contexts: ":apex.test-file_contexts", + key: "com.android.overlaytest.overlaid.key", + apps: ["OverlayRemountedTest_Target"], + installable: false, +} diff --git a/core/tests/overlaytests/remount/test-apps/overlaid_apex/manifest.json b/core/tests/overlaytests/remount/test-apps/overlaid_apex/manifest.json new file mode 100644 index 000000000000..9a5102fde11f --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/overlaid_apex/manifest.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.overlaytest.overlaid", + "version": "1" +} diff --git a/core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp b/core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp new file mode 100644 index 000000000000..07f27ee55d39 --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp @@ -0,0 +1,42 @@ +// 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. + +genrule { + name: "com.android.overlaytest.overlay.pem", + out: ["com.android.overlaytest.overlay.pem"], + cmd: "openssl genrsa -out $(out) 4096", +} + +genrule { + name: "com.android.overlaytest.overlay.pubkey", + srcs: [":com.android.overlaytest.overlay.pem"], + out: ["com.android.overlaytest.overlay.pubkey"], + tools: ["avbtool"], + cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)", +} + +apex_key { + name: "com.android.overlaytest.overlay.key", + public_key: ":com.android.overlaytest.overlay.pubkey", + private_key: ":com.android.overlaytest.overlay.pem", +} + +apex { + name: "com.android.overlaytest.overlay", + manifest: "manifest.json", + file_contexts: ":apex.test-file_contexts", + key: "com.android.overlaytest.overlay.key", + apps: ["OverlayRemountedTest_Overlay"], + installable: false, +} diff --git a/core/tests/overlaytests/remount/test-apps/overlay_apex/manifest.json b/core/tests/overlaytests/remount/test-apps/overlay_apex/manifest.json new file mode 100644 index 000000000000..ac5f84659eef --- /dev/null +++ b/core/tests/overlaytests/remount/test-apps/overlay_apex/manifest.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.overlaytest.overlay", + "version": "1" +}
\ No newline at end of file diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 19ad6f717ea5..18086ec0313e 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -883,6 +883,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-242787066": { + "message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s", + "level": "DEBUG", + "group": "WM_DEBUG_RECENTS_ANIMATIONS", + "at": "com\/android\/server\/wm\/WindowContainer.java" + }, "-198463978": { "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b", "level": "VERBOSE", @@ -901,6 +907,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java" }, + "-172900257": { + "message": "addTaskToTargets, target: %s", + "level": "DEBUG", + "group": "WM_DEBUG_RECENTS_ANIMATIONS", + "at": "com\/android\/server\/wm\/RecentsAnimationController.java" + }, "-167822951": { "message": "Attempted to add starting window to token with already existing starting window", "level": "WARN", @@ -1201,6 +1213,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowSurfaceController.java" }, + "315395835": { + "message": "Trying to add window with invalid user=%d", + "level": "WARN", + "group": "WM_ERROR", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "342460966": { "message": "DRAG %s: pos=(%d,%d)", "level": "INFO", @@ -1525,6 +1543,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "854237232": { + "message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s", + "level": "DEBUG", + "group": "WM_DEBUG_RECENTS_ANIMATIONS", + "at": "com\/android\/server\/wm\/Task.java" + }, "873914452": { "message": "goodToGo()", "level": "DEBUG", diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 918e7af12d31..05f4d6b63a4c 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -385,7 +385,7 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap const StringPiece idmap_data( reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)), static_cast<size_t>(idmap_asset->getLength())); - std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_data); + std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_path, idmap_data); if (loaded_idmap == nullptr) { LOG(ERROR) << "failed to load IDMAP " << idmap_path; return {}; @@ -538,8 +538,9 @@ bool ApkAssets::IsUpToDate() const { // Loaders are invalidated by the app, not the system, so assume they are up to date. return true; } + return (!loaded_idmap_ || loaded_idmap_->IsUpToDate()) && + last_mod_time_ == getFileModDate(path_.c_str()); - return last_mod_time_ == getFileModDate(path_.c_str()); } } // namespace android diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index 0b2fd9ec982d..eb6ee9525bb9 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -20,6 +20,7 @@ #include "android-base/logging.h" #include "android-base/stringprintf.h" +#include "androidfw/misc.h" #include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" #include "utils/ByteOrder.h" @@ -192,7 +193,9 @@ static bool IsValidIdmapHeader(const StringPiece& data) { return true; } -LoadedIdmap::LoadedIdmap(const Idmap_header* header, +LoadedIdmap::LoadedIdmap(std::string&& idmap_path, + const time_t last_mod_time, + const Idmap_header* header, const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_overlay_entry* overlay_entries, @@ -201,7 +204,9 @@ LoadedIdmap::LoadedIdmap(const Idmap_header* header, data_header_(data_header), target_entries_(target_entries), overlay_entries_(overlay_entries), - string_pool_(string_pool) { + string_pool_(string_pool), + idmap_path_(std::move(idmap_path)), + idmap_last_mod_time_(last_mod_time) { size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path), arraysize(header_->overlay_path)); @@ -212,7 +217,8 @@ LoadedIdmap::LoadedIdmap(const Idmap_header* header, target_apk_path_.assign(reinterpret_cast<const char*>(header_->target_path), length); } -std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_data) { +std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, + const StringPiece& idmap_data) { ATRACE_CALL(); if (!IsValidIdmapHeader(idmap_data)) { return {}; @@ -275,10 +281,14 @@ std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_da // Can't use make_unique because LoadedIdmap constructor is private. std::unique_ptr<LoadedIdmap> loaded_idmap = std::unique_ptr<LoadedIdmap>( - new LoadedIdmap(header, data_header, target_entries, overlay_entries, - idmap_string_pool.release())); + new LoadedIdmap(idmap_path.to_string(), getFileModDate(idmap_path.data()), header, + data_header, target_entries, overlay_entries, idmap_string_pool.release())); return std::move(loaded_idmap); } +bool LoadedIdmap::IsUpToDate() const { + return idmap_last_mod_time_ == getFileModDate(idmap_path_.c_str()); +} + } // namespace android diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index ccb57f373473..ecc1ce65d124 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -142,7 +142,13 @@ class IdmapResMap { class LoadedIdmap { public: // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed. - static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_data); + static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_path, + const StringPiece& idmap_data); + + // Returns the path to the IDMAP. + inline const std::string& IdmapPath() const { + return idmap_path_; + } // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated. inline const std::string& OverlayApkPath() const { @@ -167,6 +173,10 @@ class LoadedIdmap { return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id); } + // Returns whether the idmap file on disk has not been modified since the construction of this + // LoadedIdmap. + bool IsUpToDate() const; + protected: // Exposed as protected so that tests can subclass and mock this class out. LoadedIdmap() = default; @@ -177,13 +187,17 @@ class LoadedIdmap { const Idmap_overlay_entry* overlay_entries_; const std::unique_ptr<ResStringPool> string_pool_; + const std::string idmap_path_; std::string overlay_apk_path_; std::string target_apk_path_; + const time_t idmap_last_mod_time_; private: DISALLOW_COPY_AND_ASSIGN(LoadedIdmap); - explicit LoadedIdmap(const Idmap_header* header, + explicit LoadedIdmap(std::string&& idmap_path, + time_t last_mod_time, + const Idmap_header* header, const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_overlay_entry* overlay_entries, diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index 41ba637da5d7..7aa0dbbafab3 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -38,7 +38,7 @@ class IdmapTest : public ::testing::Test { protected: void SetUp() override { // Move to the test data directory so the idmap can locate the overlay APK. - std::string original_path = base::GetExecutableDirectory(); + original_path = base::GetExecutableDirectory(); chdir(GetTestDataPath().c_str()); system_assets_ = ApkAssets::Load("system/system.apk"); @@ -49,10 +49,14 @@ class IdmapTest : public ::testing::Test { overlayable_assets_ = ApkAssets::Load("overlayable/overlayable.apk"); ASSERT_NE(nullptr, overlayable_assets_); + } + + void TearDown() override { chdir(original_path.c_str()); } protected: + std::string original_path; std::unique_ptr<const ApkAssets> system_assets_; std::unique_ptr<const ApkAssets> overlay_assets_; std::unique_ptr<const ApkAssets> overlayable_assets_; @@ -221,8 +225,7 @@ TEST_F(IdmapTest, OverlaidResourceHasSameName) { TEST_F(IdmapTest, OverlayLoaderInterop) { std::string contents; - auto loader_assets = ApkAssets::LoadTable(GetTestDataPath() + "/loader/resources.arsc", - PROPERTY_LOADER); + auto loader_assets = ApkAssets::LoadTable("loader/resources.arsc", PROPERTY_LOADER); AssetManager2 asset_manager; asset_manager.SetApkAssets({overlayable_assets_.get(), loader_assets.get(), @@ -241,4 +244,25 @@ TEST_F(IdmapTest, OverlayLoaderInterop) { ASSERT_EQ(GetStringFromApkAssets(asset_manager, val, cookie), "loader"); } +TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { + std::string idmap_contents; + ASSERT_TRUE(base::ReadFileToString("overlay/overlay.idmap", &idmap_contents)); + + TemporaryFile temp_file; + ASSERT_TRUE(base::WriteStringToFile(idmap_contents, temp_file.path)); + + auto apk_assets = ApkAssets::LoadOverlay(temp_file.path); + ASSERT_NE(nullptr, apk_assets); + ASSERT_TRUE(apk_assets->IsUpToDate()); + + unlink(temp_file.path); + ASSERT_FALSE(apk_assets->IsUpToDate()); + sleep(2); + + base::WriteStringToFile("hello", temp_file.path); + sleep(2); + + ASSERT_FALSE(apk_assets->IsUpToDate()); +} + } // namespace diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 48aed349c39a..861eeea3bc34 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -394,7 +394,7 @@ public class Tuner implements AutoCloseable { private native Lnb nativeOpenLnbByName(String name); private native Descrambler nativeOpenDescramblerByHandle(int handle); - private native Descrambler nativeOpenDemuxByhandle(int handle); + private native int nativeOpenDemuxByhandle(int handle); private native DvrRecorder nativeOpenDvrRecorder(long bufferSize); private native DvrPlayback nativeOpenDvrPlayback(long bufferSize); @@ -985,7 +985,7 @@ public class Tuner implements AutoCloseable { boolean granted = mTunerResourceManager.requestDescrambler(request, descramblerHandle); if (granted) { mDescramblerHandle = descramblerHandle[0]; - nativeOpenDescramblerByHandle(mDescramblerHandle); + mDescrambler = nativeOpenDescramblerByHandle(mDescramblerHandle); } return granted; } diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index fc9b91c76a4f..a31f177d66ab 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -676,8 +676,6 @@ status_t JMediaCodec::getOutputFrame( if (buffer->size() > 0) { std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer(); if (c2Buffer) { - // asC2Buffer clears internal reference, so set the reference again. - buffer->copy(c2Buffer); switch (c2Buffer->data().type()) { case C2BufferData::LINEAR: { std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; @@ -2526,7 +2524,7 @@ static void android_media_MediaCodec_setAudioPresentation( codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId); } -static void android_media_MediaCodec_native_init(JNIEnv *env) { +static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { ScopedLocalRef<jclass> clazz( env, env->FindClass("android/media/MediaCodec")); CHECK(clazz.get() != NULL); @@ -2983,7 +2981,7 @@ static void android_media_MediaCodec_LinearBlock_native_obtain( } static jboolean android_media_MediaCodec_LinearBlock_checkCompatible( - JNIEnv *env, jobjectArray codecNames) { + JNIEnv *env, jclass, jobjectArray codecNames) { std::vector<std::string> names; PopulateNamesVector(env, codecNames, &names); bool isCompatible = false; diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index ac7fe5d0403d..7579ca58b37b 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -880,10 +880,12 @@ jobject JTuner::getFrontendIds() { jobject JTuner::openFrontendById(int id) { sp<IFrontend> fe; - mTuner->openFrontendById(id, [&](Result, const sp<IFrontend>& frontend) { + Result res; + mTuner->openFrontendById(id, [&](Result r, const sp<IFrontend>& frontend) { fe = frontend; + res = r; }); - if (fe == nullptr) { + if (res != Result::SUCCESS || fe == nullptr) { ALOGE("Failed to open frontend"); return NULL; } @@ -906,7 +908,7 @@ jobject JTuner::openFrontendById(int id) { (jint) jId); } -jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { +jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); @@ -915,7 +917,7 @@ jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabil return env->NewObject(clazz, capsInit, typeCap, sifStandardCap); } -jobject JTuner::getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { +jobject JTuner::getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3FrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIII)V"); @@ -930,7 +932,7 @@ jobject JTuner::getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabili codeRateCap, fecCap, demodOutputFormatCap); } -jobject JTuner::getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { +jobject JTuner::getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AtscFrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(I)V"); @@ -939,7 +941,7 @@ jobject JTuner::getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilit return env->NewObject(clazz, capsInit, modulationCap); } -jobject JTuner::getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { +jobject JTuner::getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbcFrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(III)V"); @@ -950,7 +952,7 @@ jobject JTuner::getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilit return env->NewObject(clazz, capsInit, modulationCap, fecCap, annexCap); } -jobject JTuner::getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { +jobject JTuner::getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbsFrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IJI)V"); @@ -961,7 +963,7 @@ jobject JTuner::getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilit return env->NewObject(clazz, capsInit, modulationCap, innerfecCap, standard); } -jobject JTuner::getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { +jobject JTuner::getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/DvbtFrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIIIZZ)V"); @@ -978,7 +980,7 @@ jobject JTuner::getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilit coderateCap, hierarchyCap, guardIntervalCap, isT2Supported, isMisoSupported); } -jobject JTuner::getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { +jobject JTuner::getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); @@ -988,7 +990,7 @@ jobject JTuner::getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabil return env->NewObject(clazz, capsInit, modulationCap, coderateCap); } -jobject JTuner::getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { +jobject JTuner::getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IsdbsFrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V"); @@ -998,7 +1000,7 @@ jobject JTuner::getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabili return env->NewObject(clazz, capsInit, modulationCap, coderateCap); } -jobject JTuner::getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps) { +jobject JTuner::getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps) { jclass clazz = env->FindClass("android/media/tv/tuner/frontend/IsdbtFrontendCapabilities"); jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIII)V"); @@ -1044,31 +1046,58 @@ jobject JTuner::getFrontendInfo(int id) { jobject jcaps = NULL; switch(feInfo.type) { case FrontendType::ANALOG: - jcaps = getAnalogFrontendCaps(env, caps); + if (FrontendInfo::FrontendCapabilities::hidl_discriminator::analogCaps + == caps.getDiscriminator()) { + jcaps = getAnalogFrontendCaps(env, caps); + } break; case FrontendType::ATSC3: - jcaps = getAtsc3FrontendCaps(env, caps); + if (FrontendInfo::FrontendCapabilities::hidl_discriminator::atsc3Caps + == caps.getDiscriminator()) { + jcaps = getAtsc3FrontendCaps(env, caps); + } break; case FrontendType::ATSC: - jcaps = getAtscFrontendCaps(env, caps); + if (FrontendInfo::FrontendCapabilities::hidl_discriminator::atscCaps + == caps.getDiscriminator()) { + jcaps = getAtscFrontendCaps(env, caps); + } break; case FrontendType::DVBC: - jcaps = getDvbcFrontendCaps(env, caps); + if (FrontendInfo::FrontendCapabilities::hidl_discriminator::dvbcCaps + == caps.getDiscriminator()) { + jcaps = getDvbcFrontendCaps(env, caps); + } break; case FrontendType::DVBS: - jcaps = getDvbsFrontendCaps(env, caps); + if (FrontendInfo::FrontendCapabilities::hidl_discriminator::dvbsCaps + == caps.getDiscriminator()) { + jcaps = getDvbsFrontendCaps(env, caps); + } break; case FrontendType::DVBT: - jcaps = getDvbtFrontendCaps(env, caps); + if (FrontendInfo::FrontendCapabilities::hidl_discriminator::dvbtCaps + == caps.getDiscriminator()) { + jcaps = getDvbtFrontendCaps(env, caps); + } break; case FrontendType::ISDBS: - jcaps = getIsdbsFrontendCaps(env, caps); + if (FrontendInfo::FrontendCapabilities::hidl_discriminator::isdbsCaps + == caps.getDiscriminator()) { + jcaps = getIsdbsFrontendCaps(env, caps); + } break; case FrontendType::ISDBS3: - jcaps = getIsdbs3FrontendCaps(env, caps); + if (FrontendInfo::FrontendCapabilities::hidl_discriminator::isdbs3Caps + == caps.getDiscriminator()) { + jcaps = getIsdbs3FrontendCaps(env, caps); + } break; case FrontendType::ISDBT: - jcaps = getIsdbtFrontendCaps(env, caps); + if (FrontendInfo::FrontendCapabilities::hidl_discriminator::isdbtCaps + == caps.getDiscriminator()) { + jcaps = getIsdbtFrontendCaps(env, caps); + } break; default: break; @@ -2308,7 +2337,7 @@ static void android_media_tv_Tuner_native_init(JNIEnv *env) { gFields.dvrPlaybackContext = env->GetFieldID(dvrPlaybackClazz, "mNativeContext", "J"); gFields.dvrPlaybackInitID = env->GetMethodID(dvrPlaybackClazz, "<init>", "()V"); gFields.onDvrPlaybackStatusID = - env->GetMethodID(dvrRecorderClazz, "onPlaybackStatusChanged", "(I)V"); + env->GetMethodID(dvrPlaybackClazz, "onPlaybackStatusChanged", "(I)V"); jclass linearBlockClazz = env->FindClass("android/media/MediaCodec$LinearBlock"); gFields.linearBlockInitID = env->GetMethodID(linearBlockClazz, "<init>", "()V"); @@ -3101,6 +3130,11 @@ static jobject android_media_tv_Tuner_get_demux_caps(JNIEnv* env, jobject thiz) return tuner->getDemuxCaps(); } +static jint android_media_tv_Tuner_open_demux(JNIEnv* env, jobject thiz, jint /* handle */) { + sp<JTuner> tuner = getTuner(env, thiz); + return (jint) tuner->openDemux(); +} + static jint android_media_tv_Tuner_attach_filter(JNIEnv *env, jobject dvr, jobject filter) { sp<Dvr> dvrSp = getDvr(env, dvr); if (dvrSp == NULL) { @@ -3425,6 +3459,7 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_open_dvr_playback }, { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;", (void *)android_media_tv_Tuner_get_demux_caps }, + { "nativeOpenDemuxByhandle", "(I)I", (void *)android_media_tv_Tuner_open_demux }, }; static const JNINativeMethod gFilterMethods[] = { diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 73fc38dbdec8..6749ba085739 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -188,9 +188,9 @@ struct JTuner : public RefBase { jobject openDvr(DvrType type, jlong bufferSize); jobject getDemuxCaps(); jobject getFrontendStatus(jintArray types); + Result openDemux(); protected: - Result openDemux(); virtual ~JTuner(); private: @@ -204,15 +204,15 @@ private: sp<ILnb> mLnb; sp<IDemux> mDemux; uint32_t mDemuxId; - static jobject getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); - static jobject getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); - static jobject getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); - static jobject getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); - static jobject getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); - static jobject getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); - static jobject getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); - static jobject getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); - static jobject getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities caps); + static jobject getAnalogFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps); + static jobject getAtsc3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps); + static jobject getAtscFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps); + static jobject getDvbcFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps); + static jobject getDvbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps); + static jobject getDvbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps); + static jobject getIsdbs3FrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps); + static jobject getIsdbsFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps); + static jobject getIsdbtFrontendCaps(JNIEnv *env, FrontendInfo::FrontendCapabilities& caps); }; } // namespace android diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java index ff40d8e00603..450bdb161933 100644 --- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java +++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java @@ -202,6 +202,12 @@ public class SettingsInjector { } /** + * Gives descendants a chance to log Preference click event + */ + protected void logPreferenceClick(Intent intent) { + } + + /** * Returns the settings parsed from the attributes of the * {@link SettingInjectorService#META_DATA_NAME} tag, or null. * @@ -315,6 +321,7 @@ public class SettingsInjector { // Settings > Location. Intent settingIntent = new Intent(); settingIntent.setClassName(mInfo.packageName, mInfo.settingsActivity); + logPreferenceClick(settingIntent); // Sometimes the user may navigate back to "Settings" and launch another different // injected setting after one injected setting has been launched. // diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java index 9d7e2c821297..b1234f291b74 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java @@ -18,11 +18,15 @@ package com.android.settingslib.net; import android.content.Context; import android.net.NetworkTemplate; +import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.util.ArrayUtils; + +import java.util.List; + /** * Utils class for data usage */ @@ -33,26 +37,42 @@ public class DataUsageUtils { * Return mobile NetworkTemplate based on {@code subId} */ public static NetworkTemplate getMobileTemplate(Context context, int subId) { - final TelephonyManager telephonyManager = context.getSystemService( - TelephonyManager.class); - final SubscriptionManager subscriptionManager = context.getSystemService( - SubscriptionManager.class); - final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll( - telephonyManager.getSubscriberId()); - - if (!subscriptionManager.isActiveSubscriptionId(subId)) { - Log.i(TAG, "Subscription is not active: " + subId); - return mobileAll; + final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + final int mobileDefaultSubId = telephonyManager.getSubscriptionId(); + + final SubscriptionManager subscriptionManager = + context.getSystemService(SubscriptionManager.class); + final List<SubscriptionInfo> subInfoList = + subscriptionManager.getAvailableSubscriptionInfoList(); + if (subInfoList == null) { + Log.i(TAG, "Subscription is not inited: " + subId); + return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId); } - final String[] mergedSubscriberIds = telephonyManager.createForSubscriptionId(subId) - .getMergedImsisFromGroup(); + for (SubscriptionInfo subInfo : subInfoList) { + if ((subInfo != null) && (subInfo.getSubscriptionId() == subId)) { + return getNormalizedMobileTemplate(telephonyManager, subId); + } + } + Log.i(TAG, "Subscription is not active: " + subId); + return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId); + } + private static NetworkTemplate getNormalizedMobileTemplate( + TelephonyManager telephonyManager, int subId) { + final NetworkTemplate mobileTemplate = getMobileTemplateForSubId(telephonyManager, subId); + final String[] mergedSubscriberIds = telephonyManager + .createForSubscriptionId(subId).getMergedImsisFromGroup(); if (ArrayUtils.isEmpty(mergedSubscriberIds)) { Log.i(TAG, "mergedSubscriberIds is null."); - return mobileAll; + return mobileTemplate; } - return NetworkTemplate.normalize(mobileAll, mergedSubscriberIds); + return NetworkTemplate.normalize(mobileTemplate, mergedSubscriberIds); + } + + private static NetworkTemplate getMobileTemplateForSubId( + TelephonyManager telephonyManager, int subId) { + return NetworkTemplate.buildTemplateMobileAll(telephonyManager.getSubscriberId(subId)); } } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index c6f03271f931..133d375b8c6e 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -670,7 +670,7 @@ </activity> <activity android:name=".controls.management.ControlsProviderSelectorActivity" - android:label="Controls Providers" + android:label="@string/controls_providers_title" android:theme="@style/Theme.ControlsManagement" android:showForAllUsers="true" android:clearTaskOnLaunch="true" @@ -679,6 +679,15 @@ android:visibleToInstantApps="true"> </activity> + <activity android:name=".controls.management.ControlsEditingActivity" + android:theme="@style/Theme.ControlsManagement" + android:excludeFromRecents="true" + android:showForAllUsers="true" + android:finishOnTaskLaunch="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" + android:visibleToInstantApps="true"> + </activity> + <activity android:name=".controls.management.ControlsFavoritingActivity" android:theme="@style/Theme.ControlsManagement" android:excludeFromRecents="true" diff --git a/packages/SystemUI/res/anim/bottomsheet_in.xml b/packages/SystemUI/res/anim/bottomsheet_in.xml new file mode 100644 index 000000000000..0d5efeb2be1e --- /dev/null +++ b/packages/SystemUI/res/anim/bottomsheet_in.xml @@ -0,0 +1,26 @@ +<?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 + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@*android:anim/accelerate_decelerate_interpolator" + android:zAdjustment="top"> + + <translate android:fromYDelta="100%" + android:toYDelta="0" + android:startOffset="@android:integer/config_shortAnimTime" + android:duration="@*android:integer/config_mediumAnimTime"/> +</set> diff --git a/packages/SystemUI/res/anim/bottomsheet_out.xml b/packages/SystemUI/res/anim/bottomsheet_out.xml new file mode 100644 index 000000000000..01f8d2d6b2a3 --- /dev/null +++ b/packages/SystemUI/res/anim/bottomsheet_out.xml @@ -0,0 +1,25 @@ +<?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 + --> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@*android:anim/accelerate_interpolator" + android:zAdjustment="top"> + + <translate xmlns:android="http://schemas.android.com/apk/res/android" + android:fromYDelta="0" + android:toYDelta="100%" + android:duration="@*android:integer/config_shortAnimTime" /> +</set> diff --git a/packages/SystemUI/res/drawable/rounded_bg_top.xml b/packages/SystemUI/res/drawable/rounded_bg_top.xml new file mode 100644 index 000000000000..988ab5874b07 --- /dev/null +++ b/packages/SystemUI/res/drawable/rounded_bg_top.xml @@ -0,0 +1,22 @@ +<?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. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="?android:attr/colorPrimaryDark" /> + <corners + android:topLeftRadius="?android:attr/dialogCornerRadius" + android:topRightRadius="?android:attr/dialogCornerRadius" /> +</shape> diff --git a/packages/SystemUI/res/layout/app_ops_info.xml b/packages/SystemUI/res/layout/app_ops_info.xml index bfa252c5a14b..8342a2a4a560 100644 --- a/packages/SystemUI/res/layout/app_ops_info.xml +++ b/packages/SystemUI/res/layout/app_ops_info.xml @@ -19,8 +19,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:focusable="true" android:id="@+id/app_ops_info" - android:clickable="true" android:clipChildren="false" android:clipToPadding="false" android:orientation="vertical" diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index db81e2348cd8..55c9083e4147 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -45,7 +45,8 @@ android:textAppearance="@style/TextAppearance.Control.Status" android:paddingTop="@dimen/control_padding_adjustment" android:paddingStart="@dimen/control_status_padding" - android:clickable="true" + android:screenReaderFocusable="false" + android:clickable="false" android:focusable="false" android:singleLine="true" android:ellipsize="marquee" diff --git a/packages/SystemUI/res/layout/controls_detail_dialog.xml b/packages/SystemUI/res/layout/controls_detail_dialog.xml index f2de45a57ff6..34b603f4bc3d 100644 --- a/packages/SystemUI/res/layout/controls_detail_dialog.xml +++ b/packages/SystemUI/res/layout/controls_detail_dialog.xml @@ -15,9 +15,76 @@ limitations under the License. --> -<FrameLayout +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/controls_activity_view" + android:id="@+id/control_detail_root" android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="match_parent" + android:layout_marginTop="@dimen/controls_activity_view_top_offset" + android:orientation="vertical"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginBottom="10dp"> + <ImageView + android:id="@+id/control_detail_close" + android:contentDescription="@string/accessibility_desc_close" + android:src="@drawable/ic_close" + android:background="?android:attr/selectableItemBackgroundBorderless" + android:tint="@color/control_primary_text" + android:layout_width="48dp" + android:layout_height="48dp" + android:padding="12dp" /> + <Space + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="1dp" /> + <ImageView + android:id="@+id/control_detail_open_in_app" + android:src="@drawable/ic_open_in_new" + android:background="?android:attr/selectableItemBackgroundBorderless" + android:tint="@color/control_primary_text" + android:layout_width="48dp" + android:layout_height="48dp" + android:padding="12dp" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/controls_activity_view_top_padding" + android:paddingLeft="@dimen/controls_activity_view_side_padding" + android:paddingRight="@dimen/controls_activity_view_side_padding" + android:background="@drawable/rounded_bg_top" + android:orientation="vertical"> + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.ControlDialog" + android:clickable="false" + android:focusable="false" + android:maxLines="1" + android:ellipsize="end" /> + <TextView + android:id="@+id/subtitle" + android:layout_marginTop="6dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.ControlDialog" + android:clickable="false" + android:focusable="false" + android:maxLines="1" + android:ellipsize="end" /> + + <FrameLayout + android:id="@+id/controls_activity_view" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginTop="10dp" + android:layout_weight="1" /> + + </LinearLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/controls_horizontal_divider_withEmpty.xml b/packages/SystemUI/res/layout/controls_horizontal_divider_withEmpty.xml new file mode 100644 index 000000000000..90b3398e3de2 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_horizontal_divider_withEmpty.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" +> + + <View + android:layout_width="match_parent" + android:layout_height="@dimen/controls_management_list_margin" + /> + + <FrameLayout + android:id="@+id/frame" + android:layout_width="match_parent" + android:layout_height="@dimen/control_height" + android:visibility="gone" + > + </FrameLayout> + <View + android:id="@+id/divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginBottom="10dp" + android:layout_marginStart="40dp" + android:layout_marginEnd="40dp" + android:background="#4dffffff" /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml index 9d5eb63ba795..ae57563cfb09 100644 --- a/packages/SystemUI/res/layout/controls_management.xml +++ b/packages/SystemUI/res/layout/controls_management.xml @@ -29,6 +29,8 @@ android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:focusable="false" + android:clickable="false" android:gravity="center_vertical"> <FrameLayout diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml index 42d73f3cc9ce..94df9d8f4775 100644 --- a/packages/SystemUI/res/layout/controls_management_apps.xml +++ b/packages/SystemUI/res/layout/controls_management_apps.xml @@ -14,18 +14,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<androidx.core.widget.NestedScrollView +<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list" android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" - android:orientation="vertical" - android:layout_marginTop="@dimen/controls_management_list_margin"> + android:layout_height="match_parent" + android:layout_marginTop="@dimen/controls_management_list_margin" +/> - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/list" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - -</androidx.core.widget.NestedScrollView> diff --git a/packages/SystemUI/res/layout/controls_management_editing.xml b/packages/SystemUI/res/layout/controls_management_editing.xml new file mode 100644 index 000000000000..8a14ec3666b2 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management_editing.xml @@ -0,0 +1,27 @@ +<?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. + --> + +<androidx.recyclerview.widget.RecyclerView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false" + android:paddingTop="@dimen/controls_management_list_margin" +/> + diff --git a/packages/SystemUI/res/layout/controls_spinner_item.xml b/packages/SystemUI/res/layout/controls_spinner_item.xml index 00654c832525..45540f102de1 100644 --- a/packages/SystemUI/res/layout/controls_spinner_item.xml +++ b/packages/SystemUI/res/layout/controls_spinner_item.xml @@ -30,6 +30,7 @@ android:layout_gravity="center" android:layout_width="@dimen/controls_header_app_icon_size" android:layout_height="@dimen/controls_header_app_icon_size" + android:contentDescription="@null" android:layout_marginEnd="10dp" /> <TextView diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index 623f2a0d3423..b32320956e26 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -34,6 +34,7 @@ android:orientation="horizontal" android:layout_width="0dp" android:layout_weight="1" + android:minHeight="48dp" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center"> @@ -43,6 +44,7 @@ android:layout_gravity="center" android:layout_width="@dimen/controls_header_app_icon_size" android:layout_height="@dimen/controls_header_app_icon_size" + android:contentDescription="@null" android:layout_marginEnd="10dp" /> <TextView diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml index 87cb5c7f746c..9dc502efab43 100644 --- a/packages/SystemUI/res/layout/notification_conversation_info.xml +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -20,7 +20,7 @@ android:id="@+id/notification_guts" android:layout_width="match_parent" android:layout_height="wrap_content" - android:clickable="true" + android:focusable="true" android:clipChildren="false" android:clipToPadding="true" android:orientation="vertical" diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml index dc94697f32fa..5399f57c322f 100644 --- a/packages/SystemUI/res/layout/notification_guts.xml +++ b/packages/SystemUI/res/layout/notification_guts.xml @@ -19,8 +19,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:focusable="true" android:id="@+id/notification_guts" android:visibility="gone" - android:clickable="true" android:gravity="top|start" android:theme="@*android:style/Theme.DeviceDefault.Light"/> diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 73b711d275f3..5b363820e4e2 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -20,7 +20,7 @@ android:id="@+id/notification_guts" android:layout_width="match_parent" android:layout_height="wrap_content" - android:clickable="true" + android:focusable="true" android:clipChildren="false" android:clipToPadding="true" android:orientation="vertical" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 6a8a4b9ac2fd..2a4d5ef921f7 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1250,6 +1250,12 @@ <dimen name="control_base_item_margin">2dp</dimen> <dimen name="control_status_padding">3dp</dimen> + <!-- Home Controls activity view detail panel--> + <dimen name="controls_activity_view_top_padding">25dp</dimen> + <dimen name="controls_activity_view_side_padding">12dp</dimen> + <dimen name="controls_activity_view_top_offset">200dp</dimen> + <dimen name="controls_activity_view_text_size">17sp</dimen> + <!-- Home Controls management screens --> <dimen name="controls_management_top_padding">48dp</dimen> <dimen name="controls_management_side_padding">8dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 566d143208fc..7c0b6054dddb 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2670,8 +2670,11 @@ <string name="controls_favorite_default_title">Controls</string> <!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] --> <string name="controls_favorite_subtitle">Choose controls to access from the power menu</string> - <!-- Controls management controls screen, user direction for rearranging controls [CHAR LIMIT=NONE] --> - <string name="controls_favorite_rearrange">Hold and drag a control to move it</string> + <!-- Controls management editing screen, user direction for rearranging controls [CHAR LIMIT=NONE] --> + <string name="controls_favorite_rearrange">Hold & drag to rearrange controls</string> + + <!-- Controls management editing screen, text to indicate that all the favorites have been removed [CHAR LIMIT=NONE] --> + <string name="controls_favorite_removed">All controls removed</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> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 3e02b30c376b..118aa5b3f96a 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -671,6 +671,19 @@ <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> </style> + <style name="Theme.SystemUI.Dialog.Control.DetailPanel" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar"> + <item name="android:windowAnimationStyle">@style/Animation.Bottomsheet</item> + <item name="android:windowFullscreen">true</item> + <item name="android:windowIsFloating">false</item> + <item name="android:windowBackground">@null</item> + <item name="android:backgroundDimEnabled">true</item> + </style> + + <style name="Animation.Bottomsheet"> + <item name="android:windowEnterAnimation">@anim/bottomsheet_in</item> + <item name="android:windowExitAnimation">@anim/bottomsheet_out</item> + </style> + <style name="Control" /> <style name="Control.MenuItem"> @@ -713,6 +726,11 @@ <item name="android:textSize">@dimen/control_text_size</item> <item name="android:textColor">@color/control_secondary_text</item> </style> + <style name="TextAppearance.ControlDialog"> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + <item name="android:textSize">@dimen/controls_activity_view_text_size</item> + <item name="android:textColor">@color/control_primary_text</item> + </style> <style name="Control.ListPopupWindow" parent="@*android:style/Widget.DeviceDefault.ListPopupWindow"> <item name="android:overlapAnchor">true</item> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 3bda3c8df699..806678f23bb3 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -261,6 +261,11 @@ public class ActivityManagerWrapper { animationHandler.onAnimationCanceled( taskSnapshot != null ? new ThumbnailData(taskSnapshot) : null); } + + @Override + public void onTaskAppeared(RemoteAnimationTarget app) { + animationHandler.onTaskAppeared(new RemoteAnimationTargetCompat(app)); + } }; } ActivityTaskManager.getService().startRecentsActivity(intent, receiver, runner); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index bbb83c73446c..76513c6ff3d5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -109,4 +109,16 @@ public class RecentsAnimationControllerCompat { Log.e(TAG, "Failed to set overview reached state", e); } } + + /** + * @see IRecentsAnimationController#removeTask + */ + public boolean removeTask(int taskId) { + try { + return mAnimationController.removeTask(taskId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to remove remote animation target", e); + return false; + } + } }
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java index 2c99c5c84015..c4cd192212a0 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -32,4 +32,10 @@ public interface RecentsAnimationListener { * Called when the animation into Recents was canceled. This call is made on the binder thread. */ void onAnimationCanceled(ThumbnailData thumbnailData); + + /** + * Called when the task of an activity that has been started while the recents animation + * was running becomes ready for control. + */ + void onTaskAppeared(RemoteAnimationTargetCompat app); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 61e6f39c054f..71dbbbc9da50 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -1349,7 +1349,8 @@ public class BubbleStackView extends FrameLayout { if (show && mShouldShowManageEducation && mManageEducationView.getVisibility() != VISIBLE - && mIsExpanded) { + && mIsExpanded + && mExpandedBubble.getExpandedView() != null) { mManageEducationView.setAlpha(0); mManageEducationView.setVisibility(VISIBLE); mManageEducationView.post(() -> { @@ -1909,7 +1910,8 @@ public class BubbleStackView extends FrameLayout { Log.d(TAG, "updateExpandedBubble()"); } mExpandedViewContainer.removeAllViews(); - if (mIsExpanded && mExpandedBubble != null) { + if (mIsExpanded && mExpandedBubble != null + && mExpandedBubble.getExpandedView() != null) { BubbleExpandedView bev = mExpandedBubble.getExpandedView(); mExpandedViewContainer.addView(bev); bev.populateExpandedView(); @@ -1929,7 +1931,7 @@ public class BubbleStackView extends FrameLayout { if (!mExpandedViewYAnim.isRunning()) { // We're not animating so set the value mExpandedViewContainer.setTranslationY(y); - if (mExpandedBubble != null) { + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { mExpandedBubble.getExpandedView().updateView(); } } else { @@ -1967,7 +1969,7 @@ public class BubbleStackView extends FrameLayout { } private void updatePointerPosition() { - if (mExpandedBubble == null) { + if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { return; } int index = getBubbleIndex(mExpandedBubble); @@ -2049,7 +2051,7 @@ public class BubbleStackView extends FrameLayout { * a back key down/up event pair is forwarded to the bubble Activity. */ boolean performBackPressIfNeeded() { - if (!isExpanded() || mExpandedBubble == null) { + if (!isExpanded() || mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { return false; } return mExpandedBubble.getExpandedView().performBackPressIfNeeded(); diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt index dec60073a55e..5891a7f705c8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt @@ -18,10 +18,34 @@ package com.android.systemui.controls import android.content.ComponentName import android.service.controls.Control +import android.service.controls.DeviceTypes + +interface ControlInterface { + val favorite: Boolean + val component: ComponentName + val controlId: String + val title: CharSequence + val subtitle: CharSequence + val removed: Boolean + get() = false + @DeviceTypes.DeviceType val deviceType: Int +} data class ControlStatus( val control: Control, - val component: ComponentName, - var favorite: Boolean, - val removed: Boolean = false -) + override val component: ComponentName, + override var favorite: Boolean, + override val removed: Boolean = false +) : ControlInterface { + override val controlId: String + get() = control.controlId + + override val title: CharSequence + get() = control.title + + override val subtitle: CharSequence + get() = control.subtitle + + @DeviceTypes.DeviceType override val deviceType: Int + get() = control.deviceType +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt index 6e59ac162657..40606c2689e5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt @@ -16,6 +16,7 @@ package com.android.systemui.controls.controller +import android.service.controls.Control import android.service.controls.DeviceTypes /** @@ -39,6 +40,14 @@ data class ControlInfo( companion object { private const val SEPARATOR = ":" + fun fromControl(control: Control): ControlInfo { + return ControlInfo( + control.controlId, + control.title, + control.subtitle, + control.deviceType + ) + } } /** @@ -49,13 +58,4 @@ data class ControlInfo( override fun toString(): String { return "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType" } - - class Builder { - lateinit var controlId: String - lateinit var controlTitle: CharSequence - lateinit var controlSubtitle: CharSequence - var deviceType: Int = DeviceTypes.TYPE_UNKNOWN - - fun build() = ControlInfo(controlId, controlTitle, controlSubtitle, deviceType) - } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index 568fb289027d..7cab847d52f7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -148,6 +148,18 @@ interface ControlsController : UserAwareController { fun getFavoritesForComponent(componentName: ComponentName): List<StructureInfo> /** + * Get all the favorites for a given structure. + * + * @param componentName the name of the service that provides the [Control] + * @param structureName the name of the structure + * @return a list of the current favorites in that structure + */ + fun getFavoritesForStructure( + componentName: ComponentName, + structureName: CharSequence + ): List<ControlInfo> + + /** * Adds a single favorite to a given component and structure * @param componentName the name of the service that provides the [Control] * @param structureName the name of the structure that holds the [Control] 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 8805694616a4..6d34009169d5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -544,6 +544,15 @@ class ControlsControllerImpl @Inject constructor ( override fun getFavoritesForComponent(componentName: ComponentName): List<StructureInfo> = Favorites.getStructuresForComponent(componentName) + override fun getFavoritesForStructure( + componentName: ComponentName, + structureName: CharSequence + ): List<ControlInfo> { + return Favorites.getControlsForStructure( + StructureInfo(componentName, structureName, emptyList()) + ) + } + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("ControlsController state:") pw.println(" Available: $available") diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt index 946a2365585a..3bed55912332 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt @@ -22,6 +22,7 @@ import com.android.systemui.controls.controller.ControlsBindingControllerImpl import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper +import com.android.systemui.controls.management.ControlsEditingActivity import com.android.systemui.controls.management.ControlsFavoritingActivity import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsListingControllerImpl @@ -73,6 +74,13 @@ abstract class ControlsModule { @Binds @IntoMap + @ClassKey(ControlsEditingActivity::class) + abstract fun provideControlsEditingActivity( + activity: ControlsEditingActivity + ): Activity + + @Binds + @IntoMap @ClassKey(ControlsRequestDialog::class) abstract fun provideControlsRequestDialog( activity: ControlsRequestDialog diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt index 11181e56838e..175ed061c714 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt @@ -37,23 +37,22 @@ import com.android.systemui.controls.controller.ControlInfo * @property controls List of controls as returned by loading * @property initialFavoriteIds sorted ids of favorite controls. * @property noZoneString text to use as header for all controls that have blank or `null` zone. + * @property controlsModelCallback callback to notify that favorites have changed for the first time */ class AllModel( private val controls: List<ControlStatus>, initialFavoriteIds: List<String>, - private val emptyZoneString: CharSequence + private val emptyZoneString: CharSequence, + private val controlsModelCallback: ControlsModel.ControlsModelCallback ) : ControlsModel { - override val favorites: List<ControlInfo.Builder> + private var modified = false + + override val favorites: List<ControlInfo> get() = favoriteIds.mapNotNull { id -> val control = controls.firstOrNull { it.control.controlId == id }?.control control?.let { - ControlInfo.Builder().apply { - controlId = it.controlId - controlTitle = it.title - controlSubtitle = it.subtitle - deviceType = it.deviceType - } + ControlInfo.fromControl(it) } } @@ -66,14 +65,18 @@ class AllModel( override fun changeFavoriteStatus(controlId: String, favorite: Boolean) { val toChange = elements.firstOrNull { - it is ControlWrapper && it.controlStatus.control.controlId == controlId - } as ControlWrapper? + it is ControlStatusWrapper && it.controlStatus.control.controlId == controlId + } as ControlStatusWrapper? if (favorite == toChange?.controlStatus?.favorite) return - if (favorite) { + val changed: Boolean = if (favorite) { favoriteIds.add(controlId) } else { favoriteIds.remove(controlId) } + if (changed && !modified) { + modified = true + controlsModelCallback.onFirstChange() + } toChange?.let { it.controlStatus.favorite = favorite } @@ -84,9 +87,9 @@ class AllModel( it.control.zone ?: "" } val output = mutableListOf<ElementWrapper>() - var emptyZoneValues: Sequence<ControlWrapper>? = null + var emptyZoneValues: Sequence<ControlStatusWrapper>? = null for (zoneName in map.orderedKeys) { - val values = map.getValue(zoneName).asSequence().map { ControlWrapper(it) } + val values = map.getValue(zoneName).asSequence().map { ControlStatusWrapper(it) } if (TextUtils.isEmpty(zoneName)) { emptyZoneValues = values } else { 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 1291dd98932e..607934c3bae7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -28,6 +28,7 @@ import android.widget.TextView import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R +import com.android.systemui.controls.ControlInterface import com.android.systemui.controls.ui.RenderInfo private typealias ModelFavoriteChanger = (String, Boolean) -> Unit @@ -35,11 +36,10 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit /** * Adapter for binding [Control] information to views. * - * The model for this adapter is provided by a [FavoriteModel] that is set using + * The model for this adapter is provided by a [ControlModel] that is set using * [changeFavoritesModel]. This allows for updating the model if there's a reload. * - * @param layoutInflater an inflater for the views in the containing [RecyclerView] - * @param onlyFavorites set to true to only display favorites instead of all controls + * @property elevation elevation of each control view */ class ControlAdapter( private val elevation: Float @@ -48,11 +48,12 @@ class ControlAdapter( companion object { private const val TYPE_ZONE = 0 private const val TYPE_CONTROL = 1 + private const val TYPE_DIVIDER = 2 } val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { - return if (getItemViewType(position) == TYPE_ZONE) 2 else 1 + return if (getItemViewType(position) != TYPE_CONTROL) 2 else 1 } } @@ -78,6 +79,10 @@ class ControlAdapter( TYPE_ZONE -> { ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false)) } + TYPE_DIVIDER -> { + DividerHolder(layoutInflater.inflate( + R.layout.controls_horizontal_divider_withEmpty, parent, false)) + } else -> throw IllegalStateException("Wrong viewType: $viewType") } } @@ -95,11 +100,26 @@ class ControlAdapter( } } + override fun onBindViewHolder(holder: Holder, position: Int, payloads: MutableList<Any>) { + if (payloads.isEmpty()) { + super.onBindViewHolder(holder, position, payloads) + } else { + model?.let { + val el = it.elements[position] + if (el is ControlInterface) { + holder.updateFavorite(el.favorite) + } + } + } + } + override fun getItemViewType(position: Int): Int { model?.let { return when (it.elements.get(position)) { is ZoneNameWrapper -> TYPE_ZONE - is ControlWrapper -> TYPE_CONTROL + is ControlStatusWrapper -> TYPE_CONTROL + is ControlInfoWrapper -> TYPE_CONTROL + is DividerWrapper -> TYPE_DIVIDER } } ?: throw IllegalStateException("Getting item type for null model") } @@ -115,6 +135,24 @@ sealed class Holder(view: View) : RecyclerView.ViewHolder(view) { * Bind the data from the model into the view */ abstract fun bindData(wrapper: ElementWrapper) + + open fun updateFavorite(favorite: Boolean) {} +} + +/** + * Holder for using with [DividerWrapper] to display a divider between zones. + * + * The divider can be shown or hidden. It also has a frame view the height of a control, that can + * be toggled visible or gone. + */ +private class DividerHolder(view: View) : Holder(view) { + private val frame: View = itemView.requireViewById(R.id.frame) + private val divider: View = itemView.requireViewById(R.id.divider) + override fun bindData(wrapper: ElementWrapper) { + wrapper as DividerWrapper + frame.visibility = if (wrapper.showNone) View.VISIBLE else View.GONE + divider.visibility = if (wrapper.showDivider) View.VISIBLE else View.GONE + } } /** @@ -130,11 +168,14 @@ private class ZoneHolder(view: View) : Holder(view) { } /** - * Holder for using with [ControlWrapper] to display names of zones. + * Holder for using with [ControlStatusWrapper] to display names of zones. * @param favoriteCallback this callback will be called whenever the favorite state of the * [Control] this view represents changes. */ -private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChanger) : Holder(view) { +internal class ControlHolder( + view: View, + val favoriteCallback: ModelFavoriteChanger +) : Holder(view) { private val icon: ImageView = itemView.requireViewById(R.id.icon) private val title: TextView = itemView.requireViewById(R.id.title) private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) @@ -144,20 +185,23 @@ private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChang } override fun bindData(wrapper: ElementWrapper) { - wrapper as ControlWrapper - val data = wrapper.controlStatus - val renderInfo = getRenderInfo(data.component, data.control.deviceType) - title.text = data.control.title - subtitle.text = data.control.subtitle - favorite.isChecked = data.favorite - removed.text = if (data.removed) "Removed" else "" + wrapper as ControlInterface + val renderInfo = getRenderInfo(wrapper.component, wrapper.deviceType) + title.text = wrapper.title + subtitle.text = wrapper.subtitle + favorite.isChecked = wrapper.favorite + removed.text = if (wrapper.removed) "Removed" else "" itemView.setOnClickListener { favorite.isChecked = !favorite.isChecked - favoriteCallback(data.control.controlId, favorite.isChecked) + favoriteCallback(wrapper.controlId, favorite.isChecked) } applyRenderInfo(renderInfo) } + override fun updateFavorite(favorite: Boolean) { + this.favorite.isChecked = favorite + } + private fun getRenderInfo( component: ComponentName, @DeviceTypes.DeviceType deviceType: Int diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt new file mode 100644 index 000000000000..ee1ce7ab3d83 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -0,0 +1,176 @@ +/* + * 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.controls.management + +import android.app.Activity +import android.content.ComponentName +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.view.ViewStub +import android.widget.Button +import android.widget.TextView +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.R +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.controls.controller.ControlsControllerImpl +import com.android.systemui.controls.controller.StructureInfo +import com.android.systemui.settings.CurrentUserTracker +import javax.inject.Inject + +/** + * Activity for rearranging and removing controls for a given structure + */ +class ControlsEditingActivity @Inject constructor( + private val controller: ControlsControllerImpl, + broadcastDispatcher: BroadcastDispatcher +) : Activity() { + + companion object { + private const val TAG = "ControlsEditingActivity" + private const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE + private val SUBTITLE_ID = R.string.controls_favorite_rearrange + private val EMPTY_TEXT_ID = R.string.controls_favorite_removed + } + + private lateinit var component: ComponentName + private lateinit var structure: CharSequence + private lateinit var model: FavoritesModel + private lateinit var subtitle: TextView + private lateinit var saveButton: View + + private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { + private val startingUser = controller.currentUserId + + override fun onUserSwitched(newUserId: Int) { + if (newUserId != startingUser) { + stopTracking() + finish() + } + } + } + + override fun onBackPressed() { + finish() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)?.let { + component = it + } ?: run(this::finish) + + intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let { + structure = it + } ?: run(this::finish) + + bindViews() + + bindButtons() + + setUpList() + + currentUserTracker.startTracking() + } + + private fun bindViews() { + setContentView(R.layout.controls_management) + requireViewById<ViewStub>(R.id.stub).apply { + layoutResource = R.layout.controls_management_editing + inflate() + } + requireViewById<TextView>(R.id.title).text = structure + subtitle = requireViewById<TextView>(R.id.subtitle).apply { + setText(SUBTITLE_ID) + } + } + + private fun bindButtons() { + requireViewById<Button>(R.id.other_apps).apply { + visibility = View.VISIBLE + setText(R.string.controls_menu_add) + setOnClickListener { + saveFavorites() + val intent = Intent(this@ControlsEditingActivity, + ControlsFavoritingActivity::class.java).apply { + putExtras(this@ControlsEditingActivity.intent) + putExtra(ControlsFavoritingActivity.EXTRA_SINGLE_STRUCTURE, true) + } + startActivity(intent) + finish() + } + } + + saveButton = requireViewById<Button>(R.id.done).apply { + isEnabled = false + setText(R.string.save) + setOnClickListener { + saveFavorites() + finishAffinity() + } + } + } + + private fun saveFavorites() { + controller.replaceFavoritesForStructure( + StructureInfo(component, structure, model.favorites)) + } + + private val favoritesModelCallback = object : FavoritesModel.FavoritesModelCallback { + override fun onNoneChanged(showNoFavorites: Boolean) { + if (showNoFavorites) { + subtitle.setText(EMPTY_TEXT_ID) + } else { + subtitle.setText(SUBTITLE_ID) + } + } + + override fun onFirstChange() { + saveButton.isEnabled = true + } + } + + private fun setUpList() { + val controls = controller.getFavoritesForStructure(component, structure) + model = FavoritesModel(component, controls, favoritesModelCallback) + val elevation = resources.getFloat(R.dimen.control_card_elevation) + val adapter = ControlAdapter(elevation) + val recycler = requireViewById<RecyclerView>(R.id.list) + val margin = resources + .getDimensionPixelSize(R.dimen.controls_card_margin) + val itemDecorator = MarginItemDecorator(margin, margin) + + recycler.apply { + this.adapter = adapter + layoutManager = GridLayoutManager(recycler.context, 2).apply { + spanSizeLookup = adapter.spanSizeLookup + } + addItemDecoration(itemDecorator) + } + adapter.changeModel(model) + model.attachAdapter(adapter) + ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recycler) + } + + override fun onDestroy() { + currentUserTracker.stopTracking() + super.onDestroy() + } +}
\ No newline at end of file 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 fe1e6328820d..6f34deeb8547 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -61,6 +61,7 @@ class ControlsFavoritingActivity @Inject constructor( // If provided, show this structure page first const val EXTRA_STRUCTURE = "extra_structure" + const val EXTRA_SINGLE_STRUCTURE = "extra_single_structure" private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT private const val TOOLTIP_MAX_SHOWN = 2 } @@ -131,6 +132,12 @@ class ControlsFavoritingActivity @Inject constructor( currentUserTracker.startTracking() } + private val controlsModelCallback = object : ControlsModel.ControlsModelCallback { + override fun onFirstChange() { + doneButton.isEnabled = true + } + } + private fun loadControls() { component?.let { statusText.text = resources.getText(com.android.internal.R.string.loading) @@ -142,15 +149,20 @@ class ControlsFavoritingActivity @Inject constructor( val error = data.errorOnLoad val controlsByStructure = allControls.groupBy { it.control.structure ?: "" } listOfStructures = controlsByStructure.map { - StructureContainer(it.key, AllModel(it.value, favoriteKeys, emptyZoneString)) + StructureContainer(it.key, AllModel( + it.value, favoriteKeys, emptyZoneString, controlsModelCallback)) }.sortedWith(comparator) val structureIndex = listOfStructures.indexOfFirst { sc -> sc.structureName == structureExtra }.let { if (it == -1) 0 else it } + // If we were requested to show a single structure, set the list to just that one + if (intent.getBooleanExtra(EXTRA_SINGLE_STRUCTURE, false)) { + listOfStructures = listOf(listOfStructures[structureIndex]) + } + executor.execute { - doneButton.isEnabled = true structurePager.adapter = StructureAdapter(listOfStructures) structurePager.setCurrentItem(structureIndex) if (error) { @@ -239,8 +251,11 @@ class ControlsFavoritingActivity @Inject constructor( } } + val title = structureExtra + ?: (appName ?: resources.getText(R.string.controls_favorite_default_title)) + setTitle(title) titleView = requireViewById<TextView>(R.id.title).apply { - text = appName ?: resources.getText(R.string.controls_favorite_default_title) + text = title } requireViewById<TextView>(R.id.subtitle).text = resources.getText(R.string.controls_favorite_subtitle) @@ -272,7 +287,7 @@ class ControlsFavoritingActivity @Inject constructor( setOnClickListener { if (component == null) return@setOnClickListener listOfStructures.forEach { - val favoritesForStorage = it.model.favorites.map { it.build() } + val favoritesForStorage = it.model.favorites controller.replaceFavoritesForStructure( StructureInfo(component!!, it.structureName, favoritesForStorage) ) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt index a995a2ebfd25..37b6d15c0afe 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt @@ -16,6 +16,9 @@ package com.android.systemui.controls.management +import android.content.ComponentName +import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.controls.ControlInterface import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.controller.ControlInfo @@ -27,12 +30,12 @@ import com.android.systemui.controls.controller.ControlInfo interface ControlsModel { /** - * List of favorites (builders) in order. + * List of favorites in order. * * This should be obtained prior to storing the favorites using * [ControlsController.replaceFavoritesForComponent]. */ - val favorites: List<ControlInfo.Builder> + val favorites: List<ControlInfo> /** * List of all the elements to display by the corresponding [RecyclerView]. @@ -48,6 +51,24 @@ interface ControlsModel { * Move an item (in elements) from one position to another. */ fun onMoveItem(from: Int, to: Int) {} + + /** + * Attach an adapter to the model. + * + * This can be used to notify the adapter of changes in the model. + */ + fun attachAdapter(adapter: RecyclerView.Adapter<*>) {} + + /** + * Callback to notify elements (other than the adapter) of relevant changes in the model. + */ + interface ControlsModelCallback { + + /** + * Use to notify that the model has changed for the first time + */ + fun onFirstChange() + } } /** @@ -55,5 +76,29 @@ interface ControlsModel { * [ControlAdapter]. */ sealed class ElementWrapper + data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper() -data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper()
\ No newline at end of file + +data class ControlStatusWrapper( + val controlStatus: ControlStatus +) : ElementWrapper(), ControlInterface by controlStatus + +data class ControlInfoWrapper( + override val component: ComponentName, + val controlInfo: ControlInfo, + override var favorite: Boolean +) : ElementWrapper(), ControlInterface { + override val controlId: String + get() = controlInfo.controlId + override val title: CharSequence + get() = controlInfo.controlTitle + override val subtitle: CharSequence + get() = controlInfo.controlSubtitle + override val deviceType: Int + get() = controlInfo.deviceType +} + +data class DividerWrapper( + var showNone: Boolean = false, + var showDivider: Boolean = false +) : ElementWrapper()
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 0c41f7e5df5a..3be59009f531 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -64,6 +64,7 @@ class ControlsProviderSelectorActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.controls_management) requireViewById<ViewStub>(R.id.stub).apply { layoutResource = R.layout.controls_management_apps diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt deleted file mode 100644 index 5c51e3dbe4ac..000000000000 --- a/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.controls.management - -import android.text.TextUtils -import android.util.Log -import com.android.systemui.controls.ControlStatus -import java.util.Collections -import java.util.Comparator - -/** - * Model for keeping track of current favorites and their order. - * - * This model is to be used with two [ControlAdapter] one that shows only favorites in the current - * order and another that shows all controls, separated by zone. When the favorite state of any - * control is modified or when the favorites are reordered, the adapters are notified of the change. - * - * @param listControls list of all the [ControlStatus] to display. This includes controls currently - * marked as favorites as well as those that have been removed (not returned - * from load) - * @param listFavoritesIds list of the [Control.controlId] for all the favorites, including those - * that have been removed. - * @param favoritesAdapter [ControlAdapter] used by the [RecyclerView] that shows only favorites - * @param allAdapter [ControlAdapter] used by the [RecyclerView] that shows all controls - */ -class FavoriteModel( - private val listControls: List<ControlStatus>, - listFavoritesIds: List<String>, - private val favoritesAdapter: ControlAdapter, - private val allAdapter: ControlAdapter -) { - - companion object { - private const val TAG = "FavoriteModel" - } - - /** - * List of favorite controls ([ControlWrapper]) in order. - * - * Initially, this list will give a list of wrappers in the order specified by the constructor - * variable `listFavoriteIds`. - * - * As the favorites are added, removed or moved, this list will keep track of those changes. - */ - val favorites: List<ControlWrapper> = listFavoritesIds.map { id -> - ControlWrapper(listControls.first { it.control.controlId == id }) - }.toMutableList() - - /** - * List of all controls by zones. - * - * Lists all the controls with the zone names interleaved as a flat list. After each zone name, - * the controls in that zone are listed. Zones are listed in alphabetical order - */ - val all: List<ElementWrapper> = listControls.groupBy { it.control.zone } - .mapKeys { it.key ?: "" } // map null to empty - .toSortedMap(CharSequenceComparator()) - .flatMap { - val controls = it.value.map { ControlWrapper(it) } - if (!TextUtils.isEmpty(it.key)) { - listOf(ZoneNameWrapper(it.key)) + controls - } else { - controls - } - } - - /** - * Change the favorite status of a [Control]. - * - * This can be invoked from any of the [ControlAdapter]. It will change the status of that - * control and either add it to the list of favorites (at the end) or remove it from it. - * - * Removing the favorite status from a Removed control will make it disappear completely if - * changes are saved. - * - * @param controlId the id of the [Control] to change the status - * @param favorite `true` if and only if it's set to be a favorite. - */ - fun changeFavoriteStatus(controlId: String, favorite: Boolean) { - favorites as MutableList - val index = all.indexOfFirst { - it is ControlWrapper && it.controlStatus.control.controlId == controlId - } - val control = (all[index] as ControlWrapper).controlStatus - if (control.favorite == favorite) { - Log.d(TAG, "Changing favorite to same state for ${control.control.controlId} ") - return - } else { - control.favorite = favorite - } - allAdapter.notifyItemChanged(index) - if (favorite) { - favorites.add(all[index] as ControlWrapper) - favoritesAdapter.notifyItemInserted(favorites.size - 1) - } else { - val i = favorites.indexOfFirst { it.controlStatus.control.controlId == controlId } - favorites.removeAt(i) - favoritesAdapter.notifyItemRemoved(i) - } - } - - /** - * Move items in the model and notify the [favoritesAdapter]. - */ - fun onMoveItem(from: Int, to: Int) { - if (from < to) { - for (i in from until to) { - Collections.swap(favorites, i, i + 1) - } - } else { - for (i in from downTo to + 1) { - Collections.swap(favorites, i, i - 1) - } - } - favoritesAdapter.notifyItemMoved(from, to) - } -} - -/** - * Compares [CharSequence] as [String]. - * - * It will have empty strings as the first element - */ -class CharSequenceComparator : Comparator<CharSequence> { - override fun compare(p0: CharSequence?, p1: CharSequence?): Int { - if (p0 == null && p1 == null) return 0 - else if (p0 == null && p1 != null) return -1 - else if (p0 != null && p1 == null) return 1 - return p0.toString().compareTo(p1.toString()) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt new file mode 100644 index 000000000000..411170cb322c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt @@ -0,0 +1,221 @@ +/* + * 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.controls.management + +import android.content.ComponentName +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.controls.ControlInterface +import com.android.systemui.controls.controller.ControlInfo +import java.util.Collections + +/** + * Model used to show and rearrange favorites. + * + * The model will show all the favorite controls and a divider that can be toggled visible/gone. + * It will place the items selected as favorites before the divider and the ones unselected after. + * + * @property componentName used by the [ControlAdapter] to retrieve resources. + * @property favorites list of current favorites + * @property favoritesModelCallback callback to notify on first change and empty favorites + */ +class FavoritesModel( + private val componentName: ComponentName, + favorites: List<ControlInfo>, + private val favoritesModelCallback: FavoritesModelCallback +) : ControlsModel { + + private var adapter: RecyclerView.Adapter<*>? = null + private var modified = false + + override fun attachAdapter(adapter: RecyclerView.Adapter<*>) { + this.adapter = adapter + } + + override val favorites: List<ControlInfo> + get() = elements.take(dividerPosition).map { + (it as ControlInfoWrapper).controlInfo + } + + override val elements: List<ElementWrapper> = favorites.map { + ControlInfoWrapper(componentName, it, true) + } + DividerWrapper() + + /** + * Indicates the position of the divider to determine + */ + private var dividerPosition = elements.size - 1 + + override fun changeFavoriteStatus(controlId: String, favorite: Boolean) { + val position = elements.indexOfFirst { it is ControlInterface && it.controlId == controlId } + if (position == -1) { + return // controlId not found + } + if (position < dividerPosition && favorite || position > dividerPosition && !favorite) { + return // Does not change favorite status + } + if (favorite) { + onMoveItemInternal(position, dividerPosition) + } else { + onMoveItemInternal(position, elements.size - 1) + } + } + + override fun onMoveItem(from: Int, to: Int) { + onMoveItemInternal(from, to) + } + + private fun updateDividerNone(oldDividerPosition: Int, show: Boolean) { + (elements[oldDividerPosition] as DividerWrapper).showNone = show + favoritesModelCallback.onNoneChanged(show) + } + + private fun updateDividerShow(oldDividerPosition: Int, show: Boolean) { + (elements[oldDividerPosition] as DividerWrapper).showDivider = show + } + + /** + * Performs the update in the model. + * + * * update the favorite field of the [ControlInterface] + * * update the fields of the [DividerWrapper] + * * move the corresponding element in [elements] + * + * It may emit the following signals: + * * [RecyclerView.Adapter.notifyItemChanged] if a [ControlInterface.favorite] has changed + * (in the new position) or if the information in [DividerWrapper] has changed (in the + * old position). + * * [RecyclerView.Adapter.notifyItemMoved] + * * [FavoritesModelCallback.onNoneChanged] whenever we go from 1 to 0 favorites and back + * * [ControlsModel.ControlsModelCallback.onFirstChange] upon the first change in the model + */ + private fun onMoveItemInternal(from: Int, to: Int) { + if (from == dividerPosition) return // divider does not move + var changed = false + if (from < dividerPosition && to >= dividerPosition || + from > dividerPosition && to <= dividerPosition) { + if (from < dividerPosition && to >= dividerPosition) { + // favorite to not favorite + (elements[from] as ControlInfoWrapper).favorite = false + } else if (from > dividerPosition && to <= dividerPosition) { + // not favorite to favorite + (elements[from] as ControlInfoWrapper).favorite = true + } + changed = true + updateDivider(from, to) + } + moveElement(from, to) + adapter?.notifyItemMoved(from, to) + if (changed) { + adapter?.notifyItemChanged(to, Any()) + } + if (!modified) { + modified = true + favoritesModelCallback.onFirstChange() + } + } + + private fun updateDivider(from: Int, to: Int) { + var dividerChanged = false + val oldDividerPosition = dividerPosition + if (from < dividerPosition && to >= dividerPosition) { // favorite to not favorite + dividerPosition-- + if (dividerPosition == 0) { + updateDividerNone(oldDividerPosition, true) + dividerChanged = true + } + if (dividerPosition == elements.size - 2) { + updateDividerShow(oldDividerPosition, true) + dividerChanged = true + } + } else if (from > dividerPosition && to <= dividerPosition) { // not favorite to favorite + dividerPosition++ + if (dividerPosition == 1) { + updateDividerNone(oldDividerPosition, false) + dividerChanged = true + } + if (dividerPosition == elements.size - 1) { + updateDividerShow(oldDividerPosition, false) + dividerChanged = true + } + } + if (dividerChanged) { + adapter?.notifyItemChanged(oldDividerPosition) + } + } + + private fun moveElement(from: Int, to: Int) { + if (from < to) { + for (i in from until to) { + Collections.swap(elements, i, i + 1) + } + } else { + for (i in from downTo to + 1) { + Collections.swap(elements, i, i - 1) + } + } + } + + /** + * Touch helper to facilitate dragging in the [RecyclerView]. + * + * Only views above the divider line (favorites) can be dragged or accept drops. + */ + val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(0, 0) { + + private val MOVEMENT = ItemTouchHelper.UP or + ItemTouchHelper.DOWN or + ItemTouchHelper.LEFT or + ItemTouchHelper.RIGHT + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + onMoveItem(viewHolder.adapterPosition, target.adapterPosition) + return true + } + + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + if (viewHolder.adapterPosition < dividerPosition) { + return ItemTouchHelper.Callback.makeMovementFlags(MOVEMENT, 0) + } else { + return ItemTouchHelper.Callback.makeMovementFlags(0, 0) + } + } + + override fun canDropOver( + recyclerView: RecyclerView, + current: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return target.adapterPosition < dividerPosition + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + override fun isItemViewSwipeEnabled() = false + } + + interface FavoritesModelCallback : ControlsModel.ControlsModelCallback { + fun onNoneChanged(showNoFavorites: Boolean) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt index 680d0066fc56..ad86eeb089c8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt @@ -16,19 +16,27 @@ package com.android.systemui.controls.ui +import android.app.Dialog import android.app.PendingIntent import android.content.Intent -import android.provider.Settings +import android.service.controls.Control import android.service.controls.actions.BooleanAction import android.service.controls.actions.CommandAction import android.util.Log import android.view.HapticFeedbackConstants +import com.android.systemui.R + object ControlActionCoordinator { public const val MIN_LEVEL = 0 public const val MAX_LEVEL = 10000 - private var useDetailDialog: Boolean? = null + private var dialog: Dialog? = null + + fun closeDialog() { + dialog?.dismiss() + dialog = null + } fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) { cvh.action(BooleanAction(templateId, !isChecked)) @@ -37,31 +45,39 @@ object ControlActionCoordinator { cvh.clipLayer.setLevel(nextLevel) } - fun touch(cvh: ControlViewHolder, templateId: String) { - cvh.action(CommandAction(templateId)) + fun touch(cvh: ControlViewHolder, templateId: String, control: Control) { + if (cvh.usePanel()) { + showDialog(cvh, control.getAppIntent().getIntent()) + } else { + cvh.action(CommandAction(templateId)) + } } + /** + * Allow apps to specify whether they would like to appear in a detail panel or within + * the full activity by setting the {@link Control#EXTRA_USE_PANEL} flag. In order for + * activities to determine how they are being launched, they should inspect the + * {@link Control#EXTRA_USE_PANEL} flag for a value of true. + */ fun longPress(cvh: ControlViewHolder) { // Long press snould only be called when there is valid control state, otherwise ignore cvh.cws.control?.let { - if (useDetailDialog == null) { - useDetailDialog = Settings.Secure.getInt(cvh.context.getContentResolver(), - "systemui.controls_use_detail_panel", 0) != 0 - } - try { + it.getAppIntent().send() cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - if (useDetailDialog!!) { - DetailDialog(cvh.context, it.getAppIntent()).show() - } else { - it.getAppIntent().send() - val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) - cvh.context.sendBroadcast(closeDialog) - } + cvh.context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) } catch (e: PendingIntent.CanceledException) { Log.e(ControlsUiController.TAG, "Error sending pending intent", e) - cvh.setTransientStatus("Error opening application") + cvh.setTransientStatus( + cvh.context.resources.getString(R.string.controls_error_failed)) } } } + + private fun showDialog(cvh: ControlViewHolder, intent: Intent) { + dialog = DetailDialog(cvh, intent).also { + it.setOnDismissListener { _ -> dialog = null } + it.show() + } + } } 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 b1cb04e10eef..0eb6cb100933 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -39,16 +39,29 @@ import com.android.systemui.R import kotlin.reflect.KClass -private const val UPDATE_DELAY_IN_MILLIS = 3000L -private const val ALPHA_ENABLED = (255.0 * 0.2).toInt() -private const val ALPHA_DISABLED = 255 - +/** + * Wraps the widgets that make up the UI representation of a {@link Control}. Updates to the view + * are signaled via calls to {@link #bindData}. Similar to the ViewHolder concept used in + * RecyclerViews. + */ class ControlViewHolder( val layout: ViewGroup, val controlsController: ControlsController, val uiExecutor: DelayableExecutor, - val bgExecutor: DelayableExecutor + val bgExecutor: DelayableExecutor, + val usePanels: Boolean ) { + + companion object { + private const val UPDATE_DELAY_IN_MILLIS = 3000L + private const val ALPHA_ENABLED = (255.0 * 0.2).toInt() + private const val ALPHA_DISABLED = 255 + private val FORCE_PANEL_DEVICES = setOf( + DeviceTypes.TYPE_THERMOSTAT, + DeviceTypes.TYPE_CAMERA + ) + } + val icon: ImageView = layout.requireViewById(R.id.icon) val status: TextView = layout.requireViewById(R.id.status) val title: TextView = layout.requireViewById(R.id.title) @@ -59,6 +72,8 @@ class ControlViewHolder( var cancelUpdate: Runnable? = null var behavior: Behavior? = null var lastAction: ControlAction? = null + val deviceType: Int + get() = cws.control?.let { it.getDeviceType() } ?: cws.ci.deviceType init { val ld = layout.getBackground() as LayerDrawable @@ -76,7 +91,7 @@ class ControlViewHolder( val (controlStatus, template) = cws.control?.let { title.setText(it.getTitle()) subtitle.setText(it.getSubtitle()) - Pair(it.getStatus(), it.getControlTemplate()) + Pair(it.status, it.controlTemplate) } ?: run { title.setText(cws.ci.controlTitle) subtitle.setText(cws.ci.controlSubtitle) @@ -91,7 +106,7 @@ class ControlViewHolder( }) } - val clazz = findBehavior(controlStatus, template) + val clazz = findBehavior(controlStatus, template, deviceType) if (behavior == null || behavior!!::class != clazz) { // Behavior changes can signal a change in template from the app or // first time setup @@ -126,9 +141,17 @@ class ControlViewHolder( controlsController.action(cws.componentName, cws.ci, action) } - private fun findBehavior(status: Int, template: ControlTemplate): KClass<out Behavior> { + fun usePanel(): Boolean = + usePanels && deviceType in ControlViewHolder.FORCE_PANEL_DEVICES + + private fun findBehavior( + status: Int, + template: ControlTemplate, + deviceType: Int + ): KClass<out Behavior> { return when { status == Control.STATUS_UNKNOWN -> UnknownBehavior::class + deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class template is ToggleTemplate -> ToggleBehavior::class template is StatelessTemplate -> TouchBehavior::class template is ToggleRangeTemplate -> ToggleRangeBehavior::class @@ -140,7 +163,6 @@ class ControlViewHolder( internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0) { setEnabled(enabled) - val deviceType = cws.control?.let { it.getDeviceType() } ?: cws.ci.deviceType val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset) val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 934a69508d68..fab6fc7357dd 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -30,18 +30,19 @@ import android.content.res.Configuration import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.os.Process +import android.provider.Settings import android.service.controls.Control import android.service.controls.actions.ControlAction -import android.util.TypedValue import android.util.Log -import android.view.animation.AccelerateInterpolator -import android.view.animation.DecelerateInterpolator +import android.util.TypedValue import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View import android.view.View.MeasureSpec import android.view.ViewGroup import android.view.WindowManager +import android.view.animation.AccelerateInterpolator +import android.view.animation.DecelerateInterpolator import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.ImageView @@ -49,23 +50,21 @@ import android.widget.LinearLayout import android.widget.ListPopupWindow import android.widget.Space import android.widget.TextView +import com.android.systemui.R import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.StructureInfo +import com.android.systemui.controls.management.ControlsEditingActivity import com.android.systemui.controls.management.ControlsFavoritingActivity import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.management.ControlsProviderSelectorActivity import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.util.concurrency.DelayableExecutor -import com.android.systemui.R - import dagger.Lazy - import java.text.Collator import java.util.function.Consumer - import javax.inject.Inject import javax.inject.Singleton @@ -85,6 +84,7 @@ class ControlsUiControllerImpl @Inject constructor ( private const val PREF_COMPONENT = "controls_component" private const val PREF_STRUCTURE = "controls_structure" + private const val USE_PANELS = "systemui.controls_use_panel" private const val FADE_IN_MILLIS = 225L private val EMPTY_COMPONENT = ComponentName("", "") @@ -210,14 +210,28 @@ class ControlsUiControllerImpl @Inject constructor ( } private fun startFavoritingActivity(context: Context, si: StructureInfo) { - val i = Intent(context, ControlsFavoritingActivity::class.java).apply { + startTargetedActivity(context, si, ControlsFavoritingActivity::class.java) + } + + private fun startEditingActivity(context: Context, si: StructureInfo) { + startTargetedActivity(context, si, ControlsEditingActivity::class.java) + } + + private fun startTargetedActivity(context: Context, si: StructureInfo, klazz: Class<*>) { + val i = Intent(context, klazz).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + } + putIntentExtras(i, si) + startActivity(context, i) + } + + private fun putIntentExtras(intent: Intent, si: StructureInfo) { + intent.apply { putExtra(ControlsFavoritingActivity.EXTRA_APP, - controlsListingController.get().getAppLabel(si.componentName)) + controlsListingController.get().getAppLabel(si.componentName)) putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, si.structure) putExtra(Intent.EXTRA_COMPONENT_NAME, si.componentName) - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) } - startActivity(context, i) } private fun startProviderSelectorActivity(context: Context) { @@ -253,6 +267,7 @@ class ControlsUiControllerImpl @Inject constructor ( private fun createMenu() { val items = arrayOf( context.resources.getString(R.string.controls_menu_add), + context.resources.getString(R.string.controls_menu_edit), "Reset" ) var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items) @@ -273,8 +288,10 @@ class ControlsUiControllerImpl @Inject constructor ( when (pos) { // 0: Add Control 0 -> startFavoritingActivity(view.context, selectedStructure) - // 1: TEMPORARY for reset controls - 1 -> showResetConfirmation() + // 1: Edit controls + 1 -> startEditingActivity(view.context, selectedStructure) + // 2: TEMPORARY for reset controls + 2 -> showResetConfirmation() else -> Log.w(ControlsUiController.TAG, "Unsupported index ($pos) on 'more' menu selection") } @@ -361,7 +378,6 @@ class ControlsUiControllerImpl @Inject constructor ( .setTint(context.resources.getColor(R.color.control_spinner_dropdown, null)) } parent.requireViewById<ImageView>(R.id.app_icon).apply { - setContentDescription(selectionItem.getTitle()) setImageDrawable(selectionItem.icon) } @@ -408,6 +424,9 @@ class ControlsUiControllerImpl @Inject constructor ( val maxColumns = findMaxColumns() + // use flag only temporarily for testing + val usePanels = Settings.Secure.getInt(context.contentResolver, USE_PANELS, 0) == 1 + val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup var lastRow: ViewGroup = createRow(inflater, listView) selectedStructure.controls.forEach { @@ -421,7 +440,8 @@ class ControlsUiControllerImpl @Inject constructor ( baseLayout, controlsController.get(), uiExecutor, - bgExecutor + bgExecutor, + usePanels ) val key = ControlKey(selectedStructure.componentName, it.controlId) cvh.bindData(controlsById.getValue(key)) @@ -501,6 +521,7 @@ class ControlsUiControllerImpl @Inject constructor ( hidden = true popup?.dismiss() activeDialog?.dismiss() + ControlActionCoordinator.closeDialog() controlsController.get().unsubscribe() @@ -586,7 +607,6 @@ private class ItemAdapter( setText(item.getTitle()) } view.requireViewById<ImageView>(R.id.app_icon).apply { - setContentDescription(item.appName) setImageDrawable(item.icon) } return view diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index d3d4287b8707..15c41a2005a6 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -17,18 +17,16 @@ package com.android.systemui.controls.ui import android.app.ActivityView -import android.app.ActivityOptions import android.app.Dialog -import android.app.PendingIntent import android.content.ComponentName -import android.content.Context import android.content.Intent +import android.provider.Settings import android.view.View import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.Window +import android.view.WindowInsets import android.view.WindowManager -import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS +import android.widget.ImageView +import android.widget.TextView import com.android.systemui.R @@ -38,20 +36,26 @@ import com.android.systemui.R * The activity being launched is specified by {@link android.service.controls.Control#getAppIntent}. */ class DetailDialog( - val parentContext: Context, - val intent: PendingIntent -) : Dialog(parentContext) { + val cvh: ControlViewHolder, + val intent: Intent +) : Dialog(cvh.context, R.style.Theme_SystemUI_Dialog_Control_DetailPanel) { - var activityView: ActivityView + companion object { + private const val ALPHA = (0.8f * 255).toInt() + private const val PANEL_TOP_OFFSET = "systemui.controls_panel_top_offset" + } + + lateinit var activityView: ActivityView val stateCallback: ActivityView.StateCallback = object : ActivityView.StateCallback() { override fun onActivityViewReady(view: ActivityView) { - val fillInIntent = Intent() + val launchIntent = Intent(intent) // Apply flags to make behaviour match documentLaunchMode=always. - fillInIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - view.startActivity(intent, fillInIntent, ActivityOptions.makeBasic()) + launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) + + view.startActivity(launchIntent) } override fun onActivityViewDestroyed(view: ActivityView) {} @@ -61,28 +65,8 @@ class DetailDialog( override fun onTaskRemovalStarted(taskId: Int) {} } - @Suppress("DEPRECATION") - private fun Window.setWindowParams() { - requestFeature(Window.FEATURE_NO_TITLE) - - // Inflate the decor view, so the attributes below are not overwritten by the theme. - decorView - attributes.systemUiVisibility = - (attributes.systemUiVisibility - or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - or View.SYSTEM_UI_FLAG_LAYOUT_STABLE) - - setLayout(MATCH_PARENT, MATCH_PARENT) - clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR - or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) - setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) - getAttributes().setFitInsetsTypes(0 /* types */) - } - init { - getWindow()?.setWindowParams() + window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) setContentView(R.layout.controls_detail_dialog) @@ -90,19 +74,61 @@ class DetailDialog( requireViewById<ViewGroup>(R.id.controls_activity_view).apply { addView(activityView) } + + requireViewById<ImageView>(R.id.control_detail_close).apply { + setOnClickListener { _: View -> dismiss() } + } + + requireViewById<TextView>(R.id.title).apply { + setText(cvh.title.text) + } + + requireViewById<TextView>(R.id.subtitle).apply { + setText(cvh.subtitle.text) + } + + requireViewById<ImageView>(R.id.control_detail_open_in_app).apply { + setOnClickListener { v: View -> + dismiss() + context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) + v.context.startActivity(intent) + } + } + + // consume all insets to achieve slide under effect + window.getDecorView().setOnApplyWindowInsetsListener { + v: View, insets: WindowInsets -> + activityView.apply { + val l = getPaddingLeft() + val t = getPaddingTop() + val r = getPaddingRight() + setPadding(l, t, r, insets.getSystemWindowInsets().bottom) + } + + insets.consumeSystemWindowInsets() + } + + requireViewById<ViewGroup>(R.id.control_detail_root).apply { + // use flag only temporarily for testing + val resolver = cvh.context.contentResolver + val defaultOffsetInPx = cvh.context.resources + .getDimensionPixelSize(R.dimen.controls_activity_view_top_offset) + val offsetInPx = Settings.Secure.getInt(resolver, PANEL_TOP_OFFSET, defaultOffsetInPx) + + val lp = getLayoutParams() as ViewGroup.MarginLayoutParams + lp.topMargin = offsetInPx + setLayoutParams(lp) + } } override fun show() { - val attrs = getWindow()?.attributes - attrs?.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - getWindow()?.attributes = attrs - activityView.setCallback(stateCallback) super.show() } override fun dismiss() { + if (!isShowing()) return activityView.release() super.dismiss() diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt index 15c1dabf71bd..6340db1d756d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt @@ -33,6 +33,10 @@ class TemperatureControlBehavior : Behavior { override fun initialize(cvh: ControlViewHolder) { this.cvh = cvh + + cvh.layout.setOnClickListener { _ -> + ControlActionCoordinator.touch(cvh, template.getTemplateId(), control) + } } override fun bind(cws: ControlWithState) { 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 d64a5f060487..b02c9c8972fc 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt @@ -20,7 +20,7 @@ import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.view.View import android.service.controls.Control -import android.service.controls.templates.StatelessTemplate +import android.service.controls.templates.ControlTemplate import com.android.systemui.R import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL @@ -31,7 +31,7 @@ import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL */ class TouchBehavior : Behavior { lateinit var clipLayer: Drawable - lateinit var template: StatelessTemplate + lateinit var template: ControlTemplate lateinit var control: Control lateinit var cvh: ControlViewHolder @@ -40,14 +40,14 @@ class TouchBehavior : Behavior { cvh.applyRenderInfo(false) cvh.layout.setOnClickListener(View.OnClickListener() { - ControlActionCoordinator.touch(cvh, template.getTemplateId()) + ControlActionCoordinator.touch(cvh, template.getTemplateId(), control) }) } override fun bind(cws: ControlWithState) { this.control = cws.control!! cvh.status.setText(control.getStatusText()) - template = control.getControlTemplate() as StatelessTemplate + template = control.getControlTemplate() val ld = cvh.layout.getBackground() as LayerDrawable clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index a4a589481b33..3f095dc0510e 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -222,11 +222,27 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private ControlsController mControlsController; private SharedPreferences mControlsPreferences; private final RingerModeTracker mRingerModeTracker; + private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms @VisibleForTesting public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "The global actions / power menu surface became visible on the screen.") - GA_POWER_MENU_OPEN(337); + GA_POWER_MENU_OPEN(337), + + @UiEvent(doc = "The global actions bugreport button was pressed.") + GA_BUGREPORT_PRESS(344), + + @UiEvent(doc = "The global actions bugreport button was long pressed.") + GA_BUGREPORT_LONG_PRESS(345), + + @UiEvent(doc = "The global actions emergency button was pressed.") + GA_EMERGENCY_DIALER_PRESS(346), + + @UiEvent(doc = "The global actions screenshot button was pressed.") + GA_SCREENSHOT_PRESS(347), + + @UiEvent(doc = "The global actions screenshot button was long pressed.") + GA_SCREENSHOT_LONG_PRESS(348); private final int mId; @@ -679,7 +695,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - private class EmergencyDialerAction extends EmergencyAction { + @VisibleForTesting + class EmergencyDialerAction extends EmergencyAction { private EmergencyDialerAction() { super(com.android.systemui.R.drawable.ic_emergency_star, R.string.global_action_emergency); @@ -688,6 +705,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public void onPress() { mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU); + mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS); if (mTelecomManager != null) { Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent( null /* number */); @@ -701,6 +719,11 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + @VisibleForTesting + EmergencyDialerAction makeEmergencyDialerActionForTesting() { + return new EmergencyDialerAction(); + } + private final class RestartAction extends SinglePressAction implements LongPressAction { private RestartAction() { super(R.drawable.ic_restart, R.string.global_action_restart); @@ -731,7 +754,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - private class ScreenshotAction extends SinglePressAction implements LongPressAction { + @VisibleForTesting + class ScreenshotAction extends SinglePressAction implements LongPressAction { public ScreenshotAction() { super(R.drawable.ic_screenshot, R.string.global_action_screenshot); } @@ -747,8 +771,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, public void run() { mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null); mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); + mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS); } - }, 500); + }, mDialogPressDelay); } @Override @@ -764,6 +789,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public boolean onLongPress() { if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS)) { + mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_LONG_PRESS); mScreenRecordHelper.launchRecordPrompt(); } else { onPress(); @@ -772,7 +798,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - private class BugReportAction extends SinglePressAction implements LongPressAction { + @VisibleForTesting + ScreenshotAction makeScreenshotActionForTesting() { + return new ScreenshotAction(); + } + + @VisibleForTesting + class BugReportAction extends SinglePressAction implements LongPressAction { public BugReportAction() { super(R.drawable.ic_lock_bugreport, R.string.bugreport_title); @@ -795,6 +827,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // Take an "interactive" bugreport. mMetricsLogger.action( MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE); + mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_PRESS); if (!mIActivityManager.launchBugReportHandlerApp()) { Log.w(TAG, "Bugreport handler could not be launched"); mIActivityManager.requestInteractiveBugReport(); @@ -802,7 +835,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } catch (RemoteException e) { } } - }, 500); + }, mDialogPressDelay); } @Override @@ -815,6 +848,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, try { // Take a "full" bugreport. mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); + mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS); mIActivityManager.requestFullBugReport(); } catch (RemoteException e) { } @@ -831,6 +865,11 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + @VisibleForTesting + BugReportAction makeBugReportActionForTesting() { + return new BugReportAction(); + } + private final class LogoutAction extends SinglePressAction { private LogoutAction() { super(R.drawable.ic_logout, R.string.global_action_logout); @@ -858,7 +897,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } catch (RemoteException re) { Log.e(TAG, "Couldn't logout user " + re); } - }, 500); + }, mDialogPressDelay); } } @@ -1599,6 +1638,11 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private static final int MESSAGE_REFRESH = 1; private static final int MESSAGE_SHOW = 2; private static final int DIALOG_DISMISS_DELAY = 300; // ms + private static final int DIALOG_PRESS_DELAY = 500; // ms + + @VisibleForTesting void setZeroDialogPressDelayForTesting() { + mDialogPressDelay = 0; // ms + } private Handler mHandler = new Handler() { public void handleMessage(Message msg) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 9873d240efe3..62efd8ce4cee 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -44,9 +44,11 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.core.graphics.drawable.RoundedBitmapDrawable; import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; +import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.MediaOutputSliceConstants; import com.android.settingslib.widget.AdaptiveIcon; @@ -66,6 +68,7 @@ import java.util.concurrent.Executor; public class MediaControlPanel { private static final String TAG = "MediaControlPanel"; private final NotificationMediaManager mMediaManager; + @Nullable private final LocalMediaManager mLocalMediaManager; private final Executor mForegroundExecutor; private final Executor mBackgroundExecutor; @@ -77,6 +80,7 @@ public class MediaControlPanel { private int mForegroundColor; private int mBackgroundColor; protected ComponentName mRecvComponent; + private MediaDevice mDevice; private boolean mIsRegistered = false; private final int[] mActionIds; @@ -121,19 +125,44 @@ public class MediaControlPanel { } }; + private final LocalMediaManager.DeviceCallback mDeviceCallback = + new LocalMediaManager.DeviceCallback() { + @Override + public void onDeviceListUpdate(List<MediaDevice> devices) { + if (mLocalMediaManager == null) { + return; + } + MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice(); + // Check because this can be called several times while changing devices + if (mDevice == null || !mDevice.equals(currentDevice)) { + mDevice = currentDevice; + updateDevice(mDevice); + } + } + + @Override + public void onSelectedDeviceStateChanged(MediaDevice device, int state) { + if (mDevice == null || !mDevice.equals(device)) { + mDevice = device; + updateDevice(mDevice); + } + } + }; + /** * Initialize a new control panel * @param context * @param parent * @param manager + * @param routeManager Manager used to listen for device change events. * @param layoutId layout resource to use for this control panel * @param actionIds resource IDs for action buttons in the layout * @param foregroundExecutor foreground executor * @param backgroundExecutor background executor, used for processing artwork */ public MediaControlPanel(Context context, ViewGroup parent, NotificationMediaManager manager, - @LayoutRes int layoutId, int[] actionIds, Executor foregroundExecutor, - Executor backgroundExecutor) { + @Nullable LocalMediaManager routeManager, @LayoutRes int layoutId, int[] actionIds, + Executor foregroundExecutor, Executor backgroundExecutor) { mContext = context; LayoutInflater inflater = LayoutInflater.from(mContext); mMediaNotifView = (LinearLayout) inflater.inflate(layoutId, parent, false); @@ -144,6 +173,7 @@ public class MediaControlPanel { // mStateListener to be unregistered in detach. mMediaNotifView.addOnAttachStateChangeListener(mStateListener); mMediaManager = manager; + mLocalMediaManager = routeManager; mActionIds = actionIds; mForegroundExecutor = foregroundExecutor; mBackgroundExecutor = backgroundExecutor; @@ -176,7 +206,7 @@ public class MediaControlPanel { * @param device */ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, - int bgColor, PendingIntent contentIntent, String appNameString, MediaDevice device) { + int bgColor, PendingIntent contentIntent, String appNameString) { mToken = token; mForegroundColor = iconColor; mBackgroundColor = bgColor; @@ -253,9 +283,9 @@ public class MediaControlPanel { // Transfer chip mSeamless = mMediaNotifView.findViewById(R.id.media_seamless); - if (mSeamless != null) { + if (mSeamless != null && mLocalMediaManager != null) { mSeamless.setVisibility(View.VISIBLE); - updateDevice(device); + updateDevice(mLocalMediaManager.getCurrentConnectedDevice()); ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class); mSeamless.setOnClickListener(v -> { final Intent intent = new Intent() @@ -366,7 +396,7 @@ public class MediaControlPanel { * Update the current device information * @param device device information to display */ - public void updateDevice(MediaDevice device) { + private void updateDevice(MediaDevice device) { if (mSeamless == null) { return; } @@ -456,6 +486,10 @@ public class MediaControlPanel { Assert.isMainThread(); if (!mIsRegistered) { mMediaManager.addCallback(mMediaListener); + if (mLocalMediaManager != null) { + mLocalMediaManager.registerCallback(mDeviceCallback); + mLocalMediaManager.startScan(); + } mIsRegistered = true; } } @@ -463,6 +497,10 @@ public class MediaControlPanel { private void makeInactive() { Assert.isMainThread(); if (mIsRegistered) { + if (mLocalMediaManager != null) { + mLocalMediaManager.stopScan(); + mLocalMediaManager.unregisterCallback(mDeviceCallback); + } mMediaManager.removeCallback(mMediaListener); mIsRegistered = false; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index 677947929049..bab1f39d63e0 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -240,6 +240,12 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio mInitialized = true; mContext = context; mPipBoundsHandler = pipBoundsHandler; + // Ensure that we have the display info in case we get calls to update the bounds before the + // listener calls back + final DisplayInfo displayInfo = new DisplayInfo(); + context.getDisplay().getDisplayInfo(displayInfo); + mPipBoundsHandler.onDisplayInfoChanged(displayInfo); + mResizeAnimationDuration = context.getResources() .getInteger(R.integer.config_pipResizeAnimationDuration); mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler, diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index 339a408d501a..e636707a9722 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -34,7 +34,7 @@ import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.TextView; -import com.android.settingslib.media.MediaDevice; +import com.android.settingslib.media.LocalMediaManager; import com.android.systemui.R; import com.android.systemui.media.MediaControlPanel; import com.android.systemui.media.SeekBarObserver; @@ -74,9 +74,10 @@ public class QSMediaPlayer extends MediaControlPanel { * @param backgroundExecutor */ public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager, - Executor foregroundExecutor, DelayableExecutor backgroundExecutor) { - super(context, parent, manager, R.layout.qs_media_panel, QS_ACTION_IDS, foregroundExecutor, - backgroundExecutor); + LocalMediaManager routeManager, Executor foregroundExecutor, + DelayableExecutor backgroundExecutor) { + super(context, parent, manager, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS, + foregroundExecutor, backgroundExecutor); mParent = (QSPanel) parent; mBackgroundExecutor = backgroundExecutor; mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor); @@ -101,12 +102,12 @@ public class QSMediaPlayer extends MediaControlPanel { * @param device current playback device */ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, - int bgColor, View actionsContainer, Notification notif, MediaDevice device) { + int bgColor, View actionsContainer, Notification notif) { String appName = Notification.Builder.recoverBuilder(getContext(), notif) .loadHeaderAppName(); super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent, - appName, device); + appName); // Media controls LinearLayout parentActionsLayout = (LinearLayout) actionsContainer; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index c8412fffd143..0566b2e621db 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -47,7 +47,6 @@ import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; -import com.android.settingslib.media.MediaDevice; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -75,7 +74,6 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; -import java.util.List; import java.util.concurrent.Executor; import java.util.stream.Collectors; @@ -105,8 +103,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private final LocalBluetoothManager mLocalBluetoothManager; private final Executor mForegroundExecutor; private final DelayableExecutor mBackgroundExecutor; - private LocalMediaManager mLocalMediaManager; - private MediaDevice mDevice; private boolean mUpdateCarousel = false; protected boolean mExpanded; @@ -130,34 +126,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private BrightnessMirrorController mBrightnessMirrorController; private View mDivider; - private final LocalMediaManager.DeviceCallback mDeviceCallback = - new LocalMediaManager.DeviceCallback() { - @Override - public void onDeviceListUpdate(List<MediaDevice> devices) { - if (mLocalMediaManager == null) { - return; - } - MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice(); - // Check because this can be called several times while changing devices - if (mDevice == null || !mDevice.equals(currentDevice)) { - mDevice = currentDevice; - for (QSMediaPlayer p : mMediaPlayers) { - p.updateDevice(mDevice); - } - } - } - - @Override - public void onSelectedDeviceStateChanged(MediaDevice device, int state) { - if (mDevice == null || !mDevice.equals(device)) { - mDevice = device; - for (QSMediaPlayer p : mMediaPlayers) { - p.updateDevice(mDevice); - } - } - } - }; - @Inject public QSPanel( @Named(VIEW_CONTEXT) Context context, @@ -277,7 +245,14 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne if (player == null) { Log.d(TAG, "creating new player"); - player = new QSMediaPlayer(mContext, this, mNotificationMediaManager, + // Set up listener for device changes + // TODO: integrate with MediaTransferManager? + InfoMediaManager imm = new InfoMediaManager(mContext, notif.getPackageName(), + notif.getNotification(), mLocalBluetoothManager); + LocalMediaManager routeManager = new LocalMediaManager(mContext, mLocalBluetoothManager, + imm, notif.getPackageName()); + + player = new QSMediaPlayer(mContext, this, mNotificationMediaManager, routeManager, mForegroundExecutor, mBackgroundExecutor); player.setListening(mListening); if (player.isPlaying()) { @@ -292,22 +267,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne Log.d(TAG, "setting player session"); player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer, - notif.getNotification(), mDevice); + notif.getNotification()); if (mMediaPlayers.size() > 0) { ((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE); - - if (mLocalMediaManager == null) { - // Set up listener for device changes - // TODO: integrate with MediaTransferManager? - InfoMediaManager imm = - new InfoMediaManager(mContext, null, null, mLocalBluetoothManager); - mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager, imm, - null); - mLocalMediaManager.startScan(); - mDevice = mLocalMediaManager.getCurrentConnectedDevice(); - mLocalMediaManager.registerCallback(mDeviceCallback); - } } } @@ -330,11 +293,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mMediaCarousel.removeView(player.getView()); if (mMediaPlayers.size() == 0) { ((View) mMediaCarousel.getParent()).setVisibility(View.GONE); - if (mLocalMediaManager != null) { - mLocalMediaManager.stopScan(); - mLocalMediaManager.unregisterCallback(mDeviceCallback); - mLocalMediaManager = null; - } } return true; } @@ -404,11 +362,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mBrightnessMirrorController.removeCallback(this); } mDumpManager.unregisterDumpable(getDumpableTag()); - if (mLocalMediaManager != null) { - mLocalMediaManager.stopScan(); - mLocalMediaManager.unregisterCallback(mDeviceCallback); - mLocalMediaManager = null; - } super.onDetachedFromWindow(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java index 0c5019491a59..0ba4cb159024 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java @@ -53,7 +53,7 @@ public class QuickQSMediaPlayer extends MediaControlPanel { */ public QuickQSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager, Executor foregroundExecutor, Executor backgroundExecutor) { - super(context, parent, manager, R.layout.qqs_media_panel, QQS_ACTION_IDS, + super(context, parent, manager, null, R.layout.qqs_media_panel, QQS_ACTION_IDS, foregroundExecutor, backgroundExecutor); } @@ -84,7 +84,7 @@ public class QuickQSMediaPlayer extends MediaControlPanel { return; } - super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null, null); + super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null); LinearLayout parentActionsLayout = (LinearLayout) actionsContainer; int i = 0; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 366ef931e1f5..2df450604d3b 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -1042,7 +1042,8 @@ public class DividerView extends FrameLayout implements OnTouchListener, dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect; otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect; - mDividerPositionX = dockedRect.right; + mDividerPositionX = mSplitLayout.getPrimarySplitSide() == DOCKED_RIGHT + ? otherRect.right : dockedRect.right; mDividerPositionY = dockedRect.bottom; if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java index 3b8addb85d85..92f6b4a2d8c4 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java @@ -101,7 +101,16 @@ public class SplitDisplayLayout { } int getPrimarySplitSide() { - return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP; + switch (mDisplayLayout.getNavigationBarPosition(mContext.getResources())) { + case DisplayLayout.NAV_BAR_BOTTOM: + return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP; + case DisplayLayout.NAV_BAR_LEFT: + return DOCKED_RIGHT; + case DisplayLayout.NAV_BAR_RIGHT: + return DOCKED_LEFT; + default: + return DOCKED_INVALID; + } } boolean isMinimized() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index c8b34f1f5b27..4e6df0ad1ba4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -75,6 +75,9 @@ public final class NotificationClicker implements View.OnClickListener { // We never want to open the app directly if the user clicks in between // the notifications. return; + } else if (row.areGutsExposed()) { + // ignore click if guts are exposed + return; } // Mark notification for one frame. diff --git a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java index a94af24fc843..1c7a9b5e8681 100644 --- a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java +++ b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java @@ -175,6 +175,9 @@ public class WifiDebuggingActivity extends AlertActivity IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); registerReceiver(mWifiChangeReceiver, filter); + // Close quick shade + sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + } @Override diff --git a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java index 0266a84503a1..7a31fa54b1cd 100644 --- a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java +++ b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingSecondaryUserActivity.java @@ -96,6 +96,8 @@ public class WifiDebuggingSecondaryUserActivity extends AlertActivity IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); registerReceiver(mWifiChangeReceiver, filter); + // Close quick shade + sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java index 4652abfa0721..cfec1c07ff1d 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java @@ -27,6 +27,7 @@ import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import android.annotation.IntDef; import android.annotation.NonNull; import android.content.ContentResolver; import android.content.Context; @@ -46,16 +47,27 @@ import android.view.Surface; import com.android.internal.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Contains information about the layout-properties of a display. This refers to internal layout * like insets/cutout/rotation. In general, this can be thought of as the System-UI analog to * DisplayPolicy. */ public class DisplayLayout { + @IntDef(prefix = { "NAV_BAR_" }, value = { + NAV_BAR_LEFT, + NAV_BAR_RIGHT, + NAV_BAR_BOTTOM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NavBarPosition {} + // Navigation bar position values - private static final int NAV_BAR_LEFT = 1 << 0; - private static final int NAV_BAR_RIGHT = 1 << 1; - private static final int NAV_BAR_BOTTOM = 1 << 2; + public static final int NAV_BAR_LEFT = 1 << 0; + public static final int NAV_BAR_RIGHT = 1 << 1; + public static final int NAV_BAR_BOTTOM = 1 << 2; private int mUiMode; private int mWidth; @@ -214,6 +226,14 @@ public class DisplayLayout { } /** + * Gets navigation bar position for this layout + * @return Navigation bar position for this layout. + */ + public @NavBarPosition int getNavigationBarPosition(Resources res) { + return navigationBarPosition(res, mWidth, mHeight, mRotation); + } + + /** * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta` * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and * remains at 0,0 after rotation. @@ -437,8 +457,8 @@ public class DisplayLayout { } /** Retrieve navigation bar position from resources based on rotation and size. */ - public static int navigationBarPosition(Resources res, int displayWidth, int displayHeight, - int rotation) { + public static @NavBarPosition int navigationBarPosition(Resources res, int displayWidth, + int displayHeight, int rotation) { boolean navBarCanMove = displayWidth != displayHeight && res.getBoolean( com.android.internal.R.bool.config_navBarCanMove); if (navBarCanMove && displayWidth > displayHeight) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index 8630570c4e70..f6ee46b0303a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -87,9 +87,6 @@ class ControlsControllerImplTest : SysuiTestCase() { private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo> @Captor - private lateinit var booleanConsumer: ArgumentCaptor<Consumer<Boolean>> - - @Captor private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<ControlsBindingController.LoadCallback> @Captor @@ -936,4 +933,33 @@ class ControlsControllerImplTest : SysuiTestCase() { verifyNoMoreInteractions(persistenceWrapper) verifyNoMoreInteractions(auxiliaryPersistenceWrapper) } + + @Test + fun testGetFavoritesForStructure() { + controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO) + controller.replaceFavoritesForStructure( + TEST_STRUCTURE_INFO_2.copy(componentName = TEST_COMPONENT)) + delayableExecutor.runAllReady() + + assertEquals(TEST_STRUCTURE_INFO.controls, + controller.getFavoritesForStructure(TEST_COMPONENT, TEST_STRUCTURE)) + assertEquals(TEST_STRUCTURE_INFO_2.controls, + controller.getFavoritesForStructure(TEST_COMPONENT, TEST_STRUCTURE_2)) + } + + @Test + fun testGetFavoritesForStructure_wrongStructure() { + controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO) + delayableExecutor.runAllReady() + + assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT, TEST_STRUCTURE_2).isEmpty()) + } + + @Test + fun testGetFavoritesForStructure_wrongComponent() { + controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO) + delayableExecutor.runAllReady() + + assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt index 5e0d28f6f795..236384b09514 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt @@ -31,6 +31,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @@ -43,6 +45,8 @@ class AllModelTest : SysuiTestCase() { @Mock lateinit var pendingIntent: PendingIntent + @Mock + lateinit var controlsModelCallback: ControlsModel.ControlsModelCallback val idPrefix = "controlId" val favoritesIndices = listOf(7, 3, 1, 9) @@ -84,7 +88,7 @@ class AllModelTest : SysuiTestCase() { it in favoritesIndices ) } - model = AllModel(controls, favoritesList, EMPTY_STRING) + model = AllModel(controls, favoritesList, EMPTY_STRING, controlsModelCallback) } @Test @@ -93,28 +97,28 @@ class AllModelTest : SysuiTestCase() { // Zones are sorted by order of appearance, with empty at the end with special header. val expected = listOf( ZoneNameWrapper("1"), - ControlWrapper(controls[0]), - ControlWrapper(controls[3]), - ControlWrapper(controls[6]), - ControlWrapper(controls[9]), + ControlStatusWrapper(controls[0]), + ControlStatusWrapper(controls[3]), + ControlStatusWrapper(controls[6]), + ControlStatusWrapper(controls[9]), ZoneNameWrapper("2"), - ControlWrapper(controls[1]), - ControlWrapper(controls[4]), - ControlWrapper(controls[7]), + ControlStatusWrapper(controls[1]), + ControlStatusWrapper(controls[4]), + ControlStatusWrapper(controls[7]), ZoneNameWrapper("0"), - ControlWrapper(controls[2]), - ControlWrapper(controls[5]), - ControlWrapper(controls[8]), + ControlStatusWrapper(controls[2]), + ControlStatusWrapper(controls[5]), + ControlStatusWrapper(controls[8]), ZoneNameWrapper(EMPTY_STRING), - ControlWrapper(controls[10]), - ControlWrapper(controls[11]) + ControlStatusWrapper(controls[10]), + ControlStatusWrapper(controls[11]) ) expected.zip(model.elements).forEachIndexed { index, it -> assertEquals("Error in item at index $index", it.first, it.second) } } - private fun sameControl(controlInfo: ControlInfo.Builder, control: Control): Boolean { + private fun sameControl(controlInfo: ControlInfo, control: Control): Boolean { return controlInfo.controlId == control.controlId && controlInfo.controlTitle == control.title && controlInfo.controlSubtitle == control.subtitle && @@ -124,10 +128,11 @@ class AllModelTest : SysuiTestCase() { @Test fun testAllEmpty_noHeader() { val selected_controls = listOf(controls[10], controls[11]) - val new_model = AllModel(selected_controls, emptyList(), EMPTY_STRING) + val new_model = AllModel(selected_controls, emptyList(), EMPTY_STRING, + controlsModelCallback) val expected = listOf( - ControlWrapper(controls[10]), - ControlWrapper(controls[11]) + ControlStatusWrapper(controls[10]), + ControlStatusWrapper(controls[11]) ) expected.zip(new_model.elements).forEachIndexed { index, it -> @@ -154,6 +159,8 @@ class AllModelTest : SysuiTestCase() { model.favorites.zip(expectedFavorites).forEach { assertTrue(sameControl(it.first, it.second)) } + + verify(controlsModelCallback).onFirstChange() } @Test @@ -163,10 +170,12 @@ class AllModelTest : SysuiTestCase() { model.changeFavoriteStatus(id, true) assertTrue( (model.elements.first { - it is ControlWrapper && it.controlStatus.control.controlId == id - } as ControlWrapper) + it is ControlStatusWrapper && it.controlStatus.control.controlId == id + } as ControlStatusWrapper) .controlStatus.favorite ) + + verify(controlsModelCallback).onFirstChange() } @Test @@ -180,6 +189,8 @@ class AllModelTest : SysuiTestCase() { model.favorites.zip(expectedFavorites).forEach { assertTrue(sameControl(it.first, it.second)) } + + verify(controlsModelCallback, never()).onFirstChange() } @Test @@ -194,6 +205,8 @@ class AllModelTest : SysuiTestCase() { model.favorites.zip(expectedFavorites).forEach { assertTrue(sameControl(it.first, it.second)) } + + verify(controlsModelCallback).onFirstChange() } @Test @@ -203,10 +216,12 @@ class AllModelTest : SysuiTestCase() { model.changeFavoriteStatus(id, false) assertFalse( (model.elements.first { - it is ControlWrapper && it.controlStatus.control.controlId == id - } as ControlWrapper) + it is ControlStatusWrapper && it.controlStatus.control.controlId == id + } as ControlStatusWrapper) .controlStatus.favorite ) + + verify(controlsModelCallback).onFirstChange() } @Test @@ -219,5 +234,7 @@ class AllModelTest : SysuiTestCase() { model.favorites.zip(expectedFavorites).forEach { assertTrue(sameControl(it.first, it.second)) } + + verify(controlsModelCallback, never()).onFirstChange() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt deleted file mode 100644 index c330b38fed42..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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.controls.management - -import android.app.PendingIntent -import android.content.ComponentName -import android.service.controls.Control -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.controls.ControlStatus -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Assert.fail -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.MockitoAnnotations - -open class FavoriteModelTest : SysuiTestCase() { - - @Mock - lateinit var pendingIntent: PendingIntent - @Mock - lateinit var allAdapter: ControlAdapter - @Mock - lateinit var favoritesAdapter: ControlAdapter - - val idPrefix = "controlId" - val favoritesIndices = listOf(7, 3, 1, 9) - val favoritesList = favoritesIndices.map { "controlId$it" } - lateinit var controls: List<ControlStatus> - - lateinit var model: FavoriteModel - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - // controlId0 --> zone = 0 - // controlId1 --> zone = 1, favorite - // controlId2 --> zone = 2 - // controlId3 --> zone = 0, favorite - // controlId4 --> zone = 1 - // controlId5 --> zone = 2 - // controlId6 --> zone = 0 - // controlId7 --> zone = 1, favorite - // controlId8 --> zone = 2 - // controlId9 --> zone = 0, favorite - controls = (0..9).map { - ControlStatus( - Control.StatelessBuilder("$idPrefix$it", pendingIntent) - .setZone((it % 3).toString()) - .build(), - ComponentName("", ""), - it in favoritesIndices - ) - } - - model = FavoriteModel(controls, favoritesList, favoritesAdapter, allAdapter) - } -} - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class FavoriteModelNonParametrizedTests : FavoriteModelTest() { - @Test - fun testAll() { - // Zones are sorted alphabetically - val expected = listOf( - ZoneNameWrapper("0"), - ControlWrapper(controls[0]), - ControlWrapper(controls[3]), - ControlWrapper(controls[6]), - ControlWrapper(controls[9]), - ZoneNameWrapper("1"), - ControlWrapper(controls[1]), - ControlWrapper(controls[4]), - ControlWrapper(controls[7]), - ZoneNameWrapper("2"), - ControlWrapper(controls[2]), - ControlWrapper(controls[5]), - ControlWrapper(controls[8]) - ) - assertEquals(expected, model.all) - } - - @Test - fun testFavoritesInOrder() { - val expected = favoritesIndices.map { ControlWrapper(controls[it]) } - assertEquals(expected, model.favorites) - } - - @Test - fun testChangeFavoriteStatus_addFavorite() { - val controlToAdd = 6 - model.changeFavoriteStatus("$idPrefix$controlToAdd", true) - - val pair = model.all.findControl(controlToAdd) - pair?.let { - assertTrue(it.second.favorite) - assertEquals(it.second, model.favorites.last().controlStatus) - verify(favoritesAdapter).notifyItemInserted(model.favorites.size - 1) - verify(allAdapter).notifyItemChanged(it.first) - verifyNoMoreInteractions(favoritesAdapter, allAdapter) - } ?: run { - fail("control not found") - } - } - - @Test - fun testChangeFavoriteStatus_removeFavorite() { - val controlToRemove = 3 - model.changeFavoriteStatus("$idPrefix$controlToRemove", false) - - val pair = model.all.findControl(controlToRemove) - pair?.let { - assertFalse(it.second.favorite) - assertTrue(model.favorites.none { - it.controlStatus.control.controlId == "$idPrefix$controlToRemove" - }) - verify(favoritesAdapter).notifyItemRemoved(favoritesIndices.indexOf(controlToRemove)) - verify(allAdapter).notifyItemChanged(it.first) - verifyNoMoreInteractions(favoritesAdapter, allAdapter) - } ?: run { - fail("control not found") - } - } - - @Test - fun testChangeFavoriteStatus_sameStatus() { - model.changeFavoriteStatus("${idPrefix}7", true) - model.changeFavoriteStatus("${idPrefix}6", false) - - val expected = favoritesIndices.map { ControlWrapper(controls[it]) } - assertEquals(expected, model.favorites) - - verifyNoMoreInteractions(favoritesAdapter, allAdapter) - } - - private fun List<ElementWrapper>.findControl(controlIndex: Int): Pair<Int, ControlStatus>? { - val index = indexOfFirst { - it is ControlWrapper && - it.controlStatus.control.controlId == "$idPrefix$controlIndex" - } - return if (index == -1) null else index to (get(index) as ControlWrapper).controlStatus - } -} - -@SmallTest -@RunWith(Parameterized::class) -class FavoriteModelParameterizedTest(val from: Int, val to: Int) : FavoriteModelTest() { - - companion object { - @JvmStatic - @Parameterized.Parameters(name = "{0} -> {1}") - fun data(): Collection<Array<Int>> { - return (0..3).flatMap { from -> - (0..3).map { to -> - arrayOf(from, to) - } - }.filterNot { it[0] == it[1] } - } - } - - @Test - fun testMoveItem() { - val originalFavorites = model.favorites.toList() - val originalFavoritesIds = - model.favorites.map { it.controlStatus.control.controlId }.toSet() - model.onMoveItem(from, to) - assertEquals(originalFavorites[from], model.favorites[to]) - // Check that we still have the same favorites - assertEquals(originalFavoritesIds, - model.favorites.map { it.controlStatus.control.controlId }.toSet()) - - verify(favoritesAdapter).notifyItemMoved(from, to) - - verifyNoMoreInteractions(allAdapter, favoritesAdapter) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt new file mode 100644 index 000000000000..ce33a8d49fac --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt @@ -0,0 +1,291 @@ +/* + * 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.controls.management + +import android.content.ComponentName +import android.testing.AndroidTestingRunner +import androidx.recyclerview.widget.RecyclerView +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlInterface +import com.android.systemui.controls.controller.ControlInfo +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class FavoritesModelTest : SysuiTestCase() { + + companion object { + private val TEST_COMPONENT = ComponentName.unflattenFromString("test_pkg/.test_cls")!! + private val ID_PREFIX = "control" + private val INITIAL_FAVORITES = (0..5).map { + ControlInfo("$ID_PREFIX$it", "title$it", "subtitle$it", it) + } + } + + @Mock + private lateinit var callback: FavoritesModel.FavoritesModelCallback + @Mock + private lateinit var adapter: RecyclerView.Adapter<*> + private lateinit var model: FavoritesModel + private lateinit var dividerWrapper: DividerWrapper + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + model = FavoritesModel(TEST_COMPONENT, INITIAL_FAVORITES, callback) + model.attachAdapter(adapter) + dividerWrapper = model.elements.first { it is DividerWrapper } as DividerWrapper + } + + @After + fun testListConsistency() { + assertEquals(INITIAL_FAVORITES.size + 1, model.elements.toSet().size) + val dividerIndex = getDividerPosition() + model.elements.forEachIndexed { index, element -> + if (index == dividerIndex) { + assertEquals(dividerWrapper, element) + } else { + element as ControlInterface + assertEquals(index < dividerIndex, element.favorite) + } + } + assertEquals(model.favorites, model.elements.take(dividerIndex).map { + (it as ControlInfoWrapper).controlInfo + }) + } + + @Test + fun testInitialElements() { + val expected = INITIAL_FAVORITES.map { + ControlInfoWrapper(TEST_COMPONENT, it, true) + } + DividerWrapper() + assertEquals(expected, model.elements) + } + + @Test + fun testFavorites() { + assertEquals(INITIAL_FAVORITES, model.favorites) + } + + @Test + fun testRemoveFavorite_notInFavorites() { + val removed = 4 + val id = "$ID_PREFIX$removed" + + model.changeFavoriteStatus(id, false) + + assertTrue(model.favorites.none { it.controlId == id }) + + verify(callback).onFirstChange() + } + + @Test + fun testRemoveFavorite_endOfElements() { + val removed = 4 + val id = "$ID_PREFIX$removed" + model.changeFavoriteStatus(id, false) + + assertEquals(ControlInfoWrapper( + TEST_COMPONENT, INITIAL_FAVORITES[4], false), model.elements.last()) + verify(callback).onFirstChange() + } + + @Test + fun testRemoveFavorite_adapterNotified() { + val removed = 4 + val id = "$ID_PREFIX$removed" + model.changeFavoriteStatus(id, false) + + val lastPos = model.elements.size - 1 + verify(adapter).notifyItemChanged(eq(lastPos), any(Any::class.java)) + verify(adapter).notifyItemMoved(removed, lastPos) + + verify(callback).onFirstChange() + } + + @Test + fun testRemoveFavorite_dividerMovedBack() { + val oldDividerPosition = getDividerPosition() + val removed = 4 + val id = "$ID_PREFIX$removed" + model.changeFavoriteStatus(id, false) + + assertEquals(oldDividerPosition - 1, getDividerPosition()) + + verify(callback).onFirstChange() + } + + @Test + fun testRemoveFavorite_ShowDivider() { + val oldDividerPosition = getDividerPosition() + val removed = 4 + val id = "$ID_PREFIX$removed" + model.changeFavoriteStatus(id, false) + + assertTrue(dividerWrapper.showDivider) + verify(adapter).notifyItemChanged(oldDividerPosition) + + verify(callback).onFirstChange() + } + + @Test + fun testDoubleRemove_onlyOnce() { + val removed = 4 + val id = "$ID_PREFIX$removed" + model.changeFavoriteStatus(id, false) + model.changeFavoriteStatus(id, false) + + verify(adapter /* only once */).notifyItemChanged(anyInt(), any(Any::class.java)) + verify(adapter /* only once */).notifyItemMoved(anyInt(), anyInt()) + verify(adapter /* only once (divider) */).notifyItemChanged(anyInt()) + + verify(callback).onFirstChange() + } + + @Test + fun testRemoveTwo_InSameOrder() { + val removedFirst = 3 + val removedSecond = 0 + model.changeFavoriteStatus("$ID_PREFIX$removedFirst", false) + model.changeFavoriteStatus("$ID_PREFIX$removedSecond", false) + + assertEquals(listOf( + ControlInfoWrapper(TEST_COMPONENT, INITIAL_FAVORITES[removedFirst], false), + ControlInfoWrapper(TEST_COMPONENT, INITIAL_FAVORITES[removedSecond], false) + ), model.elements.takeLast(2)) + + verify(callback).onFirstChange() + } + + @Test + fun testRemoveAll_showNone() { + INITIAL_FAVORITES.forEach { + model.changeFavoriteStatus(it.controlId, false) + } + assertEquals(dividerWrapper, model.elements.first()) + assertTrue(dividerWrapper.showNone) + verify(adapter, times(2)).notifyItemChanged(anyInt()) // divider + verify(callback).onNoneChanged(true) + + verify(callback).onFirstChange() + } + + @Test + fun testAddFavorite_movedToEnd() { + val added = 2 + val id = "$ID_PREFIX$added" + model.changeFavoriteStatus(id, false) + model.changeFavoriteStatus(id, true) + + assertEquals(id, model.favorites.last().controlId) + + verify(callback).onFirstChange() + } + + @Test + fun testAddFavorite_onlyOnce() { + val added = 2 + val id = "$ID_PREFIX$added" + model.changeFavoriteStatus(id, false) + model.changeFavoriteStatus(id, true) + model.changeFavoriteStatus(id, true) + + // Once for remove and once for add + verify(adapter, times(2)).notifyItemChanged(anyInt(), any(Any::class.java)) + verify(adapter, times(2)).notifyItemMoved(anyInt(), anyInt()) + + verify(callback).onFirstChange() + } + + @Test + fun testAddFavorite_notRemoved() { + val added = 2 + val id = "$ID_PREFIX$added" + model.changeFavoriteStatus(id, true) + + verifyNoMoreInteractions(adapter) + + verify(callback, never()).onFirstChange() + } + + @Test + fun testAddOnlyRemovedFavorite_dividerStopsShowing() { + val added = 2 + val id = "$ID_PREFIX$added" + model.changeFavoriteStatus(id, false) + model.changeFavoriteStatus(id, true) + + assertFalse(dividerWrapper.showDivider) + val inOrder = inOrder(adapter) + inOrder.verify(adapter).notifyItemChanged(model.elements.size - 1) + inOrder.verify(adapter).notifyItemChanged(model.elements.size - 2) + + verify(callback).onFirstChange() + } + + @Test + fun testAddFirstFavorite_dividerNotShowsNone() { + INITIAL_FAVORITES.forEach { + model.changeFavoriteStatus(it.controlId, false) + } + + verify(callback).onNoneChanged(true) + + model.changeFavoriteStatus("${ID_PREFIX}3", true) + assertEquals(1, getDividerPosition()) + + verify(callback).onNoneChanged(false) + + verify(callback).onFirstChange() + } + + @Test + fun testMoveBetweenFavorites() { + val from = 2 + val to = 4 + + model.onMoveItem(from, to) + assertEquals( + listOf(0, 1, 3, 4, 2, 5).map { "$ID_PREFIX$it" }, + model.favorites.map(ControlInfo::controlId) + ) + verify(adapter).notifyItemMoved(from, to) + verify(adapter, never()).notifyItemChanged(anyInt(), any(Any::class.java)) + + verify(callback).onFirstChange() + } + + private fun getDividerPosition(): Int = model.elements.indexOf(dividerWrapper) +}
\ No newline at end of file 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 300cb212ffb1..3af164db4b80 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -32,6 +32,7 @@ import android.service.dreams.IDreamManager; import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.FeatureFlagUtils; import android.view.IWindowManager; import androidx.test.filters.SmallTest; @@ -66,7 +67,7 @@ import java.util.concurrent.Executor; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper() public class GlobalActionsDialogTest extends SysuiTestCase { private GlobalActionsDialog mGlobalActionsDialog; @@ -143,11 +144,60 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mUiEventLogger, mRingerModeTracker ); + mGlobalActionsDialog.setZeroDialogPressDelayForTesting(); } + @Test public void testShouldLogVisibility() { mGlobalActionsDialog.onShow(null); + mTestableLooper.processAllMessages(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN); + } + + @Test + public void testShouldLogBugreportPress() throws InterruptedException { + GlobalActionsDialog.BugReportAction bugReportAction = + mGlobalActionsDialog.makeBugReportActionForTesting(); + bugReportAction.onPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_PRESS); + } + + @Test + public void testShouldLogBugreportLongPress() { + GlobalActionsDialog.BugReportAction bugReportAction = + mGlobalActionsDialog.makeBugReportActionForTesting(); + bugReportAction.onLongPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS); + } + + @Test + public void testShouldLogEmergencyDialerPress() { + GlobalActionsDialog.EmergencyDialerAction emergencyDialerAction = + mGlobalActionsDialog.makeEmergencyDialerActionForTesting(); + emergencyDialerAction.onPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS); + } + + @Test + public void testShouldLogScreenshotPress() { + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialog.makeScreenshotActionForTesting(); + screenshotAction.onPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_PRESS); + } + + @Test + public void testShouldLogScreenshotLongPress() { + FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS, true); + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialog.makeScreenshotActionForTesting(); + screenshotAction.onLongPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_LONG_PRESS); + } + + private void verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent event) { + mTestableLooper.processAllMessages(); verify(mUiEventLogger, times(1)) - .log(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN); + .log(event); } } diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index 190b443227ce..5b052df75ede 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -25,7 +25,7 @@ java_defaults { ], static_libs: [ "androidx.annotation_annotation", - "netd_aidl_interface-unstable-java", + "netd_aidl_interface-V3-java", "netlink-client", "networkstack-aidl-interfaces-unstable-java", "android.hardware.tetheroffload.config-V1.0-java", diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 350980137f3e..cc095a0bb4a7 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -571,9 +571,8 @@ public class TetheringManager { /** * Configure tethering with static IPv4 assignment. * - * The clientAddress must be in the localIPv4Address prefix. A DHCP server will be - * started, but will only be able to offer the client address. The two addresses must - * be in the same prefix. + * A DHCP server will be started, but will only be able to offer the client address. + * The two addresses must be in the same prefix. * * @param localIPv4Address The preferred local IPv4 link address to use. * @param clientAddress The static client address. @@ -584,10 +583,7 @@ public class TetheringManager { @NonNull final LinkAddress clientAddress) { Objects.requireNonNull(localIPv4Address); Objects.requireNonNull(clientAddress); - if (localIPv4Address.getPrefixLength() != clientAddress.getPrefixLength() - || !localIPv4Address.isIpv4() || !clientAddress.isIpv4() - || !new IpPrefix(localIPv4Address.toString()).equals( - new IpPrefix(clientAddress.toString()))) { + if (!checkStaticAddressConfiguration(localIPv4Address, clientAddress)) { throw new IllegalArgumentException("Invalid server or client addresses"); } @@ -657,6 +653,19 @@ public class TetheringManager { } /** + * Check whether the two addresses are ipv4 and in the same prefix. + * @hide + */ + public static boolean checkStaticAddressConfiguration( + @NonNull final LinkAddress localIPv4Address, + @NonNull final LinkAddress clientAddress) { + return localIPv4Address.getPrefixLength() == clientAddress.getPrefixLength() + && localIPv4Address.isIpv4() && clientAddress.isIpv4() + && new IpPrefix(localIPv4Address.toString()).equals( + new IpPrefix(clientAddress.toString())); + } + + /** * Get a TetheringRequestParcel from the configuration * @hide */ diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java index d6bc063210b3..82a26beadacf 100644 --- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java +++ b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java @@ -18,10 +18,12 @@ package android.net.dhcp; import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; -import android.annotation.NonNull; import android.net.LinkAddress; import android.util.ArraySet; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.net.Inet4Address; import java.util.Collection; import java.util.Collections; @@ -160,6 +162,17 @@ public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel { return this; } + /** + * Set the client address to tell DHCP server only offer this address. + * The client's prefix length is the same as server's. + * + * <p>If not set, the default value is null. + */ + public DhcpServingParamsParcelExt setSingleClientAddr(@Nullable Inet4Address clientAddr) { + this.clientAddr = clientAddr == null ? 0 : inet4AddressToIntHTH(clientAddr); + return this; + } + private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) { int[] res = new int[addrs.size()]; int i = 0; diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index 5b6fe91b9c1a..1dac5b784649 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -18,6 +18,7 @@ package android.net.ip; import static android.net.InetAddresses.parseNumericAddress; import static android.net.RouteInfo.RTN_UNICAST; +import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; import static android.net.util.NetworkConstants.FF; @@ -511,17 +512,24 @@ public class IpServer extends StateMachine { } } - private boolean startDhcp(Inet4Address addr, int prefixLen) { + private boolean startDhcp(final LinkAddress serverLinkAddr, final LinkAddress clientLinkAddr) { if (mUsingLegacyDhcp) { return true; } + + final Inet4Address addr = (Inet4Address) serverLinkAddr.getAddress(); + final int prefixLen = serverLinkAddr.getPrefixLength(); + final Inet4Address clientAddr = clientLinkAddr == null ? null : + (Inet4Address) clientLinkAddr.getAddress(); + final DhcpServingParamsParcel params; params = new DhcpServingParamsParcelExt() .setDefaultRouters(addr) .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS) .setDnsServers(addr) - .setServerAddr(new LinkAddress(addr, prefixLen)) - .setMetered(true); + .setServerAddr(serverLinkAddr) + .setMetered(true) + .setSingleClientAddr(clientAddr); // TODO: also advertise link MTU mDhcpServerStartIndex++; @@ -556,9 +564,10 @@ public class IpServer extends StateMachine { } } - private boolean configureDhcp(boolean enable, Inet4Address addr, int prefixLen) { + private boolean configureDhcp(boolean enable, final LinkAddress serverAddr, + final LinkAddress clientAddr) { if (enable) { - return startDhcp(addr, prefixLen); + return startDhcp(serverAddr, clientAddr); } else { stopDhcp(); return true; @@ -606,7 +615,7 @@ public class IpServer extends StateMachine { // code that calls into NetworkManagementService directly. srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR); mIpv4Address = new LinkAddress(srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH); - return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH); + return configureDhcp(enabled, mIpv4Address, null /* clientAddress */); } mIpv4Address = new LinkAddress(srvAddr, prefixLen); } catch (IllegalArgumentException e) { @@ -643,7 +652,7 @@ public class IpServer extends StateMachine { mLinkProperties.removeRoute(route); } - return configureDhcp(enabled, srvAddr, prefixLen); + return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr); } private String getRandomWifiIPv4Address() { @@ -962,7 +971,14 @@ public class IpServer extends StateMachine { } private void maybeConfigureStaticIp(final TetheringRequestParcel request) { - if (request == null) return; + // Ignore static address configuration if they are invalid or null. In theory, static + // addresses should not be invalid here because TetheringManager do not allow caller to + // specify invalid static address configuration. + if (request == null || request.localIPv4Address == null + || request.staticClientAddress == null || !checkStaticAddressConfiguration( + request.localIPv4Address, request.staticClientAddress)) { + return; + } mStaticIpv4ServerAddr = request.localIPv4Address; mStaticIpv4ClientAddr = request.staticClientAddress; diff --git a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java index e8add9830b5f..f8eb1476bad0 100644 --- a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java +++ b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java @@ -42,7 +42,9 @@ import java.util.stream.IntStream; @SmallTest public class DhcpServingParamsParcelExtTest { private static final Inet4Address TEST_ADDRESS = inet4Addr("192.168.0.123"); + private static final Inet4Address TEST_CLIENT_ADDRESS = inet4Addr("192.168.0.42"); private static final int TEST_ADDRESS_PARCELED = 0xc0a8007b; + private static final int TEST_CLIENT_ADDRESS_PARCELED = 0xc0a8002a; private static final int TEST_PREFIX_LENGTH = 17; private static final int TEST_LEASE_TIME_SECS = 120; private static final int TEST_MTU = 1000; @@ -105,6 +107,12 @@ public class DhcpServingParamsParcelExtTest { assertFalse(mParcel.metered); } + @Test + public void testSetClientAddr() { + mParcel.setSingleClientAddr(TEST_CLIENT_ADDRESS); + assertEquals(TEST_CLIENT_ADDRESS_PARCELED, mParcel.clientAddr); + } + private static Inet4Address inet4Addr(String addr) { return (Inet4Address) parseNumericAddress(addr); } diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 3a580dd8e5bd..2955903c84f5 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -38,6 +38,7 @@ import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; +import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; @@ -1668,10 +1669,13 @@ public class TetheringTest { } @Test - public void testRequestStaticServerIp() throws Exception { - final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24"); - final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24"); - final String serverAddr = "192.168.20.1"; + public void testRequestStaticIp() throws Exception { + final LinkAddress serverLinkAddr = new LinkAddress("192.168.0.123/24"); + final LinkAddress clientLinkAddr = new LinkAddress("192.168.0.42/24"); + final String serverAddr = "192.168.0.123"; + final int clientAddrParceled = 0xc0a8002a; + final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor = + ArgumentCaptor.forClass(DhcpServingParamsParcel.class); mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, serverLinkAddr, clientLinkAddr), null); mLooper.dispatchAll(); @@ -1680,8 +1684,12 @@ public class TetheringTest { sendUsbBroadcast(true, true, true, TETHERING_USB); mLooper.dispatchAll(); verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr))); - - // TODO: test static client address. + verify(mIpServerDependencies, times(1)).makeDhcpServer(any(), dhcpParamsCaptor.capture(), + any()); + final DhcpServingParamsParcel params = dhcpParamsCaptor.getValue(); + assertEquals(serverAddr, intToInet4AddressHTH(params.serverAddr).getHostAddress()); + assertEquals(24, params.serverAddrPrefixLength); + assertEquals(clientAddrParceled, params.clientAddr); } // TODO: Test that a request for hotspot mode doesn't interfere with an diff --git a/proto/src/typed_features.proto b/proto/src/typed_features.proto new file mode 100644 index 000000000000..c2b3b18ea7c8 --- /dev/null +++ b/proto/src/typed_features.proto @@ -0,0 +1,25 @@ +/* + * 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.service; +option java_multiple_files = true; + +// This message is to specify feature params that are a list of strings. +message StringListParamProto { + repeated string element = 1; +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/am/BugReportHandlerUtil.java b/services/core/java/com/android/server/am/BugReportHandlerUtil.java index ba89fce0b3f8..0a0d8d85f5c3 100644 --- a/services/core/java/com/android/server/am/BugReportHandlerUtil.java +++ b/services/core/java/com/android/server/am/BugReportHandlerUtil.java @@ -16,15 +16,20 @@ package com.android.server.am; +import static android.app.AppOpsManager.OP_NONE; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import android.app.Activity; import android.app.BroadcastOptions; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Binder; +import android.os.BugreportManager; +import android.os.BugreportParams; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -43,6 +48,8 @@ public final class BugReportHandlerUtil { private static final String SHELL_APP_PACKAGE = "com.android.shell"; private static final String INTENT_BUGREPORT_REQUESTED = "com.android.internal.intent.action.BUGREPORT_REQUESTED"; + private static final String INTENT_GET_BUGREPORT_HANDLER_RESPONSE = + "com.android.internal.intent.action.GET_BUGREPORT_HANDLER_RESPONSE"; /** * Check is BugReportHandler enabled on the device. @@ -100,6 +107,43 @@ public final class BugReportHandlerUtil { return false; } + if (getBugReportHandlerAppResponseReceivers(context, handlerApp, handlerUser).isEmpty()) { + // Just try to launch bugreport handler app to handle bugreport request + // because the bugreport handler app is old and not support to provide response to + // let BugReportHandlerUtil know it is available or not. + launchBugReportHandlerApp(context, handlerApp, handlerUser); + return true; + } + + Slog.i(TAG, "Getting response from bug report handler app: " + handlerApp); + Intent intent = new Intent(INTENT_GET_BUGREPORT_HANDLER_RESPONSE); + intent.setPackage(handlerApp); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + final long identity = Binder.clearCallingIdentity(); + try { + // Handler app's BroadcastReceiver should call setResultCode(Activity.RESULT_OK) to + // let BugreportHandlerResponseBroadcastReceiver know the handler app is available. + context.sendOrderedBroadcastAsUser(intent, + UserHandle.of(handlerUser), + android.Manifest.permission.DUMP, + OP_NONE, /* options= */ null, + new BugreportHandlerResponseBroadcastReceiver(handlerApp, handlerUser), + /* scheduler= */ null, + Activity.RESULT_CANCELED, + /* initialData= */ null, + /* initialExtras= */ null); + } catch (RuntimeException e) { + Slog.e(TAG, "Error while trying to get response from bug report handler app.", e); + return false; + } finally { + Binder.restoreCallingIdentity(identity); + } + return true; + } + + private static void launchBugReportHandlerApp(Context context, String handlerApp, + int handlerUser) { Slog.i(TAG, "Launching bug report handler app: " + handlerApp); Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED); intent.setPackage(handlerApp); @@ -115,11 +159,9 @@ public final class BugReportHandlerUtil { options.toBundle()); } catch (RuntimeException e) { Slog.e(TAG, "Error while trying to launch bugreport handler app.", e); - return false; } finally { Binder.restoreCallingIdentity(identity); } - return true; } private static String getCustomBugReportHandlerApp(Context context) { @@ -159,6 +201,16 @@ public final class BugReportHandlerUtil { handlerUser); } + private static List<ResolveInfo> getBugReportHandlerAppResponseReceivers(Context context, + String handlerApp, int handlerUser) { + // Use the app package and the user id to retrieve the receiver that can provide response + Intent intent = new Intent(INTENT_GET_BUGREPORT_HANDLER_RESPONSE); + intent.setPackage(handlerApp); + return context.getPackageManager() + .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY, + handlerUser); + } + private static String getDefaultBugReportHandlerApp(Context context) { return context.getResources().getString( com.android.internal.R.string.config_defaultBugReportHandlerApp); @@ -176,4 +228,30 @@ public final class BugReportHandlerUtil { Binder.restoreCallingIdentity(identity); } } + + private static class BugreportHandlerResponseBroadcastReceiver extends BroadcastReceiver { + private final String handlerApp; + private final int handlerUser; + + BugreportHandlerResponseBroadcastReceiver(String handlerApp, int handlerUser) { + this.handlerApp = handlerApp; + this.handlerUser = handlerUser; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (getResultCode() == Activity.RESULT_OK) { + // Try to launch bugreport handler app to handle bugreport request because the + // bugreport handler app is available. + launchBugReportHandlerApp(context, handlerApp, handlerUser); + return; + } + + Slog.w(TAG, "Request bug report because no response from handler app."); + BugreportManager bugreportManager = context.getSystemService(BugreportManager.class); + bugreportManager.requestBugreport( + new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE), + /* shareTitle= */null, /* shareDescription= */ null); + } + } } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index 2aa53cc3882e..a5de90c93aab 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -169,6 +169,8 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider } public void rebindIfDisconnected() { + //TODO: When we are connecting to the service, calling this will unbind and bind again. + // We'd better not unbind if we are connecting. if (mActiveConnection == null && shouldBind()) { unbind(); bind(); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java index fe118e51cdce..b688e0922d49 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java @@ -83,7 +83,7 @@ final class MediaRoute2ProviderWatcher { // Scan packages. // Also has the side-effect of restarting providers if needed. - mHandler.post(mScanPackagesRunnable); + postScanPackagesIfNeeded(); } } @@ -92,7 +92,7 @@ final class MediaRoute2ProviderWatcher { mRunning = false; mContext.unregisterReceiver(mScanPackagesReceiver); - mHandler.removeCallbacks(mScanPackagesRunnable); + mHandler.removeCallbacks(this::scanPackages); // Stop all providers. for (int i = mProxies.size() - 1; i >= 0; i--) { @@ -154,20 +154,19 @@ final class MediaRoute2ProviderWatcher { return -1; } + private void postScanPackagesIfNeeded() { + if (!mHandler.hasCallbacks(this::scanPackages)) { + mHandler.post(this::scanPackages); + } + } + private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) { Slog.d(TAG, "Received package manager broadcast: " + intent); } - scanPackages(); - } - }; - - private final Runnable mScanPackagesRunnable = new Runnable() { - @Override - public void run() { - scanPackages(); + postScanPackagesIfNeeded(); } }; diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java index 43fc7ed0e39d..90c85ada9def 100644 --- a/services/core/java/com/android/server/om/IdmapManager.java +++ b/services/core/java/com/android/server/om/IdmapManager.java @@ -63,20 +63,23 @@ class IdmapManager { mIdmapDaemon = IdmapDaemon.getInstance(); } + /** + * Creates the idmap for the target/overlay combination and returns whether the idmap file was + * modified. + */ boolean createIdmap(@NonNull final PackageInfo targetPackage, @NonNull final PackageInfo overlayPackage, int userId) { if (DEBUG) { Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and " + overlayPackage.packageName); } - final int sharedGid = UserHandle.getSharedAppGid(targetPackage.applicationInfo.uid); final String targetPath = targetPackage.applicationInfo.getBaseCodePath(); final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath(); try { int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId); boolean enforce = enforceOverlayable(overlayPackage); if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) { - return true; + return false; } return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies, enforce, userId) != null; diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index d108e76e37df..05a4a38feef1 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -700,14 +700,15 @@ final class OverlayManagerServiceImpl { final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName, userId); - // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native layers. + // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native + // layers. + boolean modified = false; if (targetPackage != null && overlayPackage != null && !("android".equals(targetPackageName) && !isPackageConfiguredMutable(overlayPackageName))) { - mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); + modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); } - boolean modified = false; if (overlayPackage != null) { modified |= mSettings.setBaseCodePath(overlayPackageName, userId, overlayPackage.applicationInfo.getBaseCodePath()); diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING index 75229a1adccc..6edd76f1810a 100644 --- a/services/core/java/com/android/server/om/TEST_MAPPING +++ b/services/core/java/com/android/server/om/TEST_MAPPING @@ -15,6 +15,9 @@ "name": "OverlayHostTests" }, { + "name": "OverlayRemountedTest" + }, + { "name": "CtsAppSecurityHostTestCases", "options": [ { diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index eb7057dd2994..6dcf71e9fbf0 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -232,6 +232,10 @@ public class DexManager { private boolean isSystemServerDexPathSupportedForOdex(String dexPath) { ArrayList<PackagePartitions.SystemPartition> partitions = PackagePartitions.getOrderedPartitions(identity()); + // First check the apex partition as it's not part of the SystemPartitions. + if (dexPath.startsWith("/apex/")) { + return true; + } for (int i = 0; i < partitions.size(); i++) { if (partitions.get(i).containsPath(dexPath)) { return true; diff --git a/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java b/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java new file mode 100644 index 000000000000..7cdb84ba0404 --- /dev/null +++ b/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.stats.pull; + +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE; +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE; +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE; +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.provider.DeviceConfig; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Slog; +import android.util.StatsEvent; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; +import com.android.service.nano.StringListParamProto; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods for creating {@link StatsEvent} data. + */ +final class SettingsStatsUtil { + private static final String TAG = "SettingsStatsUtil"; + private static final FlagsData[] GLOBAL_SETTINGS = new FlagsData[]{ + new FlagsData("GlobalFeature__boolean_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE), + new FlagsData("GlobalFeature__integer_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE), + new FlagsData("GlobalFeature__float_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE), + new FlagsData("GlobalFeature__string_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE) + }; + private static final FlagsData[] SECURE_SETTINGS = new FlagsData[]{ + new FlagsData("SecureFeature__boolean_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE), + new FlagsData("SecureFeature__integer_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE), + new FlagsData("SecureFeature__float_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE), + new FlagsData("SecureFeature__string_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE) + }; + private static final FlagsData[] SYSTEM_SETTINGS = new FlagsData[]{ + new FlagsData("SystemFeature__boolean_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE), + new FlagsData("SystemFeature__integer_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE), + new FlagsData("SystemFeature__float_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE), + new FlagsData("SystemFeature__string_whitelist", + SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE) + }; + + @VisibleForTesting + @NonNull + static List<StatsEvent> logGlobalSettings(Context context, int atomTag, int userId) { + final List<StatsEvent> output = new ArrayList<>(); + final ContentResolver resolver = context.getContentResolver(); + + for (FlagsData flagsData : GLOBAL_SETTINGS) { + StringListParamProto proto = getList(flagsData.mFlagName); + if (proto == null) { + continue; + } + for (String key : proto.element) { + final String value = Settings.Global.getStringForUser(resolver, key, userId); + output.add(createStatsEvent(atomTag, key, value, userId, + flagsData.mDataType)); + } + } + return output; + } + + @NonNull + static List<StatsEvent> logSystemSettings(Context context, int atomTag, int userId) { + final List<StatsEvent> output = new ArrayList<>(); + final ContentResolver resolver = context.getContentResolver(); + + for (FlagsData flagsData : SYSTEM_SETTINGS) { + StringListParamProto proto = getList(flagsData.mFlagName); + if (proto == null) { + continue; + } + for (String key : proto.element) { + final String value = Settings.System.getStringForUser(resolver, key, userId); + output.add(createStatsEvent(atomTag, key, value, userId, + flagsData.mDataType)); + } + } + return output; + } + + @NonNull + static List<StatsEvent> logSecureSettings(Context context, int atomTag, int userId) { + final List<StatsEvent> output = new ArrayList<>(); + final ContentResolver resolver = context.getContentResolver(); + + for (FlagsData flagsData : SECURE_SETTINGS) { + StringListParamProto proto = getList(flagsData.mFlagName); + if (proto == null) { + continue; + } + for (String key : proto.element) { + final String value = Settings.Secure.getStringForUser(resolver, key, userId); + output.add(createStatsEvent(atomTag, key, value, userId, + flagsData.mDataType)); + } + } + return output; + } + + @VisibleForTesting + @Nullable + static StringListParamProto getList(String flag) { + final String base64 = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, flag); + if (TextUtils.isEmpty(base64)) { + return null; + } + final byte[] decode = Base64.decode(base64, Base64.NO_PADDING | Base64.NO_WRAP); + StringListParamProto list = null; + try { + list = StringListParamProto.parseFrom(decode); + } catch (Exception e) { + Slog.e(TAG, "Error parsing string list proto", e); + } + return list; + } + + /** + * Create {@link StatsEvent} for SETTING_SNAPSHOT atom + */ + @NonNull + private static StatsEvent createStatsEvent(int atomTag, String key, String value, int userId, + int type) { + final StatsEvent.Builder builder = StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeString(key); + boolean booleanValue = false; + int intValue = 0; + float floatValue = 0; + String stringValue = ""; + if (TextUtils.isEmpty(value)) { + builder.writeInt(FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__NOTASSIGNED) + .writeBoolean(booleanValue) + .writeInt(intValue) + .writeFloat(floatValue) + .writeString(stringValue) + .writeInt(userId); + } else { + switch (type) { + case SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE: + booleanValue = "1".equals(value); + break; + case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE: + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + Slog.w(TAG, "Can not parse value to float: " + value); + } + break; + case SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE: + try { + floatValue = Float.parseFloat(value); + } catch (NumberFormatException e) { + Slog.w(TAG, "Can not parse value to float: " + value); + } + break; + case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE: + stringValue = value; + break; + default: + Slog.w(TAG, "Unexpected value type " + type); + } + builder.writeInt(type) + .writeBoolean(booleanValue) + .writeInt(intValue) + .writeFloat(floatValue) + .writeString(stringValue) + .writeInt(userId); + } + return builder.build(); + } + + /** Class for defining flag name and its data type. */ + static final class FlagsData { + /** {@link DeviceConfig} flag name, value of the flag is {@link StringListParamProto} */ + String mFlagName; + /** Data type of the value getting from {@link Settings} keys. */ + int mDataType; + + FlagsData(String flagName, int dataType) { + mFlagName = flagName; + mDataType = dataType; + } + } +} diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index e9da2c4c8472..288c22a94b45 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -416,6 +416,8 @@ public class StatsPullAtomService extends SystemService { return pullHealthHal(atomTag, data); case FrameworkStatsLog.ATTRIBUTED_APP_OPS: return pullAttributedAppOps(atomTag, data); + case FrameworkStatsLog.SETTING_SNAPSHOT: + return pullSettingsStats(atomTag, data); default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); } @@ -580,6 +582,7 @@ public class StatsPullAtomService extends SystemService { registerFullBatteryCapacity(); registerBatteryVoltage(); registerBatteryCycleCount(); + registerSettingsStats(); } /** @@ -3244,6 +3247,43 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } + private void registerSettingsStats() { + int tagId = FrameworkStatsLog.SETTING_SNAPSHOT; + mStatsManager.setPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + BackgroundThread.getExecutor(), + mStatsCallbackImpl + ); + } + + int pullSettingsStats(int atomTag, List<StatsEvent> pulledData) { + UserManager userManager = mContext.getSystemService(UserManager.class); + if (userManager == null) { + return StatsManager.PULL_SKIP; + } + + final long token = Binder.clearCallingIdentity(); + try { + for (UserInfo user : userManager.getUsers()) { + final int userId = user.getUserHandle().getIdentifier(); + + if (userId == UserHandle.USER_SYSTEM) { + pulledData.addAll(SettingsStatsUtil.logGlobalSettings(mContext, atomTag, + UserHandle.USER_SYSTEM)); + } + pulledData.addAll(SettingsStatsUtil.logSystemSettings(mContext, atomTag, userId)); + pulledData.addAll(SettingsStatsUtil.logSecureSettings(mContext, atomTag, userId)); + } + } catch (Exception e) { + Slog.e(TAG, "failed to pullSettingsStats", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + // Thermal event received from vendor thermal management subsystem private static final class ThermalEventListener extends IThermalEventListener.Stub { @Override diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 856fbc70438e..ed9b01916657 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7368,7 +7368,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } final ActivityStack stack = getRootTask(); return stack != null && - stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, true /* isTop */); + stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, + stack.topRunningActivity() == this /* isTop */); } void setTurnScreenOn(boolean turnScreenOn) { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 464839828989..a215a6137291 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -757,7 +757,7 @@ class ActivityStack extends Task { // warning toast about it. mAtmService.getTaskChangeNotificationController() .notifyActivityDismissingDockedStack(); - taskDisplayArea.onSplitScreenModeDismissed(); + taskDisplayArea.onSplitScreenModeDismissed(this); } } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 0470ffa6cdca..9de8ef776b41 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -2198,7 +2198,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // split-screen in split-screen. mService.getTaskChangeNotificationController() .notifyActivityDismissingDockedStack(); - taskDisplayArea.onSplitScreenModeDismissed(); + taskDisplayArea.onSplitScreenModeDismissed(task.getStack()); taskDisplayArea.mDisplayContent.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS, true /* notifyClients */); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 78ee1de78079..6f1ddcd793a9 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -403,11 +403,18 @@ public class AppTransition implements Dump { mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; } - boolean isNextAppTransitionOpenCrossProfileApps() { return mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS; } + boolean isNextAppTransitionCustomFromRecents() { + final RecentTasks recentTasks = mService.mAtmService.getRecentTasks(); + final String recentsPackageName = + (recentTasks != null) ? recentTasks.getRecentsComponent().getPackageName() : null; + return mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM + && mNextAppTransitionPackage.equals(recentsPackageName); + } + /** * @return true if and only if we are currently fetching app transition specs from the future * passed into {@link #overridePendingAppTransitionMultiThumbFuture} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 40243e8bbedf..80a1a4592ff3 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -198,6 +198,7 @@ import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; +import android.window.ITaskOrganizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -3421,10 +3422,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) { // Always update control target. This is needed to handle rotation. - // We cannot set target as the control target, because mInputMethodTarget can only help - // decide the z-order of IME, but cannot control IME. Only the IME target reported from - // updateInputMethodTargetWindow can control IME. - updateImeControlTarget(mInputMethodControlTarget); + updateImeControlTarget(target); if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) { return; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index a031fe82d48b..0a9878dd660b 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -442,10 +442,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // Always prepare an app transition since we rely on the transition callbacks to cleanup mWindowManager.prepareAppTransition(TRANSIT_NONE, false); controller.setCancelOnNextTransitionStart(); - } else { - // Just cancel directly to unleash from launcher when the next launching task is the - // current top task. - mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "stackOrderChanged"); } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 6fda1170a3f5..54210ae1c0b0 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -43,6 +43,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.IntArray; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; @@ -99,6 +100,8 @@ public class RecentsAnimationController implements DeathRecipient { private IRecentsAnimationRunner mRunner; private final RecentsAnimationCallbacks mCallbacks; private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>(); + private final IntArray mPendingNewTaskTargets = new IntArray(0); + private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations = new ArrayList<>(); private final int mDisplayId; @@ -220,6 +223,10 @@ public class RecentsAnimationController implements DeathRecipient { if (mCanceled) { return; } + // Remove all new task targets. + for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) { + removeTaskInternal(mPendingNewTaskTargets.get(i)); + } } // Note, the callback will handle its own synchronization, do not lock on WM lock @@ -310,6 +317,18 @@ public class RecentsAnimationController implements DeathRecipient { mWillFinishToHome = willFinishToHome; } } + + @Override + public boolean removeTask(int taskId) { + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.getWindowManagerLock()) { + return removeTaskInternal(taskId); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } }; /** @@ -405,11 +424,17 @@ public class RecentsAnimationController implements DeathRecipient { @VisibleForTesting AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) { + return addAnimation(task, isRecentTaskInvisible, null /* finishedCallback */); + } + + @VisibleForTesting + AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible, + OnAnimationFinishedCallback finishedCallback) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName()); final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task, isRecentTaskInvisible); task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */, - ANIMATION_TYPE_RECENTS); + ANIMATION_TYPE_RECENTS, finishedCallback); task.commitPendingTransaction(); mPendingAnimations.add(taskAdapter); return taskAdapter; @@ -489,6 +514,49 @@ public class RecentsAnimationController implements DeathRecipient { } } + void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) { + if (mRunner != null) { + final RemoteAnimationTarget target = createTaskRemoteAnimation(task, finishedCallback); + if (target == null) return; + + ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target); + try { + mRunner.onTaskAppeared(target); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to report task appeared", e); + } + } + } + + private RemoteAnimationTarget createTaskRemoteAnimation(Task task, + OnAnimationFinishedCallback finishedCallback) { + final SparseBooleanArray recentTaskIds = + mService.mAtmService.getRecentTasks().getRecentTaskIds(); + TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task, + !recentTaskIds.get(task.mTaskId), finishedCallback); + mPendingNewTaskTargets.add(task.mTaskId); + return adapter.createRemoteAnimationTarget(); + } + + private boolean removeTaskInternal(int taskId) { + boolean result = false; + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + // Only allows when task target has became visible to user, to prevent + // the flickering during remove animation and task visible. + final TaskAnimationAdapter target = mPendingAnimations.get(i); + if (target.mTask.mTaskId == taskId && target.mTask.isOnTop()) { + removeAnimation(target); + final int taskIndex = mPendingNewTaskTargets.indexOf(taskId); + if (taskIndex != -1) { + mPendingNewTaskTargets.remove(taskIndex); + } + result = true; + break; + } + } + return result; + } + private RemoteAnimationTarget[] createAppAnimations() { final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 11fda369ec5f..26d6c78b5d33 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -66,6 +66,7 @@ import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS; +import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT; import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER; import static com.android.server.wm.RootWindowContainerProto.PENDING_ACTIVITIES; @@ -664,7 +665,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> void setSecureSurfaceState(int userId, boolean disabled) { forAllWindows((w) -> { - if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) { + if (w.mHasSurface && userId == w.mShowUserId) { w.mWinAnimator.setSecureLocked(disabled); } }, true /* traverseTopToBottom */); @@ -1514,6 +1515,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Updates the extra information of the intent. if (fromHomeKey) { homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true); + mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "startHomeActivity"); } // Update the reason for ANR debugging to verify if the user activity is the one that // actually launched. diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 7d8a56e6c468..56147f216e73 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -162,7 +162,19 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, - outInsetsState, outActiveControls); + outInsetsState, outActiveControls, UserHandle.getUserId(mUid)); + } + + + @Override + public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs, + int viewVisibility, int displayId, int userId, Rect outFrame, + Rect outContentInsets, Rect outStableInsets, + DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { + return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, + outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, + outInsetsState, outActiveControls, userId); } @Override @@ -172,7 +184,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, new Rect() /* outFrame */, outContentInsets, outStableInsets, new DisplayCutout.ParcelableWrapper() /* cutout */, null /* outInputChannel */, - outInsetsState, null); + outInsetsState, null, UserHandle.getUserId(mUid)); } @Override diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 1e70573a2ada..fd32724d82a3 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -86,6 +86,8 @@ import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; +import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; +import static com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.TASK; @@ -135,6 +137,7 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.Surface; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.ITaskOrganizer; import com.android.internal.annotations.VisibleForTesting; @@ -3402,6 +3405,24 @@ class Task extends WindowContainer<WindowContainer> { } @Override + protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, + int transit, boolean isVoiceInteraction, + @Nullable OnAnimationFinishedCallback finishedCallback) { + final RecentsAnimationController control = mWmService.getRecentsAnimationController(); + if (control != null && enter + && getDisplayContent().mAppTransition.isNextAppTransitionCustomFromRecents()) { + ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, + "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s", + control, asTask(), AppTransition.appTransitionToString(transit)); + // We let the transition to be controlled by RecentsAnimation, and callback task's + // RemoteAnimationTarget for remote runner to animate. + control.addTaskToTargets(getRootTask(), finishedCallback); + } else { + super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback); + } + } + + @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { super.dump(pw, prefix, dumpAll); final String doublePrefix = prefix + " "; diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index fee1c8019a07..735bef87c94b 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1165,17 +1165,23 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } void onSplitScreenModeDismissed() { + onSplitScreenModeDismissed(null /* toTop */); + } + + void onSplitScreenModeDismissed(ActivityStack toTop) { mAtmService.deferWindowLayout(); try { mLaunchRootTask = null; moveSplitScreenTasksToFullScreen(); } finally { - final ActivityStack topFullscreenStack = - getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN); + final ActivityStack topFullscreenStack = toTop != null + ? toTop : getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN); final ActivityStack homeStack = getOrCreateRootHomeTask(); - if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) { + if (homeStack != null && ((topFullscreenStack != null && !isTopStack(homeStack)) + || toTop != null)) { // Whenever split-screen is dismissed we want the home stack directly behind the // current top fullscreen stack so it shows up when the top stack is finished. + // Or, if the caller specified a stack to be on top after split-screen is dismissed. // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch // once we have that. diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index ecc07bd4de01..fba22dd4e9df 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2076,8 +2076,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @see #getAnimationAdapter */ boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - boolean isVoiceInteraction, - @Nullable OnAnimationFinishedCallback animationFinishedCallback) { + boolean isVoiceInteraction, @Nullable OnAnimationFinishedCallback finishedCallback) { if (mWmService.mDisableTransitionAnimation) { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: transition animation is disabled or skipped. " @@ -2092,22 +2091,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation"); if (okToAnimate()) { - final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, - transit, enter, isVoiceInteraction); - AnimationAdapter adapter = adapters.first; - AnimationAdapter thumbnailAdapter = adapters.second; - if (adapter != null) { - startAnimation(getPendingTransaction(), adapter, !isVisible(), - ANIMATION_TYPE_APP_TRANSITION, animationFinishedCallback); - if (adapter.getShowWallpaper()) { - getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; - } - if (thumbnailAdapter != null) { - mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(), - thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, - (type, anim) -> { }); - } - } + applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback); } else { cancelAnimation(); } @@ -2201,6 +2185,26 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return resultAdapters; } + protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, + int transit, boolean isVoiceInteraction, + @Nullable OnAnimationFinishedCallback finishedCallback) { + final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, + transit, enter, isVoiceInteraction); + AnimationAdapter adapter = adapters.first; + AnimationAdapter thumbnailAdapter = adapters.second; + if (adapter != null) { + startAnimation(getPendingTransaction(), adapter, !isVisible(), + ANIMATION_TYPE_APP_TRANSITION, finishedCallback); + if (adapter.getShowWallpaper()) { + getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + } + if (thumbnailAdapter != null) { + mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(), + thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { }); + } + } + } + final SurfaceAnimationRunner getSurfaceAnimationRunner() { return mWmService.mSurfaceAnimationRunner; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c3fb68f9e0c4..9d879765f8df 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -25,6 +25,7 @@ import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS; import static android.Manifest.permission.RESTRICTED_VR_ACCESS; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; @@ -1359,7 +1360,8 @@ public class WindowManagerService extends IWindowManager.Stub LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, - InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls, + int requestUserId) { int[] appOp = new int[1]; final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0; @@ -1428,6 +1430,20 @@ public class WindowManagerService extends IWindowManager.Stub return WindowManagerGlobal.ADD_INVALID_DISPLAY; } + int userId = UserHandle.getUserId(session.mUid); + if (requestUserId != userId) { + try { + mAmInternal.handleIncomingUser(callingPid, callingUid, requestUserId, + false /*allowAll*/, ALLOW_NON_FULL, null, null); + } catch (Exception exp) { + ProtoLog.w(WM_ERROR, "Trying to add window with invalid user=%d", + requestUserId); + return WindowManagerGlobal.ADD_INVALID_USER; + } + // It's fine to use this userId + userId = requestUserId; + } + ActivityRecord activity = null; final boolean hasParent = parentWindow != null; // Use existing parent window token for child windows since they go in the same token @@ -1516,7 +1532,7 @@ public class WindowManagerService extends IWindowManager.Stub } final WindowState win = new WindowState(this, session, client, token, parentWindow, - appOp[0], seq, attrs, viewVisibility, session.mUid, + appOp[0], seq, attrs, viewVisibility, session.mUid, userId, session.mCanAddInternalSystemWindow); if (win.mDeathRecipient == null) { // Client has apparently died, so there is no reason to @@ -1836,8 +1852,7 @@ public class WindowManagerService extends IWindowManager.Stub if ((w.mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { return true; } - if (DevicePolicyCache.getInstance().getScreenCaptureDisabled( - UserHandle.getUserId(w.mOwnerUid))) { + if (DevicePolicyCache.getInstance().getScreenCaptureDisabled(w.mShowUserId)) { return true; } return false; @@ -7407,7 +7422,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { WindowState window = mWindowMap.get(token); if (window != null) { - return UserHandle.getUserId(window.mOwnerUid); + return window.mShowUserId; } return UserHandle.USER_NULL; } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 3e2e9be24c4f..86bc013e3638 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -156,7 +156,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final PooledConsumer f = PooledLambda.obtainConsumer( ActivityRecord::ensureActivityConfiguration, PooledLambda.__(ActivityRecord.class), 0, - false /* preserveWindow */); + true /* preserveWindow */); try { for (int i = haveConfigChanges.size() - 1; i >= 0; --i) { haveConfigChanges.valueAt(i).forAllActivities(f); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 627fdc342a9a..c11c29b5deb6 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -191,7 +191,6 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; -import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; import android.text.TextUtils; @@ -269,6 +268,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final int mAppOp; // UserId and appId of the owner. Don't display windows of non-current user. final int mOwnerUid; + /** + * Requested userId, if this is not equals with the userId from mOwnerUid, then this window is + * created for secondary user. + * Use this member instead of get userId from mOwnerUid while query for visibility. + */ + final int mShowUserId; /** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */ final boolean mOwnerCanAddInternalSystemWindow; final WindowId mWindowId; @@ -806,8 +811,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a, - int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) { - this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId, + int viewVisibility, int ownerId, int showUserId, + boolean ownerCanAddInternalSystemWindow) { + this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId, showUserId, ownerCanAddInternalSystemWindow, new PowerManagerWrapper() { @Override public void wakeUp(long time, @WakeReason int reason, String details) { @@ -823,8 +829,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a, - int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow, - PowerManagerWrapper powerManagerWrapper) { + int viewVisibility, int ownerId, int showUserId, + boolean ownerCanAddInternalSystemWindow, PowerManagerWrapper powerManagerWrapper) { super(service); mSession = s; mClient = c; @@ -832,6 +838,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mToken = token; mActivityRecord = mToken.asActivityRecord(); mOwnerUid = ownerId; + mShowUserId = showUserId; mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow; mWindowId = new WindowId(this); mAttrs.copyFrom(a); @@ -3275,7 +3282,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } return win.showForAllUsers() - || mWmService.isCurrentProfile(UserHandle.getUserId(win.mOwnerUid)); + || mWmService.isCurrentProfile(win.mShowUserId); } private static void applyInsets(Region outRegion, Rect frame, Rect inset) { @@ -3795,7 +3802,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(HASH_CODE, System.identityHashCode(this)); - proto.write(USER_ID, UserHandle.getUserId(mOwnerUid)); + proto.write(USER_ID, mShowUserId); final CharSequence title = getWindowTag(); if (title != null) { proto.write(TITLE, title.toString()); @@ -3979,7 +3986,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mLastTitle = title; mWasExiting = mAnimatingExit; mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this)) - + " u" + UserHandle.getUserId(mOwnerUid) + + " u" + mShowUserId + " " + mLastTitle + (mAnimatingExit ? " EXITING}" : "}"); } return mStringNameCache; diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 5976b48db764..e34b81654c72 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -554,13 +554,12 @@ class WindowToken extends WindowContainer<WindowState> { // cleared and the configuration is restored from parent. if (!changed) { clearFixedRotationTransform(null /* applyDisplayRotation */); - onConfigurationChanged(getParent().getConfiguration()); } } /** - * Clears the transform and apply display rotation if the action is given. The caller needs to - * refresh the configuration of this container after this method call. + * Clears the transform and apply display rotation if the action is given. If the display will + * not rotate, the transformed containers are restored to their original states. */ void clearFixedRotationTransform(Runnable applyDisplayRotation) { final FixedRotationTransformState state = mFixedRotationTransformState; @@ -574,6 +573,12 @@ class WindowToken extends WindowContainer<WindowState> { state.mIsTransforming = false; if (applyDisplayRotation != null) { applyDisplayRotation.run(); + } else { + // The display will not rotate to the rotation of this container, let's cancel them. + for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) { + state.mAssociatedTokens.get(i).cancelFixedRotationTransform(); + } + cancelFixedRotationTransform(); } // The state is cleared at the end, because it is used to indicate that other windows can // use seamless rotation when applying rotation to display. @@ -583,6 +588,16 @@ class WindowToken extends WindowContainer<WindowState> { mFixedRotationTransformState = null; } + /** Restores the changes that applies to this container. */ + private void cancelFixedRotationTransform() { + final WindowContainer<?> parent = getParent(); + if (parent == null) { + // The window may be detached or detaching. + return; + } + onConfigurationChanged(parent.getConfiguration()); + } + @Override void resolveOverrideConfiguration(Configuration newParentConfig) { super.resolveOverrideConfiguration(newParentConfig); diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 2dbbc5ac6806..aabc58c3a297 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -17,6 +17,7 @@ #include "BinderIncrementalService.h" #include <android-base/logging.h> +#include <android-base/no_destructor.h> #include <binder/IResultReceiver.h> #include <binder/PermissionCache.h> #include <incfs.h> @@ -93,8 +94,8 @@ BinderIncrementalService* BinderIncrementalService::start() { } status_t BinderIncrementalService::dump(int fd, const Vector<String16>&) { - static const String16 kDump("android.permission.DUMP"); - if (!PermissionCache::checkCallingPermission(kDump)) { + static const android::base::NoDestructor<String16> kDump("android.permission.DUMP"); + if (!PermissionCache::checkCallingPermission(*kDump)) { return PERMISSION_DENIED; } mImpl.onDump(fd); diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 92366e51eb47..eb65a2ddc5f1 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -20,6 +20,7 @@ #include <android-base/file.h> #include <android-base/logging.h> +#include <android-base/no_destructor.h> #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> @@ -163,7 +164,9 @@ const bool IncrementalService::sEnablePerfLogging = android::base::GetBoolProperty("incremental.perflogging", false); IncrementalService::IncFsMount::~IncFsMount() { - incrementalService.mDataLoaderManager->destroyDataLoader(mountId); + if (dataLoaderStub) { + dataLoaderStub->destroy(); + } LOG(INFO) << "Unmounting and cleaning up mount " << mountId << " with root '" << root << '\''; for (auto&& [target, _] : bindPoints) { LOG(INFO) << "\tbind: " << target; @@ -288,9 +291,12 @@ void IncrementalService::onDump(int fd) { dprintf(fd, "\t\tmountId: %d\n", mnt.mountId); dprintf(fd, "\t\troot: %s\n", mnt.root.c_str()); dprintf(fd, "\t\tnextStorageDirNo: %d\n", mnt.nextStorageDirNo.load()); - dprintf(fd, "\t\tdataLoaderStatus: %d\n", mnt.dataLoaderStatus.load()); - { - const auto& params = mnt.dataLoaderParams; + if (mnt.dataLoaderStub) { + const auto& dataLoaderStub = *mnt.dataLoaderStub; + dprintf(fd, "\t\tdataLoaderStatus: %d\n", dataLoaderStub.status()); + dprintf(fd, "\t\tdataLoaderStartRequested: %s\n", + dataLoaderStub.startRequested() ? "true" : "false"); + const auto& params = dataLoaderStub.params(); dprintf(fd, "\t\tdataLoaderParams:\n"); dprintf(fd, "\t\t\ttype: %s\n", toString(params.type).c_str()); dprintf(fd, "\t\t\tpackageName: %s\n", params.packageName.c_str()); @@ -321,10 +327,9 @@ void IncrementalService::onDump(int fd) { } } -std::optional<std::future<void>> IncrementalService::onSystemReady() { - std::promise<void> threadFinished; +void IncrementalService::onSystemReady() { if (mSystemReady.exchange(true)) { - return {}; + return; } std::vector<IfsMountPtr> mounts; @@ -338,8 +343,8 @@ std::optional<std::future<void>> IncrementalService::onSystemReady() { } } + /* TODO(b/151241369): restore data loaders on reboot. std::thread([this, mounts = std::move(mounts)]() { - /* TODO(b/151241369): restore data loaders on reboot. for (auto&& ifs : mounts) { if (prepareDataLoader(*ifs)) { LOG(INFO) << "Successfully started data loader for mount " << ifs->mountId; @@ -348,10 +353,8 @@ std::optional<std::future<void>> IncrementalService::onSystemReady() { LOG(WARNING) << "Failed to start data loader for mount " << ifs->mountId; } } - */ - mPrepareDataLoaders.set_value_at_thread_exit(); }).detach(); - return mPrepareDataLoaders.get_future(); + */ } auto IncrementalService::getStorageSlotLocked() -> MountMap::iterator { @@ -468,15 +471,13 @@ StorageId IncrementalService::createStorage( return kInvalidStorageId; } - ifs->dataLoaderParams = std::move(dataLoaderParams); - { metadata::Mount m; m.mutable_storage()->set_id(ifs->mountId); - m.mutable_loader()->set_type((int)ifs->dataLoaderParams.type); - m.mutable_loader()->set_package_name(ifs->dataLoaderParams.packageName); - m.mutable_loader()->set_class_name(ifs->dataLoaderParams.className); - m.mutable_loader()->set_arguments(ifs->dataLoaderParams.arguments); + m.mutable_loader()->set_type((int)dataLoaderParams.type); + m.mutable_loader()->set_package_name(dataLoaderParams.packageName); + m.mutable_loader()->set_class_name(dataLoaderParams.className); + m.mutable_loader()->set_arguments(dataLoaderParams.arguments); const auto metadata = m.SerializeAsString(); m.mutable_loader()->release_arguments(); m.mutable_loader()->release_class_name(); @@ -504,14 +505,20 @@ StorageId IncrementalService::createStorage( // Done here as well, all data structures are in good state. secondCleanupOnFailure.release(); - if (!prepareDataLoader(*ifs, &dataLoaderStatusListener)) { - LOG(ERROR) << "prepareDataLoader() failed"; - deleteStorageLocked(*ifs, std::move(l)); - return kInvalidStorageId; - } + auto dataLoaderStub = + prepareDataLoader(*ifs, std::move(dataLoaderParams), &dataLoaderStatusListener); + CHECK(dataLoaderStub); mountIt->second = std::move(ifs); l.unlock(); + + if (mSystemReady.load(std::memory_order_relaxed) && !dataLoaderStub->create()) { + // failed to create data loader + LOG(ERROR) << "initializeDataLoader() failed"; + deleteStorage(dataLoaderStub->id()); + return kInvalidStorageId; + } + LOG(INFO) << "created storage " << mountId; return mountId; } @@ -585,10 +592,10 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog return -EINVAL; } + const auto& params = ifs->dataLoaderStub->params(); if (enableReadLogs) { - if (auto status = - mAppOpsManager->checkPermission(kDataUsageStats, kOpUsage, - ifs->dataLoaderParams.packageName.c_str()); + if (auto status = mAppOpsManager->checkPermission(kDataUsageStats, kOpUsage, + params.packageName.c_str()); !status.isOk()) { LOG(ERROR) << "checkPermission failed: " << status.toString8(); return fromBinderStatus(status); @@ -601,7 +608,7 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog } if (enableReadLogs) { - registerAppOpsCallback(ifs->dataLoaderParams.packageName); + registerAppOpsCallback(params.packageName); } return 0; @@ -701,8 +708,8 @@ IncrementalService::IfsMountPtr IncrementalService::getIfs(StorageId storage) co const IncrementalService::IfsMountPtr& IncrementalService::getIfsLocked(StorageId storage) const { auto it = mMounts.find(storage); if (it == mMounts.end()) { - static const IfsMountPtr kEmpty = {}; - return kEmpty; + static const android::base::NoDestructor<IfsMountPtr> kEmpty{}; + return *kEmpty; } return it->second; } @@ -984,34 +991,19 @@ std::vector<std::string> IncrementalService::listFiles(StorageId storage) const } bool IncrementalService::startLoading(StorageId storage) const { + DataLoaderStubPtr dataLoaderStub; { std::unique_lock l(mLock); const auto& ifs = getIfsLocked(storage); if (!ifs) { return false; } - if (ifs->dataLoaderStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) { - ifs->dataLoaderStartRequested = true; - return true; + dataLoaderStub = ifs->dataLoaderStub; + if (!dataLoaderStub) { + return false; } } - return startDataLoader(storage); -} - -bool IncrementalService::startDataLoader(MountId mountId) const { - sp<IDataLoader> dataloader; - auto status = mDataLoaderManager->getDataLoader(mountId, &dataloader); - if (!status.isOk()) { - return false; - } - if (!dataloader) { - return false; - } - status = dataloader->start(mountId); - if (!status.isOk()) { - return false; - } - return true; + return dataLoaderStub->start(); } void IncrementalService::mountExistingImages() { @@ -1057,13 +1049,13 @@ bool IncrementalService::mountExistingImage(std::string_view root) { mNextId = std::max(mNextId, ifs->mountId + 1); // DataLoader params + DataLoaderParamsParcel dataLoaderParams; { - auto& dlp = ifs->dataLoaderParams; const auto& loader = mount.loader(); - dlp.type = (android::content::pm::DataLoaderType)loader.type(); - dlp.packageName = loader.package_name(); - dlp.className = loader.class_name(); - dlp.arguments = loader.arguments(); + dataLoaderParams.type = (android::content::pm::DataLoaderType)loader.type(); + dataLoaderParams.packageName = loader.package_name(); + dataLoaderParams.className = loader.class_name(); + dataLoaderParams.arguments = loader.arguments(); } std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints; @@ -1135,17 +1127,13 @@ bool IncrementalService::mountExistingImage(std::string_view root) { return true; } -bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, - const DataLoaderStatusListener* externalListener) { - if (!mSystemReady.load(std::memory_order_relaxed)) { - std::unique_lock l(ifs.lock); - return true; // eventually... - } - +IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader( + IncrementalService::IncFsMount& ifs, DataLoaderParamsParcel&& params, + const DataLoaderStatusListener* externalListener) { std::unique_lock l(ifs.lock); - if (ifs.dataLoaderStatus != -1) { + if (ifs.dataLoaderStub) { LOG(INFO) << "Skipped data loader preparation because it already exists"; - return true; + return ifs.dataLoaderStub; } FileSystemControlParcel fsControlParcel; @@ -1155,17 +1143,10 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, base::unique_fd(::dup(ifs.control.pendingReads()))); fsControlParcel.incremental->log.reset(base::unique_fd(::dup(ifs.control.logs()))); fsControlParcel.service = new IncrementalServiceConnector(*this, ifs.mountId); - sp<IncrementalDataLoaderListener> listener = - new IncrementalDataLoaderListener(*this, - externalListener ? *externalListener - : DataLoaderStatusListener()); - bool created = false; - auto status = mDataLoaderManager->initializeDataLoader(ifs.mountId, ifs.dataLoaderParams, fsControlParcel, listener, &created); - if (!status.isOk() || !created) { - LOG(ERROR) << "Failed to create a data loader for mount " << ifs.mountId; - return false; - } - return true; + + ifs.dataLoaderStub = new DataLoaderStub(*this, ifs.mountId, std::move(params), + std::move(fsControlParcel), externalListener); + return ifs.dataLoaderStub; } template <class Duration> @@ -1376,7 +1357,7 @@ void IncrementalService::onAppOpChanged(const std::string& packageName) { std::lock_guard l(mLock); affected.reserve(mMounts.size()); for (auto&& [id, ifs] : mMounts) { - if (ifs->mountId == id && ifs->dataLoaderParams.packageName == packageName) { + if (ifs->mountId == id && ifs->dataLoaderStub->params().packageName == packageName) { affected.push_back(ifs); } } @@ -1386,37 +1367,79 @@ void IncrementalService::onAppOpChanged(const std::string& packageName) { } } -binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId, - int newStatus) { - if (externalListener) { +IncrementalService::DataLoaderStub::~DataLoaderStub() { + CHECK(mStatus == -1 || mStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) + << "Dataloader has to be destroyed prior to destructor: " << mId + << ", status: " << mStatus; +} + +bool IncrementalService::DataLoaderStub::create() { + bool created = false; + auto status = mService.mDataLoaderManager->initializeDataLoader(mId, mParams, mControl, this, + &created); + if (!status.isOk() || !created) { + LOG(ERROR) << "Failed to create a data loader for mount " << mId; + return false; + } + return true; +} + +bool IncrementalService::DataLoaderStub::start() { + if (mStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) { + mStartRequested = true; + return true; + } + + sp<IDataLoader> dataloader; + auto status = mService.mDataLoaderManager->getDataLoader(mId, &dataloader); + if (!status.isOk()) { + return false; + } + if (!dataloader) { + return false; + } + status = dataloader->start(mId); + if (!status.isOk()) { + return false; + } + return true; +} + +void IncrementalService::DataLoaderStub::destroy() { + mDestroyRequested = true; + mService.mDataLoaderManager->destroyDataLoader(mId); +} + +binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mountId, int newStatus) { + if (mStatus == newStatus) { + return binder::Status::ok(); + } + + if (mListener) { // Give an external listener a chance to act before we destroy something. - externalListener->onStatusChanged(mountId, newStatus); + mListener->onStatusChanged(mountId, newStatus); } - bool startRequested = false; { - std::unique_lock l(incrementalService.mLock); - const auto& ifs = incrementalService.getIfsLocked(mountId); + std::unique_lock l(mService.mLock); + const auto& ifs = mService.getIfsLocked(mountId); if (!ifs) { LOG(WARNING) << "Received data loader status " << int(newStatus) << " for unknown mount " << mountId; return binder::Status::ok(); } - ifs->dataLoaderStatus = newStatus; + mStatus = newStatus; - if (newStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) { - ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_STOPPED; - incrementalService.deleteStorageLocked(*ifs, std::move(l)); + if (!mDestroyRequested && newStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) { + mService.deleteStorageLocked(*ifs, std::move(l)); return binder::Status::ok(); } - - startRequested = ifs->dataLoaderStartRequested; } switch (newStatus) { case IDataLoaderStatusListener::DATA_LOADER_CREATED: { - if (startRequested) { - incrementalService.startDataLoader(mountId); + if (mStartRequested) { + start(); } break; } diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index db14a794457e..27d40f1506ca 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -60,7 +60,8 @@ using Clock = std::chrono::steady_clock; using TimePoint = std::chrono::time_point<Clock>; using Seconds = std::chrono::seconds; -using DataLoaderStatusListener = ::android::sp<::android::content::pm::IDataLoaderStatusListener>; +using IDataLoaderStatusListener = ::android::content::pm::IDataLoaderStatusListener; +using DataLoaderStatusListener = ::android::sp<IDataLoaderStatusListener>; class IncrementalService final { public: @@ -95,7 +96,7 @@ public: void onDump(int fd); - std::optional<std::future<void>> onSystemReady(); + void onSystemReady(); StorageId createStorage(std::string_view mountPoint, DataLoaderParamsParcel&& dataLoaderParams, const DataLoaderStatusListener& dataLoaderStatusListener, @@ -134,19 +135,6 @@ public: bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath, std::string_view libDirRelativePath, std::string_view abi); - class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener { - public: - IncrementalDataLoaderListener(IncrementalService& incrementalService, - DataLoaderStatusListener externalListener) - : incrementalService(incrementalService), externalListener(externalListener) {} - // Callbacks interface - binder::Status onStatusChanged(MountId mount, int newStatus) final; - - private: - IncrementalService& incrementalService; - DataLoaderStatusListener externalListener; - }; - class AppOpsListener : public android::BnAppOpsCallback { public: AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {} @@ -171,6 +159,45 @@ public: private: static const bool sEnablePerfLogging; + struct IncFsMount; + + class DataLoaderStub : public android::content::pm::BnDataLoaderStatusListener { + public: + DataLoaderStub(IncrementalService& service, MountId id, DataLoaderParamsParcel&& params, + FileSystemControlParcel&& control, + const DataLoaderStatusListener* externalListener) + : mService(service), + mId(id), + mParams(std::move(params)), + mControl(std::move(control)), + mListener(externalListener ? *externalListener : DataLoaderStatusListener()) {} + ~DataLoaderStub(); + + bool create(); + bool start(); + void destroy(); + + // accessors + MountId id() const { return mId; } + const DataLoaderParamsParcel& params() const { return mParams; } + int status() const { return mStatus.load(); } + bool startRequested() const { return mStartRequested; } + + private: + binder::Status onStatusChanged(MountId mount, int newStatus) final; + + IncrementalService& mService; + MountId const mId; + DataLoaderParamsParcel const mParams; + FileSystemControlParcel const mControl; + DataLoaderStatusListener const mListener; + + std::atomic<int> mStatus = -1; + bool mStartRequested = false; + bool mDestroyRequested = false; + }; + using DataLoaderStubPtr = sp<DataLoaderStub>; + struct IncFsMount { struct Bind { StorageId storage; @@ -194,10 +221,8 @@ private: /*const*/ MountId mountId; StorageMap storages; BindMap bindPoints; - DataLoaderParamsParcel dataLoaderParams; + DataLoaderStubPtr dataLoaderStub; std::atomic<int> nextStorageDirNo{0}; - std::atomic<int> dataLoaderStatus = -1; - bool dataLoaderStartRequested = false; const IncrementalService& incrementalService; IncFsMount(std::string root, MountId mountId, Control control, @@ -232,8 +257,8 @@ private: std::string&& source, std::string&& target, BindKind kind, std::unique_lock<std::mutex>& mainLock); - bool prepareDataLoader(IncFsMount& ifs, const DataLoaderStatusListener* externalListener = nullptr); - bool startDataLoader(MountId mountId) const; + DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs, DataLoaderParamsParcel&& params, + const DataLoaderStatusListener* externalListener = nullptr); BindPathMap::const_iterator findStorageLocked(std::string_view path) const; StorageId findStorageId(std::string_view path) const; @@ -269,7 +294,6 @@ private: std::atomic_bool mSystemReady = false; StorageId mNextId = 0; - std::promise<void> mPrepareDataLoaders; }; } // namespace android::incremental diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index 18ae4b5af435..991131950531 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -131,6 +131,23 @@ public: binder::Status(int32_t mountId, sp<IDataLoader>* _aidl_return)); MOCK_CONST_METHOD1(destroyDataLoader, binder::Status(int32_t mountId)); + void initializeDataLoaderSuccess() { + ON_CALL(*this, initializeDataLoader(_, _, _, _, _)) + .WillByDefault(Invoke(this, &MockDataLoaderManager::initializeDataLoaderOk)); + } + void initializeDataLoaderFails() { + ON_CALL(*this, initializeDataLoader(_, _, _, _, _)) + .WillByDefault(Return( + (binder::Status::fromExceptionCode(1, String8("failed to prepare"))))); + } + void getDataLoaderSuccess() { + ON_CALL(*this, getDataLoader(_, _)) + .WillByDefault(Invoke(this, &MockDataLoaderManager::getDataLoaderOk)); + } + void destroyDataLoaderOk() { + ON_CALL(*this, destroyDataLoader(_)) + .WillByDefault(Invoke(this, &MockDataLoaderManager::setDataLoaderStatusDestroyed)); + } binder::Status initializeDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params, const FileSystemControlParcel& control, const sp<IDataLoaderStatusListener>& listener, @@ -141,32 +158,22 @@ public: *_aidl_return = true; return binder::Status::ok(); } - binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) { *_aidl_return = mDataLoader; return binder::Status::ok(); } - - void initializeDataLoaderFails() { - ON_CALL(*this, initializeDataLoader(_, _, _, _, _)) - .WillByDefault(Return( - (binder::Status::fromExceptionCode(1, String8("failed to prepare"))))); - } - void initializeDataLoaderSuccess() { - ON_CALL(*this, initializeDataLoader(_, _, _, _, _)) - .WillByDefault(Invoke(this, &MockDataLoaderManager::initializeDataLoaderOk)); - } - void getDataLoaderSuccess() { - ON_CALL(*this, getDataLoader(_, _)) - .WillByDefault(Invoke(this, &MockDataLoaderManager::getDataLoaderOk)); - } void setDataLoaderStatusNotReady() { mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_DESTROYED); } void setDataLoaderStatusReady() { mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_CREATED); } - + binder::Status setDataLoaderStatusDestroyed(int32_t id) { + if (mListener) { + mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED); + } + return binder::Status::ok(); + } int32_t setStorageParams(bool enableReadLogs) { int32_t result = -1; EXPECT_NE(mServiceConnector.get(), nullptr); @@ -299,6 +306,7 @@ public: mRootDir.path); mDataLoaderParcel.packageName = "com.test"; mDataLoaderParcel.arguments = "uri"; + mDataLoaderManager->destroyDataLoaderOk(); mIncrementalService->onSystemReady(); } @@ -346,6 +354,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) { TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) { mVold->mountIncFsInvalidControlParcel(); EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0); TemporaryDir tempDir; int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, @@ -357,7 +366,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) { mVold->mountIncFsSuccess(); mIncFs->makeFileFails(); EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0); - EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0); EXPECT_CALL(*mVold, unmountIncFs(_)); TemporaryDir tempDir; int storageId = @@ -371,7 +380,7 @@ TEST_F(IncrementalServiceTest, testCreateStorageBindMountFails) { mIncFs->makeFileSuccess(); mVold->bindMountFails(); EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0); - EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0); EXPECT_CALL(*mVold, unmountIncFs(_)); TemporaryDir tempDir; int storageId = @@ -385,7 +394,7 @@ TEST_F(IncrementalServiceTest, testCreateStoragePrepareDataLoaderFails) { mIncFs->makeFileSuccess(); mVold->bindMountSuccess(); mDataLoaderManager->initializeDataLoaderFails(); - EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; int storageId = @@ -399,7 +408,7 @@ TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) { mIncFs->makeFileSuccess(); mVold->bindMountSuccess(); mDataLoaderManager->initializeDataLoaderSuccess(); - EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)); + EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; int storageId = diff --git a/services/net/Android.bp b/services/net/Android.bp index c54102fb1d3d..3eba6c4df1bf 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -13,7 +13,7 @@ java_library_static { ], static_libs: [ "dnsresolver_aidl_interface-V2-java", - "netd_aidl_interface-unstable-java", + "netd_aidl_interface-V3-java", "netlink-client", "networkstack-client", "net-utils-services-common", @@ -44,7 +44,7 @@ java_library { ], static_libs: [ "dnsresolver_aidl_interface-V2-java", - "netd_aidl_interface-unstable-java", + "netd_aidl_interface-V3-java", "netlink-client", "networkstack-client", "net-utils-services-common", diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java index f4d7b8bd5b1a..d338b587e059 100644 --- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java @@ -382,6 +382,7 @@ public class BlobStoreManagerServiceTest { doReturn(hasLeases).when(blobMetadata).hasLeases(); doReturn(blobHandle).when(blobMetadata).getBlobHandle(); doCallRealMethod().when(blobMetadata).shouldBeDeleted(anyBoolean()); + doReturn(true).when(blobMetadata).hasLeaseWaitTimeElapsedForAll(); return blobMetadata; } diff --git a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java index ccbaee41af7c..aa923e22444d 100644 --- a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java @@ -63,6 +63,16 @@ public class LightsServiceTest { fakeHwLight(105, LightsManager.LIGHT_TYPE_MICROPHONE, 2) }; } + + @Override + public int getInterfaceVersion() { + return this.VERSION; + } + + @Override + public String getInterfaceHash() { + return this.HASH; + } }; private static HwLight fakeHwLight(int id, int type, int ordinal) { diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java index 820e61cb0a08..9eda718ed922 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java @@ -377,8 +377,7 @@ class OverlayManagerServiceImplTestsBase { return false; } final String key = createKey(overlayPackage.packageName, userId); - mIdmapFiles.add(key); - return true; + return mIdmapFiles.add(key); } @Override diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java new file mode 100644 index 000000000000..cfeadc6893db --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.stats.pull; + +import static android.os.UserHandle.USER_SYSTEM; + +import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.content.Context; +import android.provider.DeviceConfig; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Build/Install/Run: + * atest FrameworksServicesTests:SettingsStatsUtilTest + */ +@RunWith(AndroidJUnit4.class) +public class SettingsStatsUtilTest { + private static final String[] KEYS = new String[]{ + "screen_auto_brightness_adj", + "font_scale" + }; + private static final String ENCODED = "ChpzY3JlZW5fYXV0b19icmlnaHRuZXNzX2FkagoKZm9udF9zY2FsZQ"; + private static final String FLAG = "testflag"; + private Context mContext; + + @Before + public void setUp() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + FLAG, + "", + false /* makeDefault*/); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + } + + @Test + public void getList_emptyString_nullValue() { + assertNull(SettingsStatsUtil.getList(FLAG)); + } + + @Test + public void getList_notValidString_nullValue() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, FLAG, "abcd", false); + + assertNull(SettingsStatsUtil.getList(FLAG)); + } + + @Test + public void getList_validString_correctValue() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, FLAG, ENCODED, false); + + assertArrayEquals(KEYS, SettingsStatsUtil.getList(FLAG).element); + } + + @Test + public void logGlobalSettings_noWhitelist_correctSize() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__boolean_whitelist", "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__integer_whitelist", "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__float_whitelist", "", false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__string_whitelist", "", false); + + assertEquals(0, SettingsStatsUtil.logGlobalSettings(mContext, SETTING_SNAPSHOT, + USER_SYSTEM).size()); + } + + @Test + public void logGlobalSettings_correctSize() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__boolean_whitelist", ENCODED, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__integer_whitelist", ENCODED, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__float_whitelist", ENCODED, false); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, + "GlobalFeature__string_whitelist", ENCODED, false); + + assertEquals(KEYS.length * 4, + SettingsStatsUtil.logGlobalSettings(mContext, SETTING_SNAPSHOT, + USER_SYSTEM).size()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 881561f5750b..1f6ba7adf114 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -238,9 +238,6 @@ public class RecentsAnimationTest extends ActivityTestsBase { assertTrue(targetActivity.mLaunchTaskBehind); anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome"); - // The current top activity is not the recents so the animation should be canceled. - verify(mService.mWindowManager, times(1)).cancelRecentsAnimation( - eq(REORDER_KEEP_IN_PLACE), any() /* reason */); // The test uses mocked RecentsAnimationController so we have to invoke the callback // manually to simulate the flow. @@ -279,10 +276,6 @@ public class RecentsAnimationTest extends ActivityTestsBase { fullscreenStack.moveToFront("Activity start"); - // Ensure that the recents animation was canceled by cancelAnimationSynchronously(). - verify(mService.mWindowManager, times(1)).cancelRecentsAnimation( - eq(REORDER_KEEP_IN_PLACE), any()); - // Assume recents animation already started, set a state that cancel recents animation // with screenshot. doReturn(true).when(mRecentsAnimationController).shouldDeferCancelUntilNextTransition(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 7a075a26cb31..4a8e8dafb57d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -120,8 +120,8 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { IWindow iWindow = mock(IWindow.class); doReturn(mock(IBinder.class)).when(iWindow).asBinder(); window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, activity, - "Starting window", 0 /* ownerId */, false /* internalWindows */, wm, - mock(Session.class), iWindow, mPowerManagerWrapper); + "Starting window", 0 /* ownerId */, 0 /* userId*/, false /* internalWindows */, + wm, mock(Session.class), iWindow, mPowerManagerWrapper); activity.startingWindow = window; } if (mRunnableWhenAddingSplashScreen != null) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java index 084216a9a543..fc95556750f1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java @@ -112,7 +112,7 @@ class WindowTestUtils { TestWindowState(WindowManagerService service, Session session, IWindow window, WindowManager.LayoutParams attrs, WindowToken token) { - super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, + super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, 0, false /* ownerCanAddInternalSystemWindow */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 397f73c3e67c..e561c13a4e99 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -41,6 +41,7 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.content.Intent; +import android.os.UserHandle; import android.util.Log; import android.view.Display; import android.view.DisplayInfo; @@ -296,12 +297,13 @@ class WindowTestsBase extends SystemServiceTestsBase { WindowState createWindow(WindowState parent, int type, WindowToken token, String name, int ownerId, boolean ownerCanAddInternalSystemWindow) { - return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow, - mWm, mMockSession, mIWindow, mSystemServicesTestRule.getPowerManagerWrapper()); + return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId), + ownerCanAddInternalSystemWindow, mWm, mMockSession, mIWindow, + mSystemServicesTestRule.getPowerManagerWrapper()); } static WindowState createWindow(WindowState parent, int type, WindowToken token, - String name, int ownerId, boolean ownerCanAddInternalSystemWindow, + String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow, WindowManagerService service, Session session, IWindow iWindow, WindowState.PowerManagerWrapper powerManagerWrapper) { synchronized (service.mGlobalLock) { @@ -309,8 +311,8 @@ class WindowTestsBase extends SystemServiceTestsBase { attrs.setTitle(name); final WindowState w = new WindowState(service, session, iWindow, token, parent, - OP_NONE, - 0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow, + OP_NONE, 0, attrs, VISIBLE, ownerId, userId, + ownerCanAddInternalSystemWindow, powerManagerWrapper); // TODO: Probably better to make this call in the WindowState ctor to avoid errors with // adding it to the token... diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index 76479cb95b09..535d53eeef71 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import android.content.res.Configuration; import android.os.IBinder; import android.platform.test.annotations.Presubmit; @@ -134,6 +135,30 @@ public class WindowTokenTests extends WindowTestsBase { assertEquals(0, token.getWindowsCount()); } + @Test + public void testClearFixedRotationTransform() { + final WindowToken appToken = mAppWindow.mToken; + final WindowToken wallpaperToken = mWallpaperWindow.mToken; + final Configuration config = new Configuration(mDisplayContent.getConfiguration()); + final int originalRotation = config.windowConfiguration.getRotation(); + final int targetRotation = (originalRotation + 1) % 4; + + config.windowConfiguration.setRotation(targetRotation); + appToken.applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config); + wallpaperToken.linkFixedRotationTransform(appToken); + + // The window tokens should apply the rotation by the transformation. + assertEquals(targetRotation, appToken.getWindowConfiguration().getRotation()); + assertEquals(targetRotation, wallpaperToken.getWindowConfiguration().getRotation()); + + // The display doesn't rotate, the transformation will be canceled. + mAppWindow.mToken.clearFixedRotationTransform(null /* applyDisplayRotation */); + + // The window tokens should restore to the original rotation. + assertEquals(originalRotation, appToken.getWindowConfiguration().getRotation()); + assertEquals(originalRotation, wallpaperToken.getWindowConfiguration().getRotation()); + } + /** * Test that {@link WindowToken} constructor parameters is set with expectation. */ diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index bce06e4777a2..4e14fd3d59a1 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -1092,16 +1092,16 @@ public abstract class Conference extends Conferenceable { * This is applicable in two cases: * <ol> * <li>When {@link #setConferenceState(boolean)} is used to mark a conference as - * temporarily "not a conference"; we need to present the correct address in the in-call - * UI.</li> + * temporarily "not a conference"; we need to present the correct address presentation in + * the in-call UI.</li> * <li>When the conference is not hosted on the current device, we need to know the address - * information for the purpose of showing the original address to the user, as well as for - * logging to the call log.</li> + * presentation information for the purpose of showing the original address to the user, as + * well as for logging to the call log.</li> * </ol> - * @return The address of the conference, or {@code null} if not applicable. + * @return The address presentation of the conference. * @hide */ - public final int getAddressPresentation() { + public final @TelecomManager.Presentation int getAddressPresentation() { return mAddressPresentation; } diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 0d66013d92a1..73296986d82e 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -1865,25 +1865,23 @@ public abstract class ConnectionService extends Service { mConferenceById.put(callId, conference); mIdByConference.put(conference, callId); conference.addListener(mConferenceListener); - ParcelableConference parcelableConference = new ParcelableConference( - request.getAccountHandle(), - conference.getState(), - conference.getConnectionCapabilities(), - conference.getConnectionProperties(), - Collections.<String>emptyList(), //connectionIds - conference.getVideoProvider() == null ? - null : conference.getVideoProvider().getInterface(), - conference.getVideoState(), - conference.getConnectTimeMillis(), - conference.getConnectionStartElapsedRealtimeMillis(), - conference.getStatusHints(), - conference.getExtras(), - conference.getAddress(), - conference.getAddressPresentation(), - conference.getCallerDisplayName(), - conference.getCallerDisplayNamePresentation(), - conference.getDisconnectCause(), - conference.isRingbackRequested()); + ParcelableConference parcelableConference = new ParcelableConference.Builder( + request.getAccountHandle(), conference.getState()) + .setConnectionCapabilities(conference.getConnectionCapabilities()) + .setConnectionProperties(conference.getConnectionProperties()) + .setVideoAttributes(conference.getVideoProvider() == null + ? null : conference.getVideoProvider().getInterface(), + conference.getVideoState()) + .setConnectTimeMillis(conference.getConnectTimeMillis(), + conference.getConnectionStartElapsedRealtimeMillis()) + .setStatusHints(conference.getStatusHints()) + .setExtras(conference.getExtras()) + .setAddress(conference.getAddress(), conference.getAddressPresentation()) + .setCallerDisplayName(conference.getCallerDisplayName(), + conference.getCallerDisplayNamePresentation()) + .setDisconnectCause(conference.getDisconnectCause()) + .setRingbackRequested(conference.isRingbackRequested()) + .build(); if (conference.getState() != Connection.STATE_DISCONNECTED) { conference.setTelecomCallId(callId); mAdapter.setVideoProvider(callId, conference.getVideoProvider()); @@ -2484,23 +2482,25 @@ public abstract class ConnectionService extends Service { } } conference.setTelecomCallId(id); - ParcelableConference parcelableConference = new ParcelableConference( - conference.getPhoneAccountHandle(), - conference.getState(), - conference.getConnectionCapabilities(), - conference.getConnectionProperties(), - connectionIds, - conference.getVideoProvider() == null ? - null : conference.getVideoProvider().getInterface(), - conference.getVideoState(), - conference.getConnectTimeMillis(), - conference.getConnectionStartElapsedRealtimeMillis(), - conference.getStatusHints(), - conference.getExtras(), - conference.getAddress(), - conference.getAddressPresentation(), - conference.getCallerDisplayName(), - conference.getCallerDisplayNamePresentation()); + ParcelableConference parcelableConference = new ParcelableConference.Builder( + conference.getPhoneAccountHandle(), conference.getState()) + .setConnectionCapabilities(conference.getConnectionCapabilities()) + .setConnectionProperties(conference.getConnectionProperties()) + .setConnectionIds(connectionIds) + .setVideoAttributes(conference.getVideoProvider() == null + ? null : conference.getVideoProvider().getInterface(), + conference.getVideoState()) + .setConnectTimeMillis(conference.getConnectTimeMillis(), + conference.getConnectionStartElapsedRealtimeMillis()) + .setStatusHints(conference.getStatusHints()) + .setExtras(conference.getExtras()) + .setAddress(conference.getAddress(), conference.getAddressPresentation()) + .setCallerDisplayName(conference.getCallerDisplayName(), + conference.getCallerDisplayNamePresentation()) + .setDisconnectCause(conference.getDisconnectCause()) + .setRingbackRequested(conference.isRingbackRequested()) + .setCallDirection(conference.getCallDirection()) + .build(); mAdapter.addConferenceCall(id, parcelableConference); mAdapter.setVideoProvider(id, conference.getVideoProvider()); diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java index 90b69a338c7e..1f8aafbca476 100644 --- a/telecomm/java/android/telecom/ParcelableConference.java +++ b/telecomm/java/android/telecom/ParcelableConference.java @@ -22,6 +22,7 @@ import android.os.Parcel; import android.os.Parcelable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import com.android.internal.telecom.IVideoProvider; @@ -32,25 +33,130 @@ import com.android.internal.telecom.IVideoProvider; */ public final class ParcelableConference implements Parcelable { - private PhoneAccountHandle mPhoneAccount; - private int mState; - private int mConnectionCapabilities; - private int mConnectionProperties; - private List<String> mConnectionIds; - private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED; + public static final class Builder { + private final PhoneAccountHandle mPhoneAccount; + private final int mState; + private int mConnectionCapabilities; + private int mConnectionProperties; + private List<String> mConnectionIds = Collections.emptyList(); + private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED; + private IVideoProvider mVideoProvider; + private int mVideoState = VideoProfile.STATE_AUDIO_ONLY; + private StatusHints mStatusHints; + private Bundle mExtras; + private long mConnectElapsedTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED; + private Uri mAddress; + private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN; + private String mCallerDisplayName; + private int mCallerDisplayNamePresentation = TelecomManager.PRESENTATION_UNKNOWN;; + private DisconnectCause mDisconnectCause; + private boolean mRingbackRequested; + private int mCallDirection = Call.Details.DIRECTION_UNKNOWN; + + public Builder( + PhoneAccountHandle phoneAccount, + int state) { + mPhoneAccount = phoneAccount; + mState = state; + } + + public Builder setDisconnectCause(DisconnectCause cause) { + mDisconnectCause = cause; + return this; + } + + public Builder setRingbackRequested(boolean requested) { + mRingbackRequested = requested; + return this; + } + + public Builder setCallerDisplayName(String callerDisplayName, + @TelecomManager.Presentation int callerDisplayNamePresentation) { + mCallerDisplayName = callerDisplayName; + mCallerDisplayNamePresentation = callerDisplayNamePresentation; + return this; + } + + public Builder setAddress(Uri address, + @TelecomManager.Presentation int addressPresentation) { + mAddress = address; + mAddressPresentation = addressPresentation; + return this; + } + + public Builder setExtras(Bundle extras) { + mExtras = extras; + return this; + } + + public Builder setStatusHints(StatusHints hints) { + mStatusHints = hints; + return this; + } + + public Builder setConnectTimeMillis(long connectTimeMillis, long connectElapsedTimeMillis) { + mConnectTimeMillis = connectTimeMillis; + mConnectElapsedTimeMillis = connectElapsedTimeMillis; + return this; + } + + public Builder setVideoAttributes(IVideoProvider provider, + @VideoProfile.VideoState int videoState) { + mVideoProvider = provider; + mVideoState = videoState; + return this; + } + + public Builder setConnectionIds(List<String> connectionIds) { + mConnectionIds = connectionIds; + return this; + } + + public Builder setConnectionProperties(int properties) { + mConnectionProperties = properties; + return this; + } + + public Builder setConnectionCapabilities(int capabilities) { + mConnectionCapabilities = capabilities; + return this; + } + + public Builder setCallDirection(int callDirection) { + mCallDirection = callDirection; + return this; + } + + public ParcelableConference build() { + return new ParcelableConference(mPhoneAccount, mState, mConnectionCapabilities, + mConnectionProperties, mConnectionIds, mVideoProvider, mVideoState, + mConnectTimeMillis, mConnectElapsedTimeMillis, mStatusHints, mExtras, mAddress, + mAddressPresentation, mCallerDisplayName, mCallerDisplayNamePresentation, + mDisconnectCause, mRingbackRequested, mCallDirection); + } + } + + + private final PhoneAccountHandle mPhoneAccount; + private final int mState; + private final int mConnectionCapabilities; + private final int mConnectionProperties; + private final List<String> mConnectionIds; + private final long mConnectTimeMillis; private final IVideoProvider mVideoProvider; private final int mVideoState; - private StatusHints mStatusHints; - private Bundle mExtras; - private long mConnectElapsedTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED; + private final StatusHints mStatusHints; + private final Bundle mExtras; + private final long mConnectElapsedTimeMillis; private final Uri mAddress; private final int mAddressPresentation; private final String mCallerDisplayName; private final int mCallerDisplayNamePresentation; - private DisconnectCause mDisconnectCause; - private boolean mRingbackRequested; + private final DisconnectCause mDisconnectCause; + private final boolean mRingbackRequested; + private final int mCallDirection; - public ParcelableConference( + private ParcelableConference( PhoneAccountHandle phoneAccount, int state, int connectionCapabilities, @@ -67,31 +173,8 @@ public final class ParcelableConference implements Parcelable { String callerDisplayName, int callerDisplayNamePresentation, DisconnectCause disconnectCause, - boolean ringbackRequested) { - this(phoneAccount, state, connectionCapabilities, connectionProperties, connectionIds, - videoProvider, videoState, connectTimeMillis, connectElapsedTimeMillis, - statusHints, extras, address, addressPresentation, callerDisplayName, - callerDisplayNamePresentation); - mDisconnectCause = disconnectCause; - mRingbackRequested = ringbackRequested; - } - - public ParcelableConference( - PhoneAccountHandle phoneAccount, - int state, - int connectionCapabilities, - int connectionProperties, - List<String> connectionIds, - IVideoProvider videoProvider, - int videoState, - long connectTimeMillis, - long connectElapsedTimeMillis, - StatusHints statusHints, - Bundle extras, - Uri address, - int addressPresentation, - String callerDisplayName, - int callerDisplayNamePresentation) { + boolean ringbackRequested, + int callDirection) { mPhoneAccount = phoneAccount; mState = state; mConnectionCapabilities = connectionCapabilities; @@ -107,8 +190,9 @@ public final class ParcelableConference implements Parcelable { mAddressPresentation = addressPresentation; mCallerDisplayName = callerDisplayName; mCallerDisplayNamePresentation = callerDisplayNamePresentation; - mDisconnectCause = null; - mRingbackRequested = false; + mDisconnectCause = disconnectCause; + mRingbackRequested = ringbackRequested; + mCallDirection = callDirection; } @Override @@ -134,6 +218,8 @@ public final class ParcelableConference implements Parcelable { .append(mRingbackRequested) .append(", disconnectCause: ") .append(mDisconnectCause) + .append(", callDirection: ") + .append(mCallDirection) .toString(); } @@ -192,10 +278,15 @@ public final class ParcelableConference implements Parcelable { public boolean isRingbackRequested() { return mRingbackRequested; } + public int getHandlePresentation() { return mAddressPresentation; } + public int getCallDirection() { + return mCallDirection; + } + public static final @android.annotation.NonNull Parcelable.Creator<ParcelableConference> CREATOR = new Parcelable.Creator<ParcelableConference> () { @Override @@ -220,12 +311,13 @@ public final class ParcelableConference implements Parcelable { int callerDisplayNamePresentation = source.readInt(); DisconnectCause disconnectCause = source.readParcelable(classLoader); boolean isRingbackRequested = source.readInt() == 1; + int callDirection = source.readInt(); return new ParcelableConference(phoneAccount, state, capabilities, properties, connectionIds, videoCallProvider, videoState, connectTimeMillis, connectElapsedTimeMillis, statusHints, extras, address, addressPresentation, callerDisplayName, callerDisplayNamePresentation, disconnectCause, - isRingbackRequested); + isRingbackRequested, callDirection); } @Override @@ -261,5 +353,6 @@ public final class ParcelableConference implements Parcelable { destination.writeInt(mCallerDisplayNamePresentation); destination.writeParcelable(mDisconnectCause, 0); destination.writeInt(mRingbackRequested ? 1 : 0); + destination.writeInt(mCallDirection); } } diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt index cf11f4347503..f85231114f96 100644 --- a/wifi/jarjar-rules.txt +++ b/wifi/jarjar-rules.txt @@ -1,6 +1,13 @@ # used by wifi-service +# TODO (b/153596226): Find a solution for networkstack's AIDL parcelables & interfaces. +# Parcelable class names are serialized in the wire, so renaming them +# will result in the class not being found for any parcelable received/sent from the +# wifi-service jar. + +# Note: This rule is needed to ensure the rule below does not rename a Parcelable (see TODO above). rule android.net.DhcpResultsParcelable* @0 rule android.net.DhcpResults* com.android.server.x.wifi.net.DhcpResults@1 +# Note: This rule is needed to ensure the rule below does not rename a Parcelable (see TODO above). rule android.net.InterfaceConfigurationParcel* @0 rule android.net.InterfaceConfiguration* com.android.server.x.wifi.net.InterfaceConfiguration@1 rule android.net.IpMemoryStore* com.android.server.x.wifi.net.IpMemoryStore@1 @@ -10,11 +17,18 @@ rule android.net.NetworkFactory* com.android.server.x.wifi.net.NetworkFactory@1 rule android.net.ip.IpClientCallbacks* com.android.server.x.wifi.net.ip.IpClientCallbacks@1 rule android.net.ip.IpClientManager* com.android.server.x.wifi.net.ip.IpClientManager@1 rule android.net.ip.IpClientUtil* com.android.server.x.wifi.net.ip.IpClientUtil@1 +rule android.net.ipmemorystore.OnBlobRetrievedListener* com.android.server.x.wifi.net.ipmemorystore.OnBlobRetrievedListener@1 +rule android.net.ipmemorystore.OnStatusListener* com.android.server.x.wifi.net.ipmemorystore.OnStatusListener@1 +# Note: This rule is needed to ensure the rule below does not rename a Parcelable (see TODO above). +rule android.net.ipmemorystore.StatusParcelable* @0 +rule android.net.ipmemorystore.Status* com.android.server.x.wifi.net.ipmemorystore.Status@1 +rule android.net.networkstack.ModuleNetworkStackClient* com.android.server.x.wifi.net.networkstack.ModuleNetworkStackClient@1 +rule android.net.networkstack.NetworkStackClientBase* com.android.server.x.wifi.net.networkstack.NetworkStackClientBase@1 rule android.net.shared.InetAddressUtils* com.android.server.x.wifi.net.shared.InetAddressUtils@1 rule android.net.shared.InitialConfiguration* com.android.server.x.wifi.net.shared.InitialConfiguration@1 rule android.net.shared.IpConfigurationParcelableUtil* com.android.server.x.wifi.net.shared.IpConfigurationParcelableUtil@1 +rule android.net.shared.Layer2Information* com.android.server.x.wifi.net.shared.Layer2Information@1 rule android.net.shared.LinkPropertiesParcelableUtil* com.android.server.x.wifi.net.shared.LinkPropertiesParcelableUtil@1 -rule android.net.shared.ParcelableUtil* com.android.server.x.wifi.net.shared.ParcelableUtil@1 rule android.net.shared.NetdUtils* com.android.server.x.wifi.net.shared.NetdUtils@1 rule android.net.shared.NetworkMonitorUtils* com.android.server.x.wifi.net.shared.NetworkMonitorUtils@1 rule android.net.shared.ParcelableUtil* com.android.server.x.wifi.net.shared.ParcelableUtil@1 @@ -28,6 +42,8 @@ rule android.net.util.SharedLog* com.android.server.x.wifi.net.util.SharedLog@1 rule android.net.util.NetUtils* com.android.server.x.wifi.net.util.NetUtils@1 rule android.net.util.IpUtils* com.android.server.x.wifi.net.util.IpUtils@1 +rule androidx.annotation.** com.android.server.x.wifi.androidx.annotation.@1 + # We don't jar-jar the entire package because, we still use some classes (like # AsyncChannel in com.android.internal.util) from these packages which are not # inside our jar (currently in framework.jar, but will be in wifisdk.jar in the future). @@ -54,8 +70,16 @@ rule com.android.internal.messages.SystemMessageProto* com.android.server.x.wifi # Use our statically linked PlatformProperties library rule android.sysprop.** com.android.server.x.wifi.sysprop.@1 # Use our statically linked HIDL stubs -rule android.hardware.** com.android.server.x.wifi.hardware.@1 +# Note: android.hardware.wifi.** is used by various wifi feature flags. This unfortunately is also the namespace +# used by vendor HAL stubs. So, this rule is intentionally weird to try and filter the vendor HAL stubs only. +rule android.hardware.wifi.V** com.android.server.x.wifi.hardware.wifi.V@1 +rule android.hardware.wifi.supplicant.** com.android.server.x.wifi.hardware.wifi.supplicant.@1 +rule android.hardware.wifi.hostapd.** com.android.server.x.wifi.hardware.wifi.hostapd.@1 rule android.hidl.** com.android.server.x.wifi.hidl.@1 +# Use our statically linked ksoap2 +rule org.ksoap2.** com.android.server.x.wifi.ksoap2.@1 +# Use our statically linked nanohttpd +rule fi.iki.elonen.** com.android.server.x.wifi.elonen.@1 # used by both framework-wifi and wifi-service rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1 diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index f1be8b20eb53..6c8dc00cb579 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -179,6 +179,8 @@ public class WifiManager { /** * Reason code if one or more of the network suggestions added already exists in platform's * database. + * Note: this code will not be returned with Android 11 as in-place modification is allowed, + * please check {@link #addNetworkSuggestions(List)}. * @see WifiNetworkSuggestion#equals(Object) */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 3; @@ -186,6 +188,8 @@ public class WifiManager { /** * Reason code if the number of network suggestions provided by the app crosses the max * threshold set per app. + * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)} if + * the total size exceeds the limit. * @see #getMaxNumberOfNetworkSuggestionsPerApp() */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 4; @@ -193,21 +197,27 @@ public class WifiManager { /** * Reason code if one or more of the network suggestions removed does not exist in platform's * database. + * The framework won't remove any suggestions if one or more of suggestions provided + * by {@link #removeNetworkSuggestions(List)} does not exist in database. + * @see WifiNetworkSuggestion#equals(Object) */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5; /** * Reason code if one or more of the network suggestions added is not allowed. - * + * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)} + * if one or more of them is not allowed. * This error may be caused by suggestion is using SIM-based encryption method, but calling app * is not carrier privileged. */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED = 6; /** - * Reason code if one or more of the network suggestions added is invalid. - * - * Please user {@link WifiNetworkSuggestion.Builder} to create network suggestions. + * Reason code if one or more of the network suggestions added is invalid. Framework will reject + * all the suggestions in the list. + * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)} + * if one or more of them is invalid. + * Please use {@link WifiNetworkSuggestion.Builder} to create network suggestions. */ public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID = 7; @@ -1887,7 +1897,7 @@ public class WifiManager { * <li> If user reset network settings, all added suggestions will be discarded. Apps can use * {@link #getNetworkSuggestions()} to check if their suggestions are in the device.</li> * <li> In-place modification of existing suggestions are allowed. - * <li>If the provided suggestions includes any previously provided suggestions by the app, + * <li> If the provided suggestions include any previously provided suggestions by the app, * previous suggestions will be updated.</li> * <li>If one of the provided suggestions marks a previously unmetered suggestion as metered and * the device is currently connected to that suggested network, then the device will disconnect |