Merge "7/n Use display area instead of display id in ATM methods" into rvc-dev
diff --git a/Android.bp b/Android.bp
index d4ca706..03a6af5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -339,9 +339,7 @@
"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 60f6174..f06f279 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -256,18 +256,16 @@
/////////////////////////////////////////////////////////////////////
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_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 73b4a19..836e6b6 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 @@
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 5f418d4..67cd0d7 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -67,7 +67,7 @@
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 @@
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 e64edc3..dbfdcba 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 @@
// 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 c8ca44b..49b3ec1 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.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.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 @@
}
}
- void addCommitters(ArraySet<Committer> committers) {
+ void setCommitters(ArraySet<Committer> committers) {
synchronized (mMetadataLock) {
mCommitters.clear();
mCommitters.addAll(committers);
@@ -153,11 +156,16 @@
}
@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 @@
}
}
- void addLeasees(ArraySet<Leasee> leasees) {
+ void setLeasees(ArraySet<Leasee> leasees) {
synchronized (mMetadataLock) {
mLeasees.clear();
mLeasees.addAll(leasees);
@@ -380,8 +388,7 @@
}
// 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 f2c1586..6af1178 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 @@
// 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 @@
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 @@
< 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 e472d05..35a2436 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.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 @@
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 fabce76..1d07e88 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.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 @@
? 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 0000000..409a048
--- /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
similarity index 100%
rename from apex/media/framework/java/android/media/Controller2Link.aidl
rename to 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
similarity index 100%
rename from apex/media/framework/java/android/media/IMediaController2.aidl
rename to 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
similarity index 100%
rename from apex/media/framework/java/android/media/IMediaSession2.aidl
rename to 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
similarity index 100%
rename from apex/media/framework/java/android/media/IMediaSession2Service.aidl
rename to 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
similarity index 100%
rename from apex/media/framework/java/android/media/Session2Command.aidl
rename to 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
similarity index 100%
rename from apex/media/framework/java/android/media/Session2Token.aidl
rename to apex/media/aidl/stable/android/media/Session2Token.aidl
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 579963b..34fe228 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -55,17 +55,15 @@
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 @@
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 d5b5949..80308d2 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -31,6 +31,11 @@
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 @@
*/
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 c1ba73f..dc477a5 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 @@
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 66e41cc..ce5309e 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 @@
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 @@
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 @@
mContext.unregisterReceiver(receiver);
}
statsdNotReadyLocked();
+ mSentBootComplete = false;
}
}
}
@@ -758,6 +784,28 @@
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 58c78da..90764b0 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 @@
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 26ccf03..9e0fb84 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 e55ea6c..75fc7f7 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -149,15 +149,21 @@
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/Android.bp b/cmds/statsd/Android.bp
index 65061d0..b357904 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -301,6 +301,8 @@
"-Wno-unused-parameter",
],
+ require_root: true,
+
srcs: [
// atom_field_options.proto needs field_options.proto, but that is
// not included in libprotobuf-cpp-lite, so compile it here.
@@ -373,10 +375,6 @@
include_dirs: ["external/protobuf/src"],
},
- shared_libs: [
- "libprotobuf-cpp-lite",
- ],
-
}
//#############################
diff --git a/cmds/statsd/AndroidTest.xml b/cmds/statsd/AndroidTest.xml
deleted file mode 100644
index afe30a2..0000000
--- a/cmds/statsd/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<configuration description="Config for statsd_test">
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="cleanup" value="true" />
- <option name="push" value="statsd_test->/data/nativetest/statsd_test" />
- </target_preparer>
- <option name="test-suite-tag" value="apct" />
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/nativetest" />
- <option name="module-name" value="statsd_test" />
- </test>
-</configuration>
\ No newline at end of file
diff --git a/cmds/statsd/TEST_MAPPING b/cmds/statsd/TEST_MAPPING
new file mode 100644
index 0000000..8dee073
--- /dev/null
+++ b/cmds/statsd/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit" : [
+ {
+ "name" : "statsd_test"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 9169eb17..dd1d400 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1054,6 +1054,14 @@
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 @@
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 114c84f..23d4c1b 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -64,6 +64,7 @@
virtual Status systemRunning();
virtual Status statsCompanionReady();
+ virtual Status bootCompleted();
virtual Status informAnomalyAlarmFired();
virtual Status informPollAlarmFired();
virtual Status informAlarmForSubscriberTriggeringFired();
@@ -165,6 +166,11 @@
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 38b421f..453ddeb 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -384,12 +384,12 @@
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 @@
* 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 @@
* 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 @@
* 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 @@
// 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 @@
* 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 @@
* 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/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index ed98f50..f4247ec 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -181,12 +181,15 @@
message GaugeMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+ // Currently unsupported
+ repeated StateValue slice_by_state = 6;
repeated GaugeBucketInfo bucket_info = 3;
repeated DimensionsValue dimension_leaf_values_in_what = 4;
+ optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
}
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 9e7b7c8..d29394b 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -1564,7 +1564,7 @@
// Trigger Activation 1 for Metric 1. Should activate on boot.
// Trigger Activation 4 for Metric 2. Should activate immediately.
- long configAddedTimeNs = metricsManager1->mLastReportTimeNs;
+ int64_t configAddedTimeNs = metricsManager1->mLastReportTimeNs;
std::vector<int> attributionUids = {111};
std::vector<string> attributionTags = {"App1"};
std::unique_ptr<LogEvent> event1 = CreateAcquireWakelockEvent(
diff --git a/cmds/statsd/tests/StatsService_test.cpp b/cmds/statsd/tests/StatsService_test.cpp
index 86f786e..cc38c4a 100644
--- a/cmds/statsd/tests/StatsService_test.cpp
+++ b/cmds/statsd/tests/StatsService_test.cpp
@@ -65,7 +65,6 @@
args.push(String8("-1"));
args.push(String8("0"));
args.push(String8("1"));
- args.push(String8("9999999999999999999999999999999999"));
args.push(String8("a1"));
args.push(String8(""));
@@ -85,14 +84,11 @@
EXPECT_TRUE(service->getUidFromArgs(args, 2, uid));
EXPECT_EQ(1, uid);
- // "999999999999999999"
+ // "a1"
EXPECT_FALSE(service->getUidFromArgs(args, 3, uid));
- // "a1"
- EXPECT_FALSE(service->getUidFromArgs(args, 4, uid));
-
// ""
- EXPECT_FALSE(service->getUidFromArgs(args, 5, uid));
+ EXPECT_FALSE(service->getUidFromArgs(args, 4, uid));
// For a non-userdebug, uid "1" cannot be impersonated.
service->mEngBuild = false;
diff --git a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
index 69326cb..a5da9c8 100644
--- a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -141,36 +141,38 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
- EXPECT_EQ(3, reports.reports(0).metrics(0).count_metrics().data_size());
+ StatsLogReport::CountMetricDataWrapper countMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+ EXPECT_EQ(3, countMetrics.data_size());
// For each CountMetricData, check StateValue info is correct and buckets
// have correct counts.
- auto data = reports.reports(0).metrics(0).count_metrics().data(0);
- EXPECT_EQ(1, data.slice_by_state_size());
- EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
- EXPECT_TRUE(data.slice_by_state(0).has_value());
- EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
- EXPECT_EQ(2, data.bucket_info_size());
- EXPECT_EQ(1, data.bucket_info(0).count());
- EXPECT_EQ(1, data.bucket_info(1).count());
-
- data = reports.reports(0).metrics(0).count_metrics().data(1);
+ auto data = countMetrics.data(0);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN,
data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
+ ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(1, data.bucket_info(0).count());
- data = reports.reports(0).metrics(0).count_metrics().data(2);
+ data = countMetrics.data(1);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
- EXPECT_EQ(2, data.bucket_info_size());
+ ASSERT_EQ(2, data.bucket_info_size());
EXPECT_EQ(1, data.bucket_info(0).count());
EXPECT_EQ(2, data.bucket_info(1).count());
+
+ data = countMetrics.data(2);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+ ASSERT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(1, data.bucket_info(0).count());
+ EXPECT_EQ(1, data.bucket_info(1).count());
}
/**
@@ -191,7 +193,9 @@
auto syncStartMatcher = CreateSyncStartAtomMatcher();
*config.add_atom_matcher() = syncStartMatcher;
- auto state = CreateScreenStateWithOnOffMap();
+ int64_t screenOnId = 4444;
+ int64_t screenOffId = 9876;
+ auto state = CreateScreenStateWithOnOffMap(screenOnId, screenOffId);
*config.add_state() = state;
// Create count metric that slices by screen state with on/off map.
@@ -321,11 +325,13 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
- EXPECT_EQ(3, reports.reports(0).metrics(0).count_metrics().data_size());
+ StatsLogReport::CountMetricDataWrapper countMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+ EXPECT_EQ(3, countMetrics.data_size());
// For each CountMetricData, check StateValue info is correct and buckets
// have correct counts.
- auto data = reports.reports(0).metrics(0).count_metrics().data(0);
+ auto data = countMetrics.data(0);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
@@ -333,23 +339,23 @@
EXPECT_EQ(1, data.bucket_info_size());
EXPECT_EQ(1, data.bucket_info(0).count());
- data = reports.reports(0).metrics(0).count_metrics().data(1);
+ data = countMetrics.data(1);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_group_id());
- EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id());
- EXPECT_EQ(2, data.bucket_info_size());
- EXPECT_EQ(4, data.bucket_info(0).count());
- EXPECT_EQ(2, data.bucket_info(1).count());
-
- data = reports.reports(0).metrics(0).count_metrics().data(2);
- EXPECT_EQ(1, data.slice_by_state_size());
- EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
- EXPECT_TRUE(data.slice_by_state(0).has_group_id());
- EXPECT_EQ(StringToId("SCREEN_ON"), data.slice_by_state(0).group_id());
+ EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id());
EXPECT_EQ(2, data.bucket_info_size());
EXPECT_EQ(1, data.bucket_info(0).count());
EXPECT_EQ(1, data.bucket_info(1).count());
+
+ data = countMetrics.data(2);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+ EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(4, data.bucket_info(0).count());
+ EXPECT_EQ(2, data.bucket_info(1).count());
}
/**
@@ -499,50 +505,52 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
- EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size());
+ StatsLogReport::CountMetricDataWrapper countMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+ EXPECT_EQ(5, countMetrics.data_size());
// For each CountMetricData, check StateValue info is correct and buckets
// have correct counts.
- auto data = reports.reports(0).metrics(0).count_metrics().data(0);
- EXPECT_EQ(1, data.slice_by_state_size());
- EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
- EXPECT_TRUE(data.slice_by_state(0).has_value());
- EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
- EXPECT_EQ(1, data.bucket_info(0).count());
-
- data = reports.reports(0).metrics(0).count_metrics().data(1);
+ auto data = countMetrics.data(0);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
+ ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(1, data.bucket_info(0).count());
- data = reports.reports(0).metrics(0).count_metrics().data(2);
- EXPECT_EQ(1, data.slice_by_state_size());
- EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
- EXPECT_TRUE(data.slice_by_state(0).has_value());
- EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
- EXPECT_EQ(2, data.bucket_info(0).count());
-
- data = reports.reports(0).metrics(0).count_metrics().data(3);
+ data = countMetrics.data(1);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::app::PROCESS_STATE_TOP, data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
+ ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(2, data.bucket_info(0).count());
- data = reports.reports(0).metrics(0).count_metrics().data(4);
+ data = countMetrics.data(2);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(0).value());
- EXPECT_EQ(2, data.bucket_info_size());
+ ASSERT_EQ(2, data.bucket_info_size());
EXPECT_EQ(1, data.bucket_info(0).count());
EXPECT_EQ(2, data.bucket_info(1).count());
+
+ data = countMetrics.data(3);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(2, data.bucket_info(0).count());
+
+ data = countMetrics.data(4);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(1, data.bucket_info(0).count());
}
TEST(CountMetricE2eTest, TestMultipleSlicedStates) {
@@ -554,7 +562,9 @@
CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
*config.add_atom_matcher() = appCrashMatcher;
- auto state1 = CreateScreenStateWithOnOffMap();
+ int64_t screenOnId = 4444;
+ int64_t screenOffId = 9876;
+ auto state1 = CreateScreenStateWithOnOffMap(screenOnId, screenOffId);
*config.add_state() = state1;
auto state2 = CreateUidProcessState();
*config.add_state() = state2;
@@ -725,22 +735,13 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
- EXPECT_EQ(6, reports.reports(0).metrics(0).count_metrics().data_size());
+ StatsLogReport::CountMetricDataWrapper countMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+ EXPECT_EQ(6, countMetrics.data_size());
// For each CountMetricData, check StateValue info is correct and buckets
// have correct counts.
- auto data = reports.reports(0).metrics(0).count_metrics().data(0);
- EXPECT_EQ(2, data.slice_by_state_size());
- EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
- EXPECT_TRUE(data.slice_by_state(0).has_group_id());
- EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id());
- EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
- EXPECT_TRUE(data.slice_by_state(1).has_value());
- EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(1).value());
- EXPECT_EQ(1, data.bucket_info_size());
- EXPECT_EQ(1, data.bucket_info(0).count());
-
- data = reports.reports(0).metrics(0).count_metrics().data(1);
+ auto data = countMetrics.data(0);
EXPECT_EQ(2, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
@@ -748,53 +749,64 @@
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
EXPECT_TRUE(data.slice_by_state(1).has_value());
EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value());
- EXPECT_EQ(1, data.bucket_info_size());
+ ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(1, data.bucket_info(0).count());
- data = reports.reports(0).metrics(0).count_metrics().data(2);
+ data = countMetrics.data(1);
EXPECT_EQ(2, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_group_id());
- EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id());
+ EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id());
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
EXPECT_TRUE(data.slice_by_state(1).has_value());
EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value());
- EXPECT_EQ(2, data.bucket_info_size());
- EXPECT_EQ(2, data.bucket_info(0).count());
- EXPECT_EQ(1, data.bucket_info(1).count());
-
- data = reports.reports(0).metrics(0).count_metrics().data(3);
- EXPECT_EQ(2, data.slice_by_state_size());
- EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
- EXPECT_TRUE(data.slice_by_state(0).has_group_id());
- EXPECT_EQ(StringToId("SCREEN_ON"), data.slice_by_state(0).group_id());
- EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
- EXPECT_TRUE(data.slice_by_state(1).has_value());
- EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value());
- EXPECT_EQ(1, data.bucket_info_size());
+ ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(1, data.bucket_info(0).count());
- data = reports.reports(0).metrics(0).count_metrics().data(4);
+ data = countMetrics.data(2);
EXPECT_EQ(2, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_group_id());
- EXPECT_EQ(StringToId("SCREEN_ON"), data.slice_by_state(0).group_id());
+ EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id());
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
EXPECT_TRUE(data.slice_by_state(1).has_value());
EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND, data.slice_by_state(1).value());
- EXPECT_EQ(1, data.bucket_info_size());
+ ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(1, data.bucket_info(0).count());
- data = reports.reports(0).metrics(0).count_metrics().data(5);
+ data = countMetrics.data(3);
EXPECT_EQ(2, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_group_id());
- EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id());
+ EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id());
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
EXPECT_TRUE(data.slice_by_state(1).has_value());
EXPECT_EQ(android::app::PROCESS_STATE_TOP, data.slice_by_state(1).value());
- EXPECT_EQ(1, data.bucket_info_size());
+ ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(2, data.bucket_info(0).count());
+
+ data = countMetrics.data(4);
+ EXPECT_EQ(2, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+ EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
+ EXPECT_TRUE(data.slice_by_state(1).has_value());
+ EXPECT_EQ(android::app::PROCESS_STATE_FOREGROUND_SERVICE, data.slice_by_state(1).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(1, data.bucket_info(0).count());
+
+ data = countMetrics.data(5);
+ EXPECT_EQ(2, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+ EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(1).atom_id());
+ EXPECT_TRUE(data.slice_by_state(1).has_value());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND, data.slice_by_state(1).value());
+ ASSERT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(2, data.bucket_info(0).count());
+ EXPECT_EQ(1, data.bucket_info(1).count());
}
} // namespace statsd
diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index 2659944..ba09a35 100644
--- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -98,8 +98,9 @@
EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id());
EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
- const StatsLogReport::DurationMetricDataWrapper& durationMetrics =
- reports.reports(0).metrics(0).duration_metrics();
+ StatsLogReport::DurationMetricDataWrapper durationMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+ &durationMetrics);
EXPECT_EQ(1, durationMetrics.data_size());
DurationMetricData data = durationMetrics.data(0);
@@ -180,8 +181,9 @@
EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id());
EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
- const StatsLogReport::DurationMetricDataWrapper& durationMetrics =
- reports.reports(0).metrics(0).duration_metrics();
+ StatsLogReport::DurationMetricDataWrapper durationMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+ &durationMetrics);
EXPECT_EQ(1, durationMetrics.data_size());
DurationMetricData data = durationMetrics.data(0);
@@ -350,8 +352,9 @@
EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id());
EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
- const StatsLogReport::DurationMetricDataWrapper& durationMetrics =
- reports.reports(0).metrics(0).duration_metrics();
+ StatsLogReport::DurationMetricDataWrapper durationMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+ &durationMetrics);
EXPECT_EQ(1, durationMetrics.data_size());
DurationMetricData data = durationMetrics.data(0);
@@ -433,9 +436,12 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
- EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
+ StatsLogReport::DurationMetricDataWrapper durationMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+ &durationMetrics);
+ EXPECT_EQ(1, durationMetrics.data_size());
- DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = durationMetrics.data(0);
// Validate bucket info.
EXPECT_EQ(1, data.bucket_info_size());
@@ -532,9 +538,12 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
- EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
+ StatsLogReport::DurationMetricDataWrapper durationMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+ &durationMetrics);
+ EXPECT_EQ(1, durationMetrics.data_size());
- DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = durationMetrics.data(0);
// Validate dimension value.
ValidateAttributionUidDimension(data.dimensions_in_what(),
util::WAKELOCK_STATE_CHANGED, appUid);
@@ -690,9 +699,12 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
- EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
+ StatsLogReport::DurationMetricDataWrapper durationMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+ &durationMetrics);
+ EXPECT_EQ(1, durationMetrics.data_size());
- DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = durationMetrics.data(0);
// Validate dimension value.
ValidateAttributionUidDimension(data.dimensions_in_what(),
util::WAKELOCK_STATE_CHANGED, appUid);
@@ -811,9 +823,12 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
- EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size());
+ StatsLogReport::DurationMetricDataWrapper durationMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+ &durationMetrics);
+ EXPECT_EQ(3, durationMetrics.data_size());
- DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = durationMetrics.data(0);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
@@ -826,7 +841,7 @@
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
- data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ data = durationMetrics.data(1);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
@@ -839,7 +854,7 @@
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
- data = reports.reports(0).metrics(0).duration_metrics().data(2);
+ data = durationMetrics.data(2);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
@@ -970,9 +985,12 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
- EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size());
+ StatsLogReport::DurationMetricDataWrapper durationMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+ &durationMetrics);
+ EXPECT_EQ(3, durationMetrics.data_size());
- DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = durationMetrics.data(0);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
@@ -985,7 +1003,7 @@
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
- data = reports.reports(0).metrics(0).duration_metrics().data(2);
+ data = durationMetrics.data(1);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
@@ -998,7 +1016,7 @@
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
- data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ data = durationMetrics.data(2);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
@@ -1020,7 +1038,9 @@
auto batterySaverModePredicate = CreateBatterySaverModePredicate();
*config.add_predicate() = batterySaverModePredicate;
- auto screenStateWithMap = CreateScreenStateWithOnOffMap();
+ int64_t screenOnId = 4444;
+ int64_t screenOffId = 9876;
+ auto screenStateWithMap = CreateScreenStateWithOnOffMap(screenOnId, screenOffId);
*config.add_state() = screenStateWithMap;
// Create duration metric that slices by mapped screen state.
@@ -1123,13 +1143,16 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
- EXPECT_EQ(2, reports.reports(0).metrics(0).duration_metrics().data_size());
+ StatsLogReport::DurationMetricDataWrapper durationMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+ &durationMetrics);
+ EXPECT_EQ(2, durationMetrics.data_size());
- DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = durationMetrics.data(0);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_group_id());
- EXPECT_EQ(StringToId("SCREEN_ON"), data.slice_by_state(0).group_id());
+ EXPECT_EQ(screenOnId, data.slice_by_state(0).group_id());
EXPECT_EQ(2, data.bucket_info_size());
EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -1138,11 +1161,11 @@
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
- data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ data = durationMetrics.data(1);
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_group_id());
- EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id());
+ EXPECT_EQ(screenOffId, data.slice_by_state(0).group_id());
EXPECT_EQ(2, data.bucket_info_size());
EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -1314,47 +1337,25 @@
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
- EXPECT_EQ(9, reports.reports(0).metrics(0).duration_metrics().data_size());
+ StatsLogReport::DurationMetricDataWrapper durationMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+ &durationMetrics);
+ EXPECT_EQ(9, durationMetrics.data_size());
- DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = durationMetrics.data(0);
ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
- "wakelock2");
+ "wakelock1");
EXPECT_EQ(1, data.slice_by_state_size());
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
- EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
- data = reports.reports(0).metrics(0).duration_metrics().data(1);
- ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
- "wakelock2");
- EXPECT_EQ(1, data.slice_by_state_size());
- EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
- EXPECT_TRUE(data.slice_by_state(0).has_value());
- EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
- data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
- EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
- EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
- EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
-
- data = reports.reports(0).metrics(0).duration_metrics().data(2);
- ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
- "wakelock1");
- EXPECT_EQ(1, data.slice_by_state_size());
- EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
- EXPECT_TRUE(data.slice_by_state(0).has_value());
- EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
- EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
- EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
- EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
-
- data = reports.reports(0).metrics(0).duration_metrics().data(3);
+ data = durationMetrics.data(1);
ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
"wakelock1");
EXPECT_EQ(1, data.slice_by_state_size());
@@ -1362,7 +1363,7 @@
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
data.slice_by_state(0).value());
- EXPECT_EQ(2, data.bucket_info_size());
+ ASSERT_EQ(2, data.bucket_info_size());
EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
@@ -1370,7 +1371,45 @@
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
- data = reports.reports(0).metrics(0).duration_metrics().data(4);
+ data = durationMetrics.data(2);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = durationMetrics.data(3);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = durationMetrics.data(4);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock1");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = durationMetrics.data(5);
ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
"wakelock1");
EXPECT_EQ(1, data.slice_by_state_size());
@@ -1378,12 +1417,24 @@
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
+ ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
- data = reports.reports(0).metrics(0).duration_metrics().data(5);
+ data = durationMetrics.data(6);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = durationMetrics.data(7);
ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
"wakelock2");
EXPECT_EQ(1, data.slice_by_state_size());
@@ -1391,7 +1442,7 @@
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE,
data.slice_by_state(0).value());
- EXPECT_EQ(2, data.bucket_info_size());
+ ASSERT_EQ(2, data.bucket_info_size());
EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
@@ -1399,32 +1450,7 @@
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
- data = reports.reports(0).metrics(0).duration_metrics().data(6);
- ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
- "wakelock2");
- EXPECT_EQ(1, data.slice_by_state_size());
- EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
- EXPECT_TRUE(data.slice_by_state(0).has_value());
- EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
- EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
- EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
- EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
-
- data = reports.reports(0).metrics(0).duration_metrics().data(7);
- ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
- "wakelock1");
- EXPECT_EQ(1, data.slice_by_state_size());
- EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
- EXPECT_TRUE(data.slice_by_state(0).has_value());
- EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
- data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
- EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
- EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
- EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
-
- data = reports.reports(0).metrics(0).duration_metrics().data(8);
+ data = durationMetrics.data(8);
ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
"wakelock2");
EXPECT_EQ(1, data.slice_by_state_size());
@@ -1432,7 +1458,7 @@
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
data.slice_by_state(0).value());
- EXPECT_EQ(1, data.bucket_info_size());
+ ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
diff --git a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
index f1e2744..ba8d283 100644
--- a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
@@ -391,7 +391,6 @@
backfillStartEndTimestamp(&reports);
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
- EXPECT_EQ(4, reports.reports(0).metrics(0).count_metrics().data_size());
StatsLogReport::CountMetricDataWrapper countMetrics;
sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
@@ -699,7 +698,6 @@
backfillStartEndTimestamp(&reports);
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
- EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size());
StatsLogReport::CountMetricDataWrapper countMetrics;
sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
@@ -1033,7 +1031,6 @@
backfillStartEndTimestamp(&reports);
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
- EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size());
StatsLogReport::CountMetricDataWrapper countMetrics;
sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
@@ -1257,7 +1254,6 @@
backfillStartEndTimestamp(&reports);
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(1, reports.reports(0).metrics_size());
- EXPECT_EQ(3, reports.reports(0).metrics(0).count_metrics().data_size());
StatsLogReport::CountMetricDataWrapper countMetrics;
sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
@@ -1695,8 +1691,6 @@
backfillStartEndTimestamp(&reports);
EXPECT_EQ(1, reports.reports_size());
EXPECT_EQ(2, reports.reports(0).metrics_size());
- EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size());
- EXPECT_EQ(5, reports.reports(0).metrics(1).count_metrics().data_size());
StatsLogReport::CountMetricDataWrapper countMetrics;
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 009e49a5..3b4d646 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -3726,7 +3726,8 @@
return true;
}));
- const StateMap& stateMap = CreateScreenStateOnOffMap();
+ const StateMap& stateMap =
+ CreateScreenStateOnOffMap(/*screen on id=*/321, /*screen off id=*/123);
const StateMap_StateGroup screenOnGroup = stateMap.group(0);
const StateMap_StateGroup screenOffGroup = stateMap.group(1);
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index ed3cf5b..687014f 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -324,40 +324,29 @@
return state;
}
-State CreateScreenStateWithOnOffMap() {
+State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId) {
State state;
state.set_id(StringToId("ScreenStateOnOff"));
state.set_atom_id(util::SCREEN_STATE_CHANGED);
- auto map = CreateScreenStateOnOffMap();
+ auto map = CreateScreenStateOnOffMap(screenOnId, screenOffId);
*state.mutable_map() = map;
return state;
}
-State CreateScreenStateWithInDozeMap() {
- State state;
- state.set_id(StringToId("ScreenStateInDoze"));
- state.set_atom_id(util::SCREEN_STATE_CHANGED);
-
- auto map = CreateScreenStateInDozeMap();
- *state.mutable_map() = map;
-
- return state;
-}
-
-StateMap_StateGroup CreateScreenStateOnGroup() {
+StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId) {
StateMap_StateGroup group;
- group.set_group_id(StringToId("SCREEN_ON"));
+ group.set_group_id(screenOnId);
group.add_value(2);
group.add_value(5);
group.add_value(6);
return group;
}
-StateMap_StateGroup CreateScreenStateOffGroup() {
+StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId) {
StateMap_StateGroup group;
- group.set_group_id(StringToId("SCREEN_OFF"));
+ group.set_group_id(screenOffId);
group.add_value(0);
group.add_value(1);
group.add_value(3);
@@ -365,36 +354,10 @@
return group;
}
-StateMap CreateScreenStateOnOffMap() {
+StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId) {
StateMap map;
- *map.add_group() = CreateScreenStateOnGroup();
- *map.add_group() = CreateScreenStateOffGroup();
- return map;
-}
-
-StateMap_StateGroup CreateScreenStateInDozeGroup() {
- StateMap_StateGroup group;
- group.set_group_id(StringToId("SCREEN_DOZE"));
- group.add_value(3);
- group.add_value(4);
- return group;
-}
-
-StateMap_StateGroup CreateScreenStateNotDozeGroup() {
- StateMap_StateGroup group;
- group.set_group_id(StringToId("SCREEN_NOT_DOZE"));
- group.add_value(0);
- group.add_value(1);
- group.add_value(2);
- group.add_value(5);
- group.add_value(6);
- return group;
-}
-
-StateMap CreateScreenStateInDozeMap() {
- StateMap map;
- *map.add_group() = CreateScreenStateInDozeGroup();
- *map.add_group() = CreateScreenStateNotDozeGroup();
+ *map.add_group() = CreateScreenStateOnGroup(screenOnId);
+ *map.add_group() = CreateScreenStateOffGroup(screenOffId);
return map;
}
@@ -1038,6 +1001,27 @@
}
}
+bool LessThan(const google::protobuf::RepeatedPtrField<StateValue>& s1,
+ const google::protobuf::RepeatedPtrField<StateValue>& s2) {
+ if (s1.size() != s2.size()) {
+ return s1.size() < s2.size();
+ }
+ for (int i = 0; i < s1.size(); i++) {
+ const StateValue& state1 = s1[i];
+ const StateValue& state2 = s2[i];
+ if (state1.atom_id() != state2.atom_id()) {
+ return state1.atom_id() < state2.atom_id();
+ }
+ if (state1.value() != state2.value()) {
+ return state1.value() < state2.value();
+ }
+ if (state1.group_id() != state2.group_id()) {
+ return state1.group_id() < state2.group_id();
+ }
+ }
+ return false;
+}
+
bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2) {
if (s1.field() != s2.field()) {
return s1.field() < s2.field();
@@ -1086,7 +1070,7 @@
return false;
}
- return LessThan(s1.dimInCondition, s2.dimInCondition);
+ return LessThan(s1.stateValues, s2.stateValues);
}
void backfillStringInDimension(const std::map<uint64_t, string>& str_map,
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index d6ea77eb..37b9889 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -138,27 +138,16 @@
// Create State proto for overlay state atom.
State CreateOverlayState();
-State CreateScreenStateWithOnOffMap();
-
-State CreateScreenStateWithInDozeMap();
+State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId);
// Create StateGroup proto for ScreenState ON group
-StateMap_StateGroup CreateScreenStateOnGroup();
+StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId);
// Create StateGroup proto for ScreenState OFF group
-StateMap_StateGroup CreateScreenStateOffGroup();
+StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId);
// Create StateMap proto for ScreenState ON/OFF map
-StateMap CreateScreenStateOnOffMap();
-
-// Create StateGroup proto for ScreenState IN DOZE group
-StateMap_StateGroup CreateScreenStateInDozeGroup();
-
-// Create StateGroup proto for ScreenState NOT IN DOZE group
-StateMap_StateGroup CreateScreenStateNotDozeGroup();
-
-// Create StateMap proto for ScreenState IN DOZE map
-StateMap CreateScreenStateInDozeMap();
+StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId);
// Add a predicate to the predicate combination.
void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
@@ -319,12 +308,14 @@
const DimensionsValue& value, int node_idx, int atomId, int uid, const std::string& tag);
struct DimensionsPair {
- DimensionsPair(DimensionsValue m1, DimensionsValue m2) : dimInWhat(m1), dimInCondition(m2){};
+ DimensionsPair(DimensionsValue m1, google::protobuf::RepeatedPtrField<StateValue> m2)
+ : dimInWhat(m1), stateValues(m2){};
DimensionsValue dimInWhat;
- DimensionsValue dimInCondition;
+ google::protobuf::RepeatedPtrField<StateValue> stateValues;
};
+bool LessThan(const StateValue& s1, const StateValue& s2);
bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2);
bool LessThan(const DimensionsPair& s1, const DimensionsPair& s2);
@@ -393,7 +384,7 @@
for (int i = 0; i < metricData.data_size(); ++i) {
dimensionIndexMap.insert(
std::make_pair(DimensionsPair(metricData.data(i).dimensions_in_what(),
- metricData.data(i).dimensions_in_condition()),
+ metricData.data(i).slice_by_state()),
i));
}
for (const auto& itr : dimensionIndexMap) {
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 d5da0b4..54a744b 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 @@
"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 d00366b..47ccc2f 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -344,7 +344,7 @@
ApkAssets apkAssets = null;
if (mLoadedApkAssets != null) {
apkAssets = mLoadedApkAssets.get(newKey);
- if (apkAssets != null) {
+ if (apkAssets != null && apkAssets.isUpToDate()) {
return apkAssets;
}
}
@@ -353,7 +353,7 @@
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 @@
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 c94d428..6bd8b1d 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.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 @@
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/hardware/camera2/params/Capability.java b/core/java/android/hardware/camera2/params/Capability.java
index 6f59c5f..ebb534a 100644
--- a/core/java/android/hardware/camera2/params/Capability.java
+++ b/core/java/android/hardware/camera2/params/Capability.java
@@ -16,8 +16,8 @@
package android.hardware.camera2.params;
-import static com.android.internal.util.Preconditions.checkArgumentInRange;
import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
import android.annotation.NonNull;
import android.hardware.camera2.CameraCharacteristics;
@@ -64,9 +64,15 @@
"maxStreamingWidth must be nonnegative");
mMaxStreamingHeight = checkArgumentNonnegative(maxStreamingHeight,
"maxStreamingHeight must be nonnegative");
- mMinZoomRatio = checkArgumentInRange(minZoomRatio, 0.0f, 1.0f,
- "minZoomRatio must be between 0.0f and 1.0f");
- mMaxZoomRatio = maxZoomRatio;
+
+ if (minZoomRatio > maxZoomRatio) {
+ throw new IllegalArgumentException("minZoomRatio " + minZoomRatio
+ + " is greater than maxZoomRatio " + maxZoomRatio);
+ }
+ mMinZoomRatio = checkArgumentPositive(minZoomRatio,
+ "minZoomRatio must be positive");
+ mMaxZoomRatio = checkArgumentPositive(maxZoomRatio,
+ "maxZoomRatio must be positive");
}
/**
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index e9bcefe..31d7d082 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -812,7 +812,7 @@
* this is the destination the probes are being redirected to, otherwise {@code null}.
*/
public void onValidationStatus(@ValidationStatus int status, @Nullable Uri redirectUri) {
- networkStatus(status, redirectUri.toString());
+ networkStatus(status, null == redirectUri ? "" : redirectUri.toString());
}
/** @hide TODO delete once subclasses have moved to onValidationStatus */
protected void networkStatus(int status, String redirectUrl) {
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 2041cfb..c87b827 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -75,7 +75,7 @@
}
/**
- * 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/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index 2dbaea8..25cb040 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -109,4 +109,9 @@
* Setting up native library directories and extract native libs onto a storage.
*/
boolean configureNativeBinaries(int storageId, in @utf8InCpp String apkFullPath, in @utf8InCpp String libDirRelativePath, in @utf8InCpp String abi);
+
+ /**
+ * Waits until all native library extraction is done for the storage
+ */
+ boolean waitForNativeBinariesExtraction(int storageId);
}
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index 7092751..70ebbaa 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -480,4 +480,18 @@
return false;
}
}
+
+ /**
+ * Waits for all native binary extraction operations to complete on the storage.
+ *
+ * @return Success of not.
+ */
+ public boolean waitForNativeBinariesExtraction() {
+ try {
+ return mService.waitForNativeBinariesExtraction(mId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
}
diff --git a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
index dd434b4..bf0bb9e 100644
--- a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
+++ b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
@@ -17,6 +17,7 @@
package android.service.autofill;
import android.os.IBinder;
+import android.os.RemoteCallback;
import android.service.autofill.IInlineSuggestionUiCallback;
import android.service.autofill.InlinePresentation;
@@ -29,4 +30,5 @@
void renderSuggestion(in IInlineSuggestionUiCallback callback,
in InlinePresentation presentation, int width, int height,
in IBinder hostInputToken, int displayId);
+ void getInlineSuggestionsRendererInfo(in RemoteCallback callback);
}
diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java
index cba6608..e3ed21f 100644
--- a/core/java/android/service/autofill/InlineSuggestionRenderService.java
+++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java
@@ -30,6 +30,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
@@ -128,6 +129,11 @@
}
}
+ private void handleGetInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) {
+ final Bundle rendererInfo = onGetInlineSuggestionsRendererInfo();
+ callback.sendResult(rendererInfo);
+ }
+
private void sendResult(@NonNull IInlineSuggestionUiCallback callback,
@Nullable SurfaceControlViewHost.SurfacePackage surface) {
try {
@@ -151,6 +157,13 @@
InlineSuggestionRenderService.this, callback, presentation,
width, height, hostInputToken, displayId));
}
+
+ @Override
+ public void getInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) {
+ mHandler.sendMessage(obtainMessage(
+ InlineSuggestionRenderService::handleGetInlineSuggestionsRendererInfo,
+ InlineSuggestionRenderService.this, callback));
+ }
}.asBinder();
}
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index c047dc0..60373ac 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -34,6 +34,8 @@
import android.util.ExceptionUtils;
import android.util.Slog;
+import libcore.io.IoUtils;
+
import java.io.IOException;
import java.util.Collection;
@@ -115,22 +117,10 @@
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/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl
index 071c259..b6ce9f5 100644
--- a/core/java/android/view/IPinnedStackListener.aidl
+++ b/core/java/android/view/IPinnedStackListener.aidl
@@ -37,13 +37,9 @@
/**
* Called when the window manager has detected a change that would cause the movement bounds
- * to be changed (ie. after configuration change, aspect ratio change, etc). It then provides
- * the components that allow the listener to calculate the movement bounds itself.
- * The {@param animatingBounds} are provided to indicate the current target bounds of the
- * pinned stack (the final bounds if animating, the current bounds if not),
- * which may be helpful in calculating dependent animation bounds.
+ * to be changed (ie. after configuration change, aspect ratio change, etc).
*/
- void onMovementBoundsChanged(in Rect animatingBounds, boolean fromImeAdjustment);
+ void onMovementBoundsChanged(boolean fromImeAdjustment);
/**
* Called when window manager decides to adjust the pinned stack bounds because of the IME, or
diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index a60a5cc..983ab2e 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -114,4 +114,16 @@
* 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 6eb90fc..925786f 100644
--- a/core/java/android/view/IRecentsAnimationRunner.aidl
+++ b/core/java/android/view/IRecentsAnimationRunner.aidl
@@ -56,4 +56,10 @@
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 81bfcb0..a0527b7 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -48,6 +48,11 @@
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 da18608..8abe72f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14684,17 +14684,19 @@
}
}
}
- 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 dab1108..ed1edc3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -111,6 +111,7 @@
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 @@
* 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 @@
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 @@
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 ab968d7..fba6a55 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -144,6 +144,7 @@
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 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 @@
// 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 316a5f2..2975d5e 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -104,7 +104,8 @@
@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 39ed401..ec51301 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -130,6 +130,20 @@
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 482d5b25..71dd665 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1968,6 +1968,38 @@
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/java/android/window/TaskOrganizerTaskEmbedder.java b/core/java/android/window/TaskOrganizerTaskEmbedder.java
index 2091c93..39a0101 100644
--- a/core/java/android/window/TaskOrganizerTaskEmbedder.java
+++ b/core/java/android/window/TaskOrganizerTaskEmbedder.java
@@ -254,7 +254,9 @@
mTaskToken = taskInfo.token;
mTaskLeash = mTaskToken.getLeash();
mTransaction.reparent(mTaskLeash, mSurfaceControl)
- .show(mSurfaceControl).apply();
+ .show(mTaskLeash)
+ .show(mSurfaceControl)
+ .apply();
if (mPendingNotifyBoundsChanged) {
// TODO: Either defer show or hide and synchronize show with the resize
notifyBoundsChanged();
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 04bf915..02cf25a 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -26,7 +26,6 @@
import static android.system.OsConstants.S_IXOTH;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageLite;
@@ -40,6 +39,7 @@
import android.os.incremental.IncrementalStorage;
import android.system.ErrnoException;
import android.system.Os;
+import android.util.ArraySet;
import android.util.Slog;
import dalvik.system.CloseGuard;
@@ -545,4 +545,18 @@
}
return false;
}
+
+ /**
+ * Wait for all native library extraction to complete for the passed storages.
+ *
+ * @param incrementalStorages A list of the storages to wait for.
+ */
+ public static void waitForNativeBinariesExtraction(
+ ArraySet<IncrementalStorage> incrementalStorages) {
+ for (int i = 0; i < incrementalStorages.size(); ++i) {
+ IncrementalStorage storage = incrementalStorages.valueAtUnchecked(i);
+ storage.waitForNativeBinariesExtraction();
+ }
+ }
+
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1c978bf..32a79f3 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/coretests/Android.bp b/core/tests/coretests/Android.bp
index 8d51a60..1e16ee0 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -72,6 +72,7 @@
":BinderDeathRecipientHelperApp1",
":BinderDeathRecipientHelperApp2",
],
+ required: ["com.android.cts.helpers.aosp"],
}
// Rules to copy all the test apks to the intermediate raw resource directory
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index ed9d3f5..04952bd 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -24,6 +24,9 @@
<option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" />
<option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
</target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" />
+
<option name="test-tag" value="FrameworksCoreTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.coretests" />
diff --git a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
index 94d85e6..4dd4d1c 100644
--- a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
+++ b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
@@ -41,6 +41,7 @@
import android.print.test.services.PrinterDiscoverySessionCallbacks;
import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.recommendation.IRecommendationsChangeListener;
+import android.support.test.uiautomator.UiDevice;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
@@ -71,6 +72,10 @@
private IPrintManager mIPrintManager;
+ public static UiDevice getUiDevice() {
+ return UiDevice.getInstance(getInstrumentation());
+ }
+
/**
* Create a new IPrintManagerParametersTest and setup basic fields.
*/
diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp
index a2fcef5..2b38cca 100644
--- a/core/tests/overlaytests/host/Android.bp
+++ b/core/tests/overlaytests/host/Android.bp
@@ -16,7 +16,7 @@
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 eec7be2..d898d22 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 @@
}
@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 @@
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 cc7704b..f3c0abd 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 @@
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 @@
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 @@
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 f8607f4..878f05d 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_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 @@
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 @@
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 @@
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 @@
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
similarity index 84%
rename from core/tests/overlaytests/remount/host/Android.bp
rename to core/tests/overlaytests/remount/Android.bp
index 3825c55..5757cfe 100644
--- a/core/tests/overlaytests/remount/host/Android.bp
+++ b/core/tests/overlaytests/remount/Android.bp
@@ -21,8 +21,12 @@
],
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
similarity index 100%
rename from core/tests/overlaytests/remount/host/AndroidTest.xml
rename to 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 0000000..3fa8bcd
--- /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 0000000..14b5bf6
--- /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
similarity index 60%
rename from core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
rename to core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
index 06b2ac8..7f2c060 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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..70e3423
--- /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
similarity index 75%
rename from core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
rename to core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java
index 8696091..bb72d0e 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 @@
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.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 @@
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 @@
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 @@
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 @@
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
similarity index 73%
rename from core/tests/overlaytests/remount/target/Android.bp
rename to core/tests/overlaytests/remount/test-apps/Overlay/Android.bp
index 83f9f28..a1fdbfd 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/target/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml
similarity index 66%
copy from core/tests/overlaytests/remount/target/AndroidManifest.xml
copy to core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml
index dc07dca..d6d706c 100644
--- a/core/tests/overlaytests/remount/target/AndroidManifest.xml
+++ b/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -16,11 +16,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- 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" />
- </application>
-</manifest>
+ 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/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
similarity index 70%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
index b5f444a..675e44f 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,6 +15,6 @@
~ limitations under the License.
-->
-<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>
+<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
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
rename to 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
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
rename to 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
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
rename to 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
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
rename to 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
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
rename to 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
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
rename to 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
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
rename to 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
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/res/values/values.xml
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/test-apps/Target/Android.bp
similarity index 72%
copy from core/tests/overlaytests/remount/target/Android.bp
copy to core/tests/overlaytests/remount/test-apps/Target/Android.bp
index 83f9f28..19947b1 100644
--- a/core/tests/overlaytests/remount/target/Android.bp
+++ b/core/tests/overlaytests/remount/test-apps/Target/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.
@@ -14,7 +14,15 @@
android_test_helper_app {
name: "OverlayRemountedTest_Target",
- srcs: ["src/**/*.java"],
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
similarity index 89%
rename from core/tests/overlaytests/remount/target/AndroidManifest.xml
rename to core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml
index dc07dca..d1c7b7e8 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/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
similarity index 70%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
index b5f444a..4aa5bce 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,6 +15,10 @@
~ limitations under the License.
-->
-<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>
+<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
similarity index 72%
rename from core/tests/overlaytests/remount/target/res/values/values.xml
rename to core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml
index b5f444a..76253a9 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/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml
similarity index 70%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml
index b5f444a..4aa5bce 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,6 +15,10 @@
~ limitations under the License.
-->
-<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>
+<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_upgrade/values/values.xml
similarity index 60%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml
index b5f444a..f552cb0 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -14,7 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<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>
-</resources>
+<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 0000000..e6ebd5e
--- /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 0000000..9a5102f
--- /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 0000000..07f27ee
--- /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 0000000..ac5f846
--- /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 19ad6f7..18086ec 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 918e7af..05f4d6b 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -385,7 +385,7 @@
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 @@
// 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 0b2fd9e..eb6ee95 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 @@
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 @@
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 @@
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 @@
// 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 ccb57f3..ecc1ce6 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -142,7 +142,13 @@
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 @@
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 @@
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 41ba637..7aa0dbb 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -38,7 +38,7 @@
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 @@
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, 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 @@
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 48aed34..861eeea 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -394,7 +394,7 @@
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 @@
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 fc9b91c..a31f177 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -676,8 +676,6 @@
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 @@
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 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 ac7fe5d..7579ca5 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -880,10 +880,12 @@
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 @@
(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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
(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 73fc38d..6749ba0 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -188,9 +188,9 @@
jobject openDvr(DvrType type, jlong bufferSize);
jobject getDemuxCaps();
jobject getFrontendStatus(jintArray types);
+ Result openDemux();
protected:
- Result openDemux();
virtual ~JTuner();
private:
@@ -204,15 +204,15 @@
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/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml
index 062f7bd..d105e44 100644
--- a/packages/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml
@@ -18,15 +18,14 @@
Car has solid black background instead of a transparent one
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/black"
- android:fitsSystemWindows="true">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black"
+ android:fitsSystemWindows="true">
<include
style="@style/BouncerSecurityContainer"
layout="@layout/keyguard_host_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
-</FrameLayout>
-
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_container.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_container.xml
new file mode 100644
index 0000000..3e35df9
--- /dev/null
+++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_container.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.
+ -->
+
+<!-- Car customizations
+ Car has solid black background instead of a transparent one
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"/>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
index 6ecab51..534c51e 100644
--- a/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
+++ b/packages/CarSystemUI/res/layout/car_fullscreen_user_switcher.xml
@@ -18,7 +18,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fullscreen_user_switcher"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:background="@color/car_user_switcher_background_color">
<LinearLayout
android:id="@+id/container"
diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
index 067e359..3542323 100644
--- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
+++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
@@ -29,6 +29,11 @@
android:layout="@layout/notification_panel_container"
android:layout_marginBottom="@dimen/navigation_bar_height"/>
+ <ViewStub android:id="@+id/keyguard_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout="@layout/keyguard_container" />
+
<ViewStub android:id="@+id/fullscreen_user_switcher_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index 0b56d05..bf1bf38 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -71,8 +71,9 @@
<!-- Car System UI's OverlayViewsMediator-->
<string-array name="config_carSystemUIOverlayViewsMediators" translatable="false">
- <item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
<item>com.android.systemui.car.notification.NotificationPanelViewMediator</item>
+ <item>com.android.systemui.car.keyguard.CarKeyguardViewMediator</item>
+ <item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
</string-array>
<!-- SystemUI Services: The classes of the stuff to start. -->
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index c275536..4ea48ba 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -24,6 +24,7 @@
import com.android.keyguard.KeyguardViewController;
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarDeviceProvisionedControllerImpl;
+import com.android.systemui.car.keyguard.CarKeyguardViewController;
import com.android.systemui.dagger.SystemUIRootComponent;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
@@ -146,7 +147,7 @@
@Binds
abstract KeyguardViewController bindKeyguardViewController(
- CarStatusBarKeyguardViewManager keyguardViewManager);
+ CarKeyguardViewController carKeyguardViewController);
@Binds
abstract DeviceProvisionedController bindDeviceProvisionedController(
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
new file mode 100644
index 0000000..707ee4f
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.keyguard;
+
+import android.car.Car;
+import android.car.user.CarUserManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.R;
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.navigationbar.car.CarNavigationBarController;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.window.OverlayViewController;
+import com.android.systemui.window.OverlayViewGlobalStateController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Automotive implementation of the {@link KeyguardViewController}. It controls the Keyguard View
+ * that is mounted to the SystemUIOverlayWindow.
+ */
+@Singleton
+public class CarKeyguardViewController extends OverlayViewController implements
+ KeyguardViewController {
+ private static final String TAG = "CarKeyguardViewController";
+ private static final boolean DEBUG = true;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final CarServiceProvider mCarServiceProvider;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final LockPatternUtils mLockPatternUtils;
+ private final FalsingManager mFalsingManager;
+ private final KeyguardBypassController mKeyguardBypassController;
+ private final DismissCallbackRegistry mDismissCallbackRegistry;
+ private final ViewMediatorCallback mViewMediatorCallback;
+ private final CarNavigationBarController mCarNavigationBarController;
+ // Needed to instantiate mBouncer.
+ private final KeyguardBouncer.BouncerExpansionCallback
+ mExpansionCallback = new KeyguardBouncer.BouncerExpansionCallback() {
+ @Override
+ public void onFullyShown() {
+ }
+
+ @Override
+ public void onStartingToHide() {
+ }
+
+ @Override
+ public void onStartingToShow() {
+ }
+
+ @Override
+ public void onFullyHidden() {
+ }
+ };
+ private final CarUserManager.UserLifecycleListener mUserLifecycleListener = (e) -> {
+ if (e.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) {
+ revealKeyguardIfBouncerPrepared();
+ }
+ };
+
+ private KeyguardBouncer mBouncer;
+ private OnKeyguardCancelClickedListener mKeyguardCancelClickedListener;
+ private boolean mShowing;
+
+ @Inject
+ public CarKeyguardViewController(
+ Context context,
+ @Main Handler mainHandler,
+ CarServiceProvider carServiceProvider,
+ OverlayViewGlobalStateController overlayViewGlobalStateController,
+ KeyguardStateController keyguardStateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ BiometricUnlockController biometricUnlockController,
+ ViewMediatorCallback viewMediatorCallback,
+ CarNavigationBarController carNavigationBarController,
+ /* The params below are only used to reuse KeyguardBouncer */
+ LockPatternUtils lockPatternUtils,
+ DismissCallbackRegistry dismissCallbackRegistry,
+ FalsingManager falsingManager,
+ KeyguardBypassController keyguardBypassController) {
+
+ super(R.id.keyguard_stub, overlayViewGlobalStateController);
+
+ mContext = context;
+ mHandler = mainHandler;
+ mCarServiceProvider = carServiceProvider;
+ mKeyguardStateController = keyguardStateController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
+ mFalsingManager = falsingManager;
+ mKeyguardBypassController = keyguardBypassController;
+ mDismissCallbackRegistry = dismissCallbackRegistry;
+ mViewMediatorCallback = viewMediatorCallback;
+ mCarNavigationBarController = carNavigationBarController;
+
+ biometricUnlockController.setKeyguardViewController(this);
+ registerUserSwitchedListener();
+ }
+
+ @Override
+ public void onFinishInflate() {
+ mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
+ mViewMediatorCallback, mLockPatternUtils,
+ getLayout().findViewById(R.id.keyguard_container), mDismissCallbackRegistry,
+ mExpansionCallback, mKeyguardStateController, mFalsingManager,
+ mKeyguardBypassController);
+ }
+
+ @Override
+ public void notifyKeyguardAuthenticated(boolean strongAuth) {
+ mBouncer.notifyKeyguardAuthenticated(strongAuth);
+ }
+
+ @Override
+ public void showBouncer(boolean scrimmed) {
+ if (mShowing && !mBouncer.isShowing()) {
+ mBouncer.show(/* resetSecuritySelection= */ false);
+ }
+ }
+
+ @Override
+ public void show(Bundle options) {
+ if (mShowing) return;
+
+ mShowing = true;
+ mKeyguardStateController.notifyKeyguardState(mShowing, /* occluded= */ false);
+ mCarNavigationBarController.showAllKeyguardButtons(/* isSetUp= */ true);
+ start();
+ reset(/* hideBouncerWhenShowing= */ false);
+ notifyKeyguardUpdateMonitor();
+ }
+
+ @Override
+ public void hide(long startTime, long fadeoutDuration) {
+ if (!mShowing) return;
+
+ mViewMediatorCallback.readyForKeyguardDone();
+ mShowing = false;
+ mKeyguardStateController.notifyKeyguardState(mShowing, /* occluded= */ false);
+ mBouncer.hide(/* destroyView= */ true);
+ mCarNavigationBarController.hideAllKeyguardButtons(/* isSetUp= */ true);
+ stop();
+ mKeyguardStateController.notifyKeyguardDoneFading();
+ mViewMediatorCallback.keyguardGone();
+ notifyKeyguardUpdateMonitor();
+ }
+
+ @Override
+ public void reset(boolean hideBouncerWhenShowing) {
+ if (mShowing) {
+ if (mBouncer != null) {
+ if (!mBouncer.isSecure()) {
+ dismissAndCollapse();
+ }
+ mBouncer.show(/* resetSecuritySelection= */ true);
+ }
+ mKeyguardUpdateMonitor.sendKeyguardReset();
+ notifyKeyguardUpdateMonitor();
+ }
+ }
+
+ @Override
+ public void onFinishedGoingToSleep() {
+ mBouncer.onScreenTurnedOff();
+ }
+
+ @Override
+ public void onCancelClicked() {
+ mBouncer.hide(/* destroyView= */ true);
+ mKeyguardCancelClickedListener.onCancelClicked();
+ }
+
+ @Override
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ @Override
+ public void dismissAndCollapse() {
+ hide(/* startTime= */ 0, /* fadeoutDuration= */ 0);
+ }
+
+ @Override
+ public void startPreHideAnimation(Runnable finishRunnable) {
+ mBouncer.startPreHideAnimation(finishRunnable);
+ }
+
+ @Override
+ public void setNeedsInput(boolean needsInput) {
+ getLayout().setFocusable(needsInput);
+ }
+
+ /**
+ * Add listener for keyguard cancel clicked.
+ */
+ public void registerOnKeyguardCancelClickedListener(
+ OnKeyguardCancelClickedListener keyguardCancelClickedListener) {
+ mKeyguardCancelClickedListener = keyguardCancelClickedListener;
+ }
+
+ /**
+ * Remove listener for keyguard cancel clicked.
+ */
+ public void unregisterOnKeyguardCancelClickedListener(
+ OnKeyguardCancelClickedListener keyguardCancelClickedListener) {
+ mKeyguardCancelClickedListener = null;
+ }
+
+ @Override
+ public ViewRootImpl getViewRootImpl() {
+ return ((View) getLayout().getParent()).getViewRootImpl();
+ }
+
+ @Override
+ public boolean isBouncerShowing() {
+ return mBouncer.isShowing();
+ }
+
+ @Override
+ public boolean bouncerIsOrWillBeShowing() {
+ return mBouncer.isShowing() || mBouncer.inTransit();
+ }
+
+ @Override
+ public void keyguardGoingAway() {
+ // no-op
+ }
+
+ @Override
+ public void onStartedGoingToSleep() {
+ // no-op
+ }
+
+ @Override
+ public void onStartedWakingUp() {
+ // no-op
+ }
+
+ @Override
+ public void onScreenTurningOn() {
+ // no-op
+ }
+
+ @Override
+ public void onScreenTurnedOn() {
+ // no-op
+ }
+
+ @Override
+ public void setOccluded(boolean occluded, boolean animate) {
+ // no-op
+ }
+
+ @Override
+ public boolean shouldDisableWindowAnimationsForUnlock() {
+ return false;
+ }
+
+ @Override
+ public boolean isGoingToNotificationShade() {
+ return false;
+ }
+
+ @Override
+ public boolean isUnlockWithWallpaper() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldSubtleWindowAnimationsForUnlock() {
+ return false;
+ }
+
+ @Override
+ public void registerStatusBar(StatusBar statusBar, ViewGroup container,
+ NotificationPanelViewController notificationPanelViewController,
+ BiometricUnlockController biometricUnlockController,
+ DismissCallbackRegistry dismissCallbackRegistry, ViewGroup lockIconContainer,
+ View notificationContainer, KeyguardBypassController bypassController,
+ FalsingManager falsingManager) {
+ // no-op
+ }
+
+ /**
+ * Hides Keyguard so that the transitioning Bouncer can be hidden until it is prepared. To be
+ * called by {@link com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator}
+ * when a new user is selected.
+ */
+ public void hideKeyguardToPrepareBouncer() {
+ getLayout().setVisibility(View.INVISIBLE);
+ }
+
+ private void revealKeyguardIfBouncerPrepared() {
+ int reattemptDelayMillis = 50;
+ Runnable revealKeyguard = () -> {
+ if (!mBouncer.inTransit() || !mBouncer.isSecure()) {
+ getLayout().setVisibility(View.VISIBLE);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "revealKeyguardIfBouncerPrepared: Bouncer is not prepared "
+ + "yet so reattempting after " + reattemptDelayMillis + "ms.");
+ }
+ mHandler.postDelayed(this::revealKeyguardIfBouncerPrepared, reattemptDelayMillis);
+ }
+ };
+ mHandler.post(revealKeyguard);
+ }
+
+ private void notifyKeyguardUpdateMonitor() {
+ mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(mShowing);
+ if (mBouncer != null) {
+ mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isBouncerShowing());
+ }
+ }
+
+ private void registerUserSwitchedListener() {
+ mCarServiceProvider.addListener(car -> {
+ CarUserManager userManager = (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
+ userManager.addListener(Runnable::run, mUserLifecycleListener);
+ });
+ }
+
+ /**
+ * Defines a callback for keyguard cancel button clicked listeners.
+ */
+ public interface OnKeyguardCancelClickedListener {
+ /**
+ * Called when keyguard cancel button is clicked.
+ */
+ void onCancelClicked();
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java
new file mode 100644
index 0000000..db0f5d8
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewMediator.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car.keyguard;
+
+import com.android.systemui.car.userswitcher.FullScreenUserSwitcherViewController;
+import com.android.systemui.window.OverlayViewMediator;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Manages events originating from the Keyguard service that cause Keyguard or other OverlayWindow
+ * Components to appear or disappear.
+ */
+@Singleton
+public class CarKeyguardViewMediator implements OverlayViewMediator {
+
+ private final CarKeyguardViewController mCarKeyguardViewController;
+ private final FullScreenUserSwitcherViewController mFullScreenUserSwitcherViewController;
+
+ @Inject
+ public CarKeyguardViewMediator(
+ CarKeyguardViewController carKeyguardViewController,
+ FullScreenUserSwitcherViewController fullScreenUserSwitcherViewController
+ ) {
+ mCarKeyguardViewController = carKeyguardViewController;
+ mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController;
+ }
+
+ @Override
+ public void registerListeners() {
+ mCarKeyguardViewController.registerOnKeyguardCancelClickedListener(
+ mFullScreenUserSwitcherViewController::start);
+ }
+
+ @Override
+ public void setupOverlayContentViewControllers() {
+ // no-op
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
index 50e43be..149531f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
@@ -16,20 +16,9 @@
package com.android.systemui.car.userswitcher;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.os.Handler;
-
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.car.keyguard.CarKeyguardViewController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.car.CarStatusBar;
-import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
import com.android.systemui.window.OverlayViewMediator;
import javax.inject.Inject;
@@ -42,47 +31,24 @@
public class FullscreenUserSwitcherViewMediator implements OverlayViewMediator {
private static final String TAG = FullscreenUserSwitcherViewMediator.class.getSimpleName();
- private final Context mContext;
- private final CarStatusBarKeyguardViewManager mCarStatusBarKeyguardViewManager;
- private final Handler mMainHandler;
private final StatusBarStateController mStatusBarStateController;
private final FullScreenUserSwitcherViewController mFullScreenUserSwitcherViewController;
- private final ScreenLifecycle mScreenLifecycle;
- private final CarStatusBar mCarStatusBar;
- private final boolean mIsUserSwitcherEnabled;
+ private final CarKeyguardViewController mCarKeyguardViewController;
@Inject
public FullscreenUserSwitcherViewMediator(
- Context context,
- @Main Resources resources,
- @Main Handler mainHandler,
- CarStatusBarKeyguardViewManager carStatusBarKeyguardViewManager,
- CarStatusBar carStatusBar,
StatusBarStateController statusBarStateController,
- FullScreenUserSwitcherViewController fullScreenUserSwitcherViewController,
- ScreenLifecycle screenLifecycle) {
- mContext = context;
+ CarKeyguardViewController carKeyguardViewController,
+ FullScreenUserSwitcherViewController fullScreenUserSwitcherViewController) {
- mIsUserSwitcherEnabled = resources.getBoolean(R.bool.config_enableFullscreenUserSwitcher);
-
- mMainHandler = mainHandler;
-
- mCarStatusBarKeyguardViewManager = carStatusBarKeyguardViewManager;
- mCarStatusBar = carStatusBar;
mStatusBarStateController = statusBarStateController;
mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController;
- mScreenLifecycle = screenLifecycle;
+ mCarKeyguardViewController = carKeyguardViewController;
}
@Override
public void registerListeners() {
- registerUserSwitcherShowListeners();
registerUserSwitcherHideListeners();
- registerHideKeyguardListeners();
- }
-
- private void registerUserSwitcherShowListeners() {
- mCarStatusBarKeyguardViewManager.addOnKeyguardCancelClickedListener(this::show);
}
private void registerUserSwitcherHideListeners() {
@@ -97,37 +63,6 @@
});
}
- private void registerHideKeyguardListeners() {
- mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
- @Override
- public void onStateChanged(int newState) {
- if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) {
- return;
- }
- dismissKeyguardWhenUserSwitcherNotDisplayed(newState);
- }
- });
-
- mScreenLifecycle.addObserver(new ScreenLifecycle.Observer() {
- @Override
- public void onScreenTurnedOn() {
- dismissKeyguardWhenUserSwitcherNotDisplayed(mStatusBarStateController.getState());
- }
- });
-
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
- return;
- }
-
- // Try to dismiss the keyguard after every user switch.
- dismissKeyguardWhenUserSwitcherNotDisplayed(mStatusBarStateController.getState());
- }
- }, new IntentFilter(Intent.ACTION_USER_SWITCHED));
- }
-
@Override
public void setupOverlayContentViewControllers() {
mFullScreenUserSwitcherViewController.setUserGridSelectionListener(this::onUserSelected);
@@ -135,33 +70,13 @@
/**
* Every time user clicks on an item in the switcher, we hide the switcher.
- *
- * We dismiss the entire keyguard if user clicked on the foreground user (user we're already
- * logged in as).
*/
private void onUserSelected(UserGridRecyclerView.UserRecord record) {
+ if (record.mType != UserGridRecyclerView.UserRecord.FOREGROUND_USER) {
+ mCarKeyguardViewController.hideKeyguardToPrepareBouncer();
+ }
+
hide();
- if (record.mType == UserGridRecyclerView.UserRecord.FOREGROUND_USER) {
- mCarStatusBar.dismissKeyguard();
- }
- }
-
- // We automatically dismiss keyguard unless user switcher is being shown above the keyguard.
- private void dismissKeyguardWhenUserSwitcherNotDisplayed(int state) {
- if (!mIsUserSwitcherEnabled) {
- return; // Not using the full screen user switcher.
- }
-
- if (state == StatusBarState.FULLSCREEN_USER_SWITCHER
- && !mFullScreenUserSwitcherViewController.isVisible()) {
- // Current execution path continues to set state after this, thus we deffer the
- // dismissal to the next execution cycle.
-
- // Dismiss the keyguard if switcher is not visible.
- // TODO(b/150402329): Remove once keyguard is implemented using Overlay Window
- // abstractions.
- mMainHandler.post(mCarStatusBar::dismissKeyguard);
- }
}
private void hide() {
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java
index dd29f8d..6b4f3e3 100644
--- a/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.window;
+import com.android.systemui.car.keyguard.CarKeyguardViewMediator;
import com.android.systemui.car.notification.NotificationPanelViewMediator;
import com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator;
@@ -29,12 +30,6 @@
*/
@Module
public abstract class OverlayWindowModule {
- /** Injects FullscreenUserSwitcherViewsMediator. */
- @Binds
- @IntoMap
- @ClassKey(FullscreenUserSwitcherViewMediator.class)
- public abstract OverlayViewMediator bindFullscreenUserSwitcherViewsMediator(
- FullscreenUserSwitcherViewMediator overlayViewsMediator);
/** Injects NotificationPanelViewMediator. */
@Binds
@@ -42,4 +37,18 @@
@ClassKey(NotificationPanelViewMediator.class)
public abstract OverlayViewMediator bindNotificationPanelViewMediator(
NotificationPanelViewMediator notificationPanelViewMediator);
+
+ /** Inject into CarKeyguardViewMediator. */
+ @Binds
+ @IntoMap
+ @ClassKey(CarKeyguardViewMediator.class)
+ public abstract OverlayViewMediator bindCarKeyguardViewMediator(
+ CarKeyguardViewMediator carKeyguardViewMediator);
+
+ /** Injects FullscreenUserSwitcherViewsMediator. */
+ @Binds
+ @IntoMap
+ @ClassKey(FullscreenUserSwitcherViewMediator.class)
+ public abstract OverlayViewMediator bindFullscreenUserSwitcherViewsMediator(
+ FullscreenUserSwitcherViewMediator overlayViewsMediator);
}
diff --git a/packages/PrintSpooler/tests/outofprocess/Android.bp b/packages/PrintSpooler/tests/outofprocess/Android.bp
index c6dc263..0e028b0 100644
--- a/packages/PrintSpooler/tests/outofprocess/Android.bp
+++ b/packages/PrintSpooler/tests/outofprocess/Android.bp
@@ -27,4 +27,5 @@
sdk_version: "test_current",
test_suites: ["device-tests"],
+ required: ["com.android.cts.helpers.aosp"],
}
diff --git a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
index b649e82..922d735 100644
--- a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
+++ b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
@@ -18,6 +18,8 @@
<option name="test-file-name" value="PrintSpoolerOutOfProcessTests.apk" />
</target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" />
+
<option name="test-suite-tag" value="apct" />
<option name="test-tag" value="PrintSpoolerOutOfProcessTests" />
<option name="config-descriptor:metadata" key="component" value="print" />
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
index 61c2f54..132545b 100644
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
@@ -36,6 +36,7 @@
import android.print.test.services.PrinterDiscoverySessionCallbacks;
import android.print.test.services.StubbablePrinterDiscoverySession;
import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiSelector;
@@ -78,6 +79,10 @@
void accept(T t) throws InterruptedException;
}
+ public static UiDevice getUiDevice() {
+ return UiDevice.getInstance(getInstrumentation());
+ }
+
/**
* Execute {@code waiter} until {@code condition} is met.
*
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index ff40d8e..450bdb1 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 @@
}
/**
+ * 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 @@
// 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 9d7e2c8..b1234f2 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 @@
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 @@
* 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());
+ final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+ final int mobileDefaultSubId = telephonyManager.getSubscriptionId();
- if (!subscriptionManager.isActiveSubscriptionId(subId)) {
- Log.i(TAG, "Subscription is not active: " + subId);
- return mobileAll;
+ 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 c6f0327..133d375b 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 0000000..0d5efeb
--- /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 0000000..01f8d2d
--- /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 0000000..988ab58
--- /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 bfa252c..8342a2a 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 db81e23..55c9083 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 f2de45a..34b603f 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 0000000..90b3398
--- /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 9d5eb63..ae57563 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 42d73f3..94df9d8 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/core/tests/overlaytests/remount/target/res/values/values.xml b/packages/SystemUI/res/layout/controls_management_editing.xml
similarity index 60%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to packages/SystemUI/res/layout/controls_management_editing.xml
index b5f444a..8a14ec3 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/packages/SystemUI/res/layout/controls_management_editing.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,6 +15,13 @@
~ limitations under the License.
-->
-<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>
-</resources>
+<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 00654c8..45540f1 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 623f2a0..b323209 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 87cb5c7f..9dc502e 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 dc94697..5399f57 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 73b711d..5b36382 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 6a8a4b9..2a4d5ef 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 566d143..7c0b605 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 3e02b30..118aa5b 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 3bda3c8..806678f 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 @@
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/PinnedStackListenerForwarder.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
index 34a0268..4794847 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
@@ -18,7 +18,6 @@
import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
-import android.graphics.Rect;
import android.view.DisplayInfo;
import android.view.IPinnedStackController;
import android.view.IPinnedStackListener;
@@ -53,9 +52,9 @@
}
@Override
- public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment) {
+ public void onMovementBoundsChanged(boolean fromImeAdjustment) {
for (PinnedStackListener listener : mListeners) {
- listener.onMovementBoundsChanged(animatingBounds, fromImeAdjustment);
+ listener.onMovementBoundsChanged(fromImeAdjustment);
}
}
@@ -108,7 +107,7 @@
public static class PinnedStackListener {
public void onListenerRegistered(IPinnedStackController controller) {}
- public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment) {}
+ public void onMovementBoundsChanged(boolean fromImeAdjustment) {}
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
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 bbb83c7..76513c6 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 @@
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 2c99c5c..c4cd192 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 @@
* 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/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 1e1ce4e..5c3d17c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -19,7 +19,9 @@
import static android.view.ViewRootImpl.sNewInsetsMode;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
+
import static com.android.systemui.DejankUtils.whitelistIpcs;
+
import static java.lang.Integer.max;
import android.app.Activity;
@@ -28,7 +30,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
-import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.Looper;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 2f10394..03ccc1c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -147,6 +147,28 @@
*/
ViewRootImpl getViewRootImpl();
+ /**
+ * Notifies that the user has authenticated by other means than using the bouncer, for example,
+ * fingerprint.
+ */
+ void notifyKeyguardAuthenticated(boolean strongAuth);
+
+ /**
+ * Shows the Bouncer.
+ *
+ */
+ void showBouncer(boolean scrimmed);
+
+ /**
+ * Returns {@code true} when the bouncer is currently showing
+ */
+ boolean isBouncerShowing();
+
+ /**
+ * When bouncer is fully visible or it is showing but animation didn't finish yet.
+ */
+ boolean bouncerIsOrWillBeShowing();
+
// TODO: Deprecate registerStatusBar in KeyguardViewController interface. It is currently
// only used for testing purposes in StatusBarKeyguardViewManager, and it prevents us from
// achieving complete abstraction away from where the Keyguard View is mounted.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 61e6f39..71dbbbc 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 @@
if (show
&& mShouldShowManageEducation
&& mManageEducationView.getVisibility() != VISIBLE
- && mIsExpanded) {
+ && mIsExpanded
+ && mExpandedBubble.getExpandedView() != null) {
mManageEducationView.setAlpha(0);
mManageEducationView.setVisibility(VISIBLE);
mManageEducationView.post(() -> {
@@ -1909,7 +1910,8 @@
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 @@
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 @@
}
private void updatePointerPosition() {
- if (mExpandedBubble == null) {
+ if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
return;
}
int index = getBubbleIndex(mExpandedBubble);
@@ -2049,7 +2051,7 @@
* 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 dec6007..5891a7f 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 @@
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 6e59ac1..40606c2 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 @@
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 @@
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 568fb28..7cab847 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 @@
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 8805694..6d34009 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 @@
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 946a236..3bed559 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.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 @@
@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 11181e5..175ed06 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 @@
* @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 @@
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 @@
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 1291dd9..607934c 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 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 @@
/**
* 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 @@
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 @@
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 @@
}
}
+ 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 @@
* 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 @@
}
/**
- * 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 @@
}
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 0000000..ee1ce7a
--- /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 fe1e632..6f34dee 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 @@
// 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 @@
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 @@
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 @@
}
}
+ 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 @@
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 a995a2e..37b6d15 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 @@
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 @@
* 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 @@
* [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 0c41f7e..3be5900 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 @@
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 5c51e3d..0000000
--- 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 0000000..411170cb
--- /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 680d006..ad86eeb 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 @@
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 b1cb04e..0eb6cb1 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 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 @@
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 @@
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 @@
})
}
- 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 @@
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 @@
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 934a695..fab6fc7 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.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.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 @@
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,16 +210,30 @@
}
private fun startFavoritingActivity(context: Context, si: StructureInfo) {
- val i = Intent(context, ControlsFavoritingActivity::class.java).apply {
- putExtra(ControlsFavoritingActivity.EXTRA_APP,
- controlsListingController.get().getAppLabel(si.componentName))
- putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, si.structure)
- putExtra(Intent.EXTRA_COMPONENT_NAME, si.componentName)
+ 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))
+ putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, si.structure)
+ putExtra(Intent.EXTRA_COMPONENT_NAME, si.componentName)
+ }
+ }
+
private fun startProviderSelectorActivity(context: Context) {
val i = Intent(context, ControlsProviderSelectorActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -253,6 +267,7 @@
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 @@
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 @@
.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 @@
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 @@
baseLayout,
controlsController.get(),
uiExecutor,
- bgExecutor
+ bgExecutor,
+ usePanels
)
val key = ControlKey(selectedStructure.componentName, it.controlId)
cvh.bindData(controlsById.getValue(key))
@@ -501,6 +521,7 @@
hidden = true
popup?.dismiss()
activeDialog?.dismiss()
+ ControlActionCoordinator.closeDialog()
controlsController.get().unsubscribe()
@@ -586,7 +607,6 @@
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 d3d4287..15c41a2 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 @@
* 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 @@
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 @@
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 15c1dab..6340db1 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 @@
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 d64a5f0..b02c9c8 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.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 @@
*/
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 @@
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 a4a5894..3f095dc 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 @@
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 @@
}
}
- 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 @@
@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 @@
}
}
+ @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 @@
}
}
- 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 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 @@
@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 @@
}
}
- 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 @@
// 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 @@
} catch (RemoteException e) {
}
}
- }, 500);
+ }, mDialogPressDelay);
}
@Override
@@ -815,6 +848,7 @@
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 @@
}
}
+ @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 @@
} catch (RemoteException re) {
Log.e(TAG, "Couldn't logout user " + re);
}
- }, 500);
+ }, mDialogPressDelay);
}
}
@@ -1599,6 +1638,11 @@
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 9873d24..62efd8c 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.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 @@
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 @@
private int mForegroundColor;
private int mBackgroundColor;
protected ComponentName mRecvComponent;
+ private MediaDevice mDevice;
private boolean mIsRegistered = false;
private final int[] mActionIds;
@@ -121,19 +125,44 @@
}
};
+ 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 @@
// mStateListener to be unregistered in detach.
mMediaNotifView.addOnAttachStateChangeListener(mStateListener);
mMediaManager = manager;
+ mLocalMediaManager = routeManager;
mActionIds = actionIds;
mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
@@ -176,7 +206,7 @@
* @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 @@
// 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 @@
* 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 @@
Assert.isMainThread();
if (!mIsRegistered) {
mMediaManager.addCallback(mMediaListener);
+ if (mLocalMediaManager != null) {
+ mLocalMediaManager.registerCallback(mDeviceCallback);
+ mLocalMediaManager.startScan();
+ }
mIsRegistered = true;
}
}
@@ -463,6 +497,10 @@
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/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index cf8f268..dd83e42 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -34,8 +34,13 @@
/** ViewModel for seek bar in QS media player. */
class SeekBarViewModel(val bgExecutor: DelayableExecutor) {
+ private var _data = Progress(false, false, null, null, null)
+ set(value) {
+ field = value
+ _progress.postValue(value)
+ }
private val _progress = MutableLiveData<Progress>().apply {
- postValue(Progress(false, false, null, null, null))
+ postValue(_data)
}
val progress: LiveData<Progress>
get() = _progress
@@ -73,7 +78,7 @@
val position = playbackState?.position?.toInt()
val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt()
val enabled = if (duration != null && duration <= 0) false else true
- _progress.postValue(Progress(enabled, seekAvailable, position, duration, color))
+ _data = Progress(enabled, seekAvailable, position, duration, color)
if (shouldPollPlaybackPosition()) {
checkPlaybackPosition()
}
@@ -82,8 +87,8 @@
@AnyThread
private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({
val currentPosition = controller?.playbackState?.position?.toInt()
- if (currentPosition != null && _progress.value!!.elapsedTime != currentPosition) {
- _progress.postValue(_progress.value!!.copy(elapsedTime = currentPosition))
+ if (currentPosition != null && _data.elapsedTime != currentPosition) {
+ _data = _data.copy(elapsedTime = currentPosition)
}
if (shouldPollPlaybackPosition()) {
checkPlaybackPosition()
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index d219a9e..dba4343 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -319,6 +319,7 @@
getSurfaceTransactionHelper()
.crop(tx, leash, getDestinationBounds())
.round(tx, leash, shouldApplyCornerRadius());
+ tx.show(leash);
tx.apply();
}
};
@@ -359,6 +360,7 @@
getSurfaceTransactionHelper()
.alpha(tx, leash, 1f)
.round(tx, leash, shouldApplyCornerRadius());
+ tx.show(leash);
tx.apply();
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index d2994ae..a95d6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -317,17 +317,22 @@
/**
* TODO(b/152809058): consolidate the display info handling logic in SysUI
+ *
+ * @param destinationBoundsOut the current destination bounds will be populated to this param
*/
@SuppressWarnings("unchecked")
- public void onMovementBoundsChanged(boolean fromImeAdjustment, boolean fromShelfAdjustment) {
+ public void onMovementBoundsChanged(Rect destinationBoundsOut,
+ boolean fromImeAdjustment, boolean fromShelfAdjustment) {
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
+ destinationBoundsOut.set(mLastReportedBounds);
if (animator == null || !animator.isRunning()
|| animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
return;
}
final Rect currentDestinationBounds = animator.getDestinationBounds();
+ destinationBoundsOut.set(currentDestinationBounds);
if (!fromImeAdjustment && !fromShelfAdjustment
&& mPipBoundsHandler.getDisplayBounds().contains(currentDestinationBounds)) {
// no need to update the destination bounds, bail early
@@ -342,6 +347,7 @@
animator.updateEndValue(newDestinationBounds);
}
animator.setDestinationBounds(newDestinationBounds);
+ destinationBoundsOut.set(newDestinationBounds);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 918c45b..a2667d9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -21,6 +21,7 @@
import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IActivityManager;
@@ -160,9 +161,9 @@
}
@Override
- public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment) {
- mHandler.post(() -> updateMovementBounds(animatingBounds, fromImeAdjustment,
- false /* fromShelfAdjustment */));
+ public void onMovementBoundsChanged(boolean fromImeAdjustment) {
+ mHandler.post(() -> updateMovementBounds(null /* toBounds */,
+ fromImeAdjustment, false /* fromShelfAdjustment */));
}
@Override
@@ -352,17 +353,19 @@
mMenuController.onPinnedStackAnimationEnded();
}
- private void updateMovementBounds(Rect animatingBounds, boolean fromImeAdjustment,
- boolean fromShelfAdjustment) {
+ private void updateMovementBounds(@Nullable Rect toBounds,
+ boolean fromImeAdjustment, boolean fromShelfAdjustment) {
// Populate inset / normal bounds and DisplayInfo from mPipBoundsHandler before
- // passing to mTouchHandler, mTouchHandler would rely on the bounds calculated by
- // mPipBoundsHandler with up-to-dated information
+ // passing to mTouchHandler/mPipTaskOrganizer
+ final Rect outBounds = new Rect(toBounds);
mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
- animatingBounds, mTmpDisplayInfo);
+ outBounds, mTmpDisplayInfo);
+ // mTouchHandler would rely on the bounds populated from mPipTaskOrganizer
+ mPipTaskOrganizer.onMovementBoundsChanged(outBounds,
+ fromImeAdjustment, fromShelfAdjustment);
mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
- animatingBounds, fromImeAdjustment, fromShelfAdjustment,
+ outBounds, fromImeAdjustment, fromShelfAdjustment,
mTmpDisplayInfo.rotation);
- mPipTaskOrganizer.onMovementBoundsChanged(fromImeAdjustment, fromShelfAdjustment);
}
public void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 1e9daab..ddba9ea 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -380,11 +380,18 @@
}
// Re-calculate the expanded bounds
- mNormalBounds = normalBounds;
+ mNormalBounds.set(normalBounds);
Rect normalMovementBounds = new Rect();
mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalMovementBounds,
bottomOffset);
+ if (mMovementBounds.isEmpty()) {
+ // mMovementBounds is not initialized yet and a clean movement bounds without
+ // bottom offset shall be used later in this function.
+ mSnapAlgorithm.getMovementBounds(curBounds, insetBounds, mMovementBounds,
+ 0 /* bottomOffset */);
+ }
+
// Calculate the expanded size
float aspectRatio = (float) normalBounds.width() / normalBounds.height();
Point displaySize = new Point();
@@ -430,8 +437,8 @@
// Update the movement bounds after doing the calculations based on the old movement bounds
// above
- mNormalMovementBounds = normalMovementBounds;
- mExpandedMovementBounds = expandedMovementBounds;
+ mNormalMovementBounds.set(normalMovementBounds);
+ mExpandedMovementBounds.set(expandedMovementBounds);
mDisplayRotation = displayRotation;
mInsetBounds.set(insetBounds);
updateMovementBounds();
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 99a01d3..bab1f39 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -208,12 +208,13 @@
}
@Override
- public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment) {
+ public void onMovementBoundsChanged(boolean fromImeAdjustment) {
mHandler.post(() -> {
// Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+ final Rect destinationBounds = new Rect();
mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
- animatingBounds, mTmpDisplayInfo);
- mDefaultPipBounds.set(animatingBounds);
+ destinationBounds, mTmpDisplayInfo);
+ mDefaultPipBounds.set(destinationBounds);
});
}
@@ -239,6 +240,12 @@
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 339a408..e636707 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.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 @@
* @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 @@
* @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 c8412ff..0566b2e 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0c50194..0ba4cb1 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 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 @@
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 366ef93..2df4506 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 @@
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 3b8addb..92f6b4a 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 @@
}
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 c8b34f1..4e6df0a 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 @@
// 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/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index fb88ea5..8c9bb6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -34,6 +34,7 @@
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.KeyguardViewController;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
@@ -145,7 +146,7 @@
private final Context mContext;
private final int mWakeUpDelay;
private int mMode;
- private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private KeyguardViewController mKeyguardViewController;
private DozeScrimController mDozeScrimController;
private KeyguardViewMediator mKeyguardViewMediator;
private ScrimController mScrimController;
@@ -204,9 +205,8 @@
dumpManager.registerDumpable(getClass().getName(), this);
}
- public void setStatusBarKeyguardViewManager(
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ public void setKeyguardViewController(KeyguardViewController keyguardViewController) {
+ mKeyguardViewController = keyguardViewController;
}
private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@@ -328,7 +328,7 @@
case MODE_DISMISS_BOUNCER:
case MODE_UNLOCK_FADING:
Trace.beginSection("MODE_DISMISS_BOUNCER or MODE_UNLOCK_FADING");
- mStatusBarKeyguardViewManager.notifyKeyguardAuthenticated(
+ mKeyguardViewController.notifyKeyguardAuthenticated(
false /* strongAuth */);
Trace.endSection();
break;
@@ -376,7 +376,7 @@
private void showBouncer() {
if (mMode == MODE_SHOW_BOUNCER) {
- mStatusBarKeyguardViewManager.showBouncer(false);
+ mKeyguardViewController.showBouncer(false);
}
mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
false /* delayed */, BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
@@ -431,7 +431,7 @@
boolean deviceDreaming = mUpdateMonitor.isDreaming();
if (!mUpdateMonitor.isDeviceInteractive()) {
- if (!mStatusBarKeyguardViewManager.isShowing()) {
+ if (!mKeyguardViewController.isShowing()) {
return MODE_ONLY_WAKE;
} else if (mDozeScrimController.isPulsing() && unlockingAllowed) {
return MODE_WAKE_AND_UNLOCK_PULSING;
@@ -444,12 +444,12 @@
if (unlockingAllowed && deviceDreaming) {
return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
}
- if (mStatusBarKeyguardViewManager.isShowing()) {
- if (mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing() && unlockingAllowed) {
+ if (mKeyguardViewController.isShowing()) {
+ if (mKeyguardViewController.bouncerIsOrWillBeShowing() && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
} else if (unlockingAllowed) {
return MODE_UNLOCK_COLLAPSING;
- } else if (!mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ } else if (!mKeyguardViewController.isBouncerShowing()) {
return MODE_SHOW_BOUNCER;
}
}
@@ -463,7 +463,7 @@
boolean bypass = mKeyguardBypassController.getBypassEnabled();
if (!mUpdateMonitor.isDeviceInteractive()) {
- if (!mStatusBarKeyguardViewManager.isShowing()) {
+ if (!mKeyguardViewController.isShowing()) {
return bypass ? MODE_WAKE_AND_UNLOCK : MODE_ONLY_WAKE;
} else if (!unlockingAllowed) {
return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
@@ -484,8 +484,8 @@
if (unlockingAllowed && deviceDreaming) {
return bypass ? MODE_WAKE_AND_UNLOCK_FROM_DREAM : MODE_ONLY_WAKE;
}
- if (mStatusBarKeyguardViewManager.isShowing()) {
- if (mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing() && unlockingAllowed) {
+ if (mKeyguardViewController.isShowing()) {
+ if (mKeyguardViewController.bouncerIsOrWillBeShowing() && unlockingAllowed) {
if (bypass && mKeyguardBypassController.canPlaySubtleWindowAnimations()) {
return MODE_UNLOCK_FADING;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index fa55b74..6e147f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1399,7 +1399,7 @@
mStackScroller, mKeyguardBypassController, mFalsingManager);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
- mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+ mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mRemoteInputManager.getController().addCallback(mStatusBarKeyguardViewManager);
mDynamicPrivacyController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 45719c7..03021c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -744,14 +744,12 @@
return false;
}
+ @Override
public boolean isBouncerShowing() {
return mBouncer.isShowing();
}
- /**
- * When bouncer is fully visible or {@link KeyguardBouncer#show(boolean)} was called but
- * animation didn't finish yet.
- */
+ @Override
public boolean bouncerIsOrWillBeShowing() {
return mBouncer.isShowing() || mBouncer.inTransit();
}
diff --git a/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/wifi/WifiDebuggingActivity.java
index a94af24..1c7a9b5 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 @@
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 0266a84..7a31fa5 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 @@
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 4652abf..cfec1c0 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_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 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 @@
}
/**
+ * 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 @@
}
/** 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 8630570..f6ee46b 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 @@
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 @@
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 5e0d28f..236384b 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.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 @@
@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 @@
it in favoritesIndices
)
}
- model = AllModel(controls, favoritesList, EMPTY_STRING)
+ model = AllModel(controls, favoritesList, EMPTY_STRING, controlsModelCallback)
}
@Test
@@ -93,28 +97,28 @@
// 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 @@
@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 @@
model.favorites.zip(expectedFavorites).forEach {
assertTrue(sameControl(it.first, it.second))
}
+
+ verify(controlsModelCallback).onFirstChange()
}
@Test
@@ -163,10 +170,12 @@
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 @@
model.favorites.zip(expectedFavorites).forEach {
assertTrue(sameControl(it.first, it.second))
}
+
+ verify(controlsModelCallback, never()).onFirstChange()
}
@Test
@@ -194,6 +205,8 @@
model.favorites.zip(expectedFavorites).forEach {
assertTrue(sameControl(it.first, it.second))
}
+
+ verify(controlsModelCallback).onFirstChange()
}
@Test
@@ -203,10 +216,12 @@
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 @@
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 c330b38..0000000
--- 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 0000000..ce33a8d
--- /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 300cb21..3af164d 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.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 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper()
public class GlobalActionsDialogTest extends SysuiTestCase {
private GlobalActionsDialog mGlobalActionsDialog;
@@ -143,11 +144,60 @@
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/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index 0d66340..56a7484 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -51,7 +51,6 @@
private PipAnimationController mPipAnimationController;
- @Mock
private SurfaceControl mLeash;
@Mock
@@ -61,6 +60,10 @@
public void setUp() throws Exception {
mPipAnimationController = new PipAnimationController(
mContext, new PipSurfaceTransactionHelper(mContext));
+ mLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("FakeLeash")
+ .build();
MockitoAnnotations.initMocks(this);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
index 4d7e6ae..7211254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
@@ -29,7 +29,6 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Size;
-import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
@@ -59,12 +58,8 @@
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class PipTouchHandlerTest extends SysuiTestCase {
- private static final int ROUNDING_ERROR_MARGIN = 10;
- private static final float DEFAULT_ASPECT_RATIO = 1f;
- private static final Rect EMPTY_CURRENT_BOUNDS = null;
private PipTouchHandler mPipTouchHandler;
- private DisplayInfo mDefaultDisplayInfo;
@Mock
private IActivityManager mActivityManager;
@@ -90,18 +85,17 @@
@Mock
private DeviceConfigProxy mDeviceConfigProxy;
-
private PipSnapAlgorithm mPipSnapAlgorithm;
private PipMotionHelper mMotionHelper;
private PipResizeGestureHandler mPipResizeGestureHandler;
- Rect mInsetBounds;
- Rect mMinBounds;
- Rect mCurBounds;
- boolean mFromImeAdjustment;
- boolean mFromShelfAdjustment;
- int mDisplayRotation;
-
+ private Rect mInsetBounds;
+ private Rect mMinBounds;
+ private Rect mCurBounds;
+ private boolean mFromImeAdjustment;
+ private boolean mFromShelfAdjustment;
+ private int mDisplayRotation;
+ private int mImeHeight;
@Before
public void setUp() throws Exception {
@@ -121,10 +115,11 @@
mInsetBounds = new Rect(10, 10, 990, 990);
// minBounds of 100x100 bottom right corner
mMinBounds = new Rect(890, 890, 990, 990);
- mCurBounds = new Rect();
+ mCurBounds = new Rect(mMinBounds);
mFromImeAdjustment = false;
mFromShelfAdjustment = false;
mDisplayRotation = 0;
+ mImeHeight = 100;
}
@Test
@@ -162,6 +157,8 @@
@Test
public void updateMovementBounds_withImeAdjustment_movesPip() {
mFromImeAdjustment = true;
+ mPipTouchHandler.onImeVisibilityChanged(true /* imeVisible */, mImeHeight);
+
mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mMinBounds, mCurBounds,
mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index bfcf41d..a927c80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -109,7 +109,7 @@
mNotificationShadeWindowController, mKeyguardStateController, mHandler,
mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
mMetricsLogger, mDumpManager);
- mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+ mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
}
@Test
@@ -202,7 +202,7 @@
@Test
public void onBiometricAuthenticated_whenFace_andBypass_dismissKeyguard() {
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
- mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+ mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
// the value of isStrongBiometric doesn't matter here since we only care about the returned
@@ -221,7 +221,7 @@
public void onBiometricAuthenticated_whenFace_andBypass_encrypted_showBouncer() {
reset(mUpdateMonitor);
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
- mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+ mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
// the value of isStrongBiometric doesn't matter here since we only care about the returned
@@ -241,7 +241,7 @@
@Test
public void onBiometricAuthenticated_whenFace_noBypass_encrypted_doNothing() {
reset(mUpdateMonitor);
- mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+ mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
// the value of isStrongBiometric doesn't matter here since we only care about the returned
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 190b443..5b052df 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -25,7 +25,7 @@
],
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 3509801..cc095a0 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 @@
/**
* 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 @@
@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 @@
}
/**
+ * 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 d6bc063..82a26be 100644
--- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -18,10 +18,12 @@
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 @@
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 5b6fe91..1dac5b7 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -18,6 +18,7 @@
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 @@
}
}
- 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 @@
}
}
- 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 @@
// 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 @@
mLinkProperties.removeRoute(route);
}
- return configureDhcp(enabled, srvAddr, prefixLen);
+ return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
}
private String getRandomWifiIPv4Address() {
@@ -962,7 +971,14 @@
}
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 e8add98..f8eb147 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 @@
@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 @@
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 3a580dd..2955903c 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_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 @@
}
@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 @@
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 0000000..c2b3b18
--- /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/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 8c1360c..ca1b27b 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -3669,6 +3669,9 @@
// Default launcher from package manager.
final ComponentName defaultLauncher = mPackageManagerInternal
.getDefaultHomeActivity(UserHandle.getUserId(callingUid));
+ if (defaultLauncher == null) {
+ return;
+ }
int defaultLauncherUid = 0;
try {
defaultLauncherUid = mPackageManager.getApplicationInfo(
diff --git a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
index 7ad5632..347174c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
@@ -27,7 +27,9 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteCallback;
import android.service.autofill.IInlineSuggestionRenderService;
import android.service.autofill.IInlineSuggestionUiCallback;
import android.service.autofill.InlinePresentation;
@@ -91,6 +93,15 @@
hostInputToken, displayId));
}
+ /**
+ * Gets the inline suggestions renderer info as a {@link Bundle}.
+ */
+ public void getInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) {
+ scheduleAsyncRequest((s) -> s.getInlineSuggestionsRendererInfo(new RemoteCallback(
+ (bundle) -> callback.sendResult(bundle)
+ )));
+ }
+
@Nullable
private static ServiceInfo getServiceInfo(Context context, int userId) {
final String packageName =
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 78b943c..3d68618 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -652,10 +652,6 @@
return mService.isInlineSuggestionsEnabled();
}
- private boolean isInlineSuggestionRenderServiceAvailable() {
- return mService.getRemoteInlineSuggestionRenderServiceLocked() != null;
- }
-
/**
* Clears the existing response for the partition, reads a new structure, and then requests a
* new fill response from the fill service.
@@ -715,14 +711,18 @@
// Only ask IME to create inline suggestions request if Autofill provider supports it and
// the render service is available.
- if (isInlineSuggestionsEnabledByAutofillProviderLocked()
- && isInlineSuggestionRenderServiceAvailable()) {
+ final RemoteInlineSuggestionRenderService remoteRenderService =
+ mService.getRemoteInlineSuggestionRenderServiceLocked();
+ if (isInlineSuggestionsEnabledByAutofillProviderLocked() && remoteRenderService != null) {
Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
if (inlineSuggestionsRequestConsumer != null) {
- // TODO(b/146454892): pipe the uiExtras from the ExtServices.
- mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId,
- inlineSuggestionsRequestConsumer, Bundle.EMPTY);
+ remoteRenderService.getInlineSuggestionsRendererInfo(
+ new RemoteCallback((extras) -> {
+ mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+ mCurrentViewId, inlineSuggestionsRequestConsumer, extras);
+ }
+ ));
}
} else {
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
@@ -3130,13 +3130,18 @@
// 1. the field is augmented autofill only (when standard autofill provider is None or
// when it returns null response)
// 2. standard autofill provider doesn't support inline suggestion
- if (isInlineSuggestionRenderServiceAvailable()
+ final RemoteInlineSuggestionRenderService remoteRenderService =
+ mService.getRemoteInlineSuggestionRenderServiceLocked();
+ if (remoteRenderService != null
&& (mForAugmentedAutofillOnly
|| !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
- // TODO(b/146454892): pipe the uiExtras from the ExtServices.
- mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId,
- /*requestConsumer=*/ requestAugmentedAutofill, Bundle.EMPTY);
+ remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
+ (extras) -> {
+ mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+ mCurrentViewId, /*requestConsumer=*/ requestAugmentedAutofill,
+ extras);
+ }, mHandler));
} else {
requestAugmentedAutofill.accept(
mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null));
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a8bc2b4..77773ed 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -127,7 +127,7 @@
"android.hardware.soundtrigger-V2.3-java",
"android.hidl.manager-V1.2-java",
"capture_state_listener-aidl-java",
- "dnsresolver_aidl_interface-V2-java",
+ "dnsresolver_aidl_interface-V4-java",
"netd_event_listener_interface-java",
"overlayable_policy_aidl-java",
],
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 8a1de1f..51427c1 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3339,6 +3339,8 @@
getNetworkPermission(networkAgent.networkCapabilities));
}
mDnsResolver.createNetworkCache(networkAgent.network.netId);
+ mDnsManager.updateTransportsForNetwork(networkAgent.network.netId,
+ networkAgent.networkCapabilities.getTransportTypes());
return true;
} catch (RemoteException | ServiceSpecificException e) {
loge("Error creating network " + networkAgent.network.netId + ": "
@@ -6088,7 +6090,13 @@
log("Setting DNS servers for network " + netId + " to " + dnses);
}
try {
- mDnsManager.setDnsConfigurationForNetwork(netId, newLp, isDefaultNetwork);
+ mDnsManager.noteDnsServersForNetwork(netId, newLp);
+ // TODO: netd should listen on [::1]:53 and proxy queries to the current
+ // default network, and we should just set net.dns1 to ::1, not least
+ // because applications attempting to use net.dns resolvers will bypass
+ // the privacy protections of things like DNS-over-TLS.
+ if (isDefaultNetwork) mDnsManager.setDefaultDnsSystemProperties(newLp.getDnsServers());
+ mDnsManager.flushVmDnsCache();
} catch (Exception e) {
loge("Exception in setDnsConfigurationForNetwork: " + e);
}
@@ -6286,6 +6294,10 @@
// bubble those changes through.
updateAllVpnsCapabilities();
}
+
+ if (!newNc.equalsTransportTypes(prevNc)) {
+ mDnsManager.updateTransportsForNetwork(nai.network.netId, newNc.getTransportTypes());
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/BugReportHandlerUtil.java b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
index ba89fce..0a0d8d8 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 @@
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 @@
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 @@
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 @@
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 @@
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/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
index ea607cb..0e34801 100644
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
@@ -36,7 +36,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowInsets;
-import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
@@ -59,12 +58,6 @@
String switchingToSystemUserMessage) {
super(service, context, oldUser, newUser, aboveSystem, switchingFromSystemUserMessage,
switchingToSystemUserMessage);
-
- // {@link UserSwitchingDialog} uses {@link WindowManager.LayoutParams.TYPE_SYSTEM_ERROR}
- // when trying to show dialog above system. That window type has been deprecated and since
- // this is a system dialog, hence, it makes sense to put this in System Dialog Window.
- // This window also automatically shows status bar.
- getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
}
@Override
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 30e765f..02d8571 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3622,7 +3622,15 @@
hdlr = h;
// Remove from client list so that it is re-inserted at top of list
iter.remove();
- hdlr.getBinder().unlinkToDeath(hdlr, 0);
+ try {
+ hdlr.getBinder().unlinkToDeath(hdlr, 0);
+ if (cb != hdlr.getBinder()) {
+ hdlr = null;
+ }
+ } catch (NoSuchElementException e) {
+ hdlr = null;
+ Log.w(TAG, "link does not exist ...");
+ }
break;
}
}
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index 5250a77..506c8e3 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -27,6 +27,7 @@
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -34,6 +35,7 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkUtils;
+import android.net.ResolverOptionsParcel;
import android.net.ResolverParamsParcel;
import android.net.Uri;
import android.net.shared.PrivateDnsConfig;
@@ -237,6 +239,8 @@
// TODO: Replace these Maps with SparseArrays.
private final Map<Integer, PrivateDnsConfig> mPrivateDnsMap;
private final Map<Integer, PrivateDnsValidationStatuses> mPrivateDnsValidationMap;
+ private final Map<Integer, LinkProperties> mLinkPropertiesMap;
+ private final Map<Integer, int[]> mTransportsMap;
private int mNumDnsEntries;
private int mSampleValidity;
@@ -253,6 +257,8 @@
mSystemProperties = sp;
mPrivateDnsMap = new HashMap<>();
mPrivateDnsValidationMap = new HashMap<>();
+ mLinkPropertiesMap = new HashMap<>();
+ mTransportsMap = new HashMap<>();
// TODO: Create and register ContentObservers to track every setting
// used herein, posting messages to respond to changes.
@@ -265,6 +271,8 @@
public void removeNetwork(Network network) {
mPrivateDnsMap.remove(network.netId);
mPrivateDnsValidationMap.remove(network.netId);
+ mTransportsMap.remove(network.netId);
+ mLinkPropertiesMap.remove(network.netId);
}
public PrivateDnsConfig updatePrivateDns(Network network, PrivateDnsConfig cfg) {
@@ -304,9 +312,35 @@
statuses.updateStatus(update);
}
- public void setDnsConfigurationForNetwork(
- int netId, LinkProperties lp, boolean isDefaultNetwork) {
+ /**
+ * When creating a new network or transport types are changed in a specific network,
+ * transport types are always saved to a hashMap before update dns config.
+ * When destroying network, the specific network will be removed from the hashMap.
+ * The hashMap is always accessed on the same thread.
+ */
+ public void updateTransportsForNetwork(int netId, @NonNull int[] transportTypes) {
+ mTransportsMap.put(netId, transportTypes);
+ sendDnsConfigurationForNetwork(netId);
+ }
+ /**
+ * When {@link LinkProperties} are changed in a specific network, they are
+ * always saved to a hashMap before update dns config.
+ * When destroying network, the specific network will be removed from the hashMap.
+ * The hashMap is always accessed on the same thread.
+ */
+ public void noteDnsServersForNetwork(int netId, @NonNull LinkProperties lp) {
+ mLinkPropertiesMap.put(netId, lp);
+ sendDnsConfigurationForNetwork(netId);
+ }
+
+ /**
+ * Send dns configuration parameters to resolver for a given network.
+ */
+ public void sendDnsConfigurationForNetwork(int netId) {
+ final LinkProperties lp = mLinkPropertiesMap.get(netId);
+ final int[] transportTypes = mTransportsMap.get(netId);
+ if (lp == null || transportTypes == null) return;
updateParametersSettings();
final ResolverParamsParcel paramsParcel = new ResolverParamsParcel();
@@ -319,15 +353,16 @@
// networks like IMS.
final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
PRIVATE_DNS_OFF);
-
final boolean useTls = privateDnsCfg.useTls;
final boolean strictMode = privateDnsCfg.inStrictMode();
+
paramsParcel.netId = netId;
paramsParcel.sampleValiditySeconds = mSampleValidity;
paramsParcel.successThreshold = mSuccessThreshold;
paramsParcel.minSamples = mMinSamples;
paramsParcel.maxSamples = mMaxSamples;
- paramsParcel.servers = NetworkUtils.makeStrings(lp.getDnsServers());
+ paramsParcel.servers =
+ NetworkUtils.makeStrings(lp.getDnsServers());
paramsParcel.domains = getDomainStrings(lp.getDomains());
paramsParcel.tlsName = strictMode ? privateDnsCfg.hostname : "";
paramsParcel.tlsServers =
@@ -337,6 +372,8 @@
.collect(Collectors.toList()))
: useTls ? paramsParcel.servers // Opportunistic
: new String[0]; // Off
+ paramsParcel.resolverOptions = new ResolverOptionsParcel();
+ paramsParcel.transportTypes = transportTypes;
// Prepare to track the validation status of the DNS servers in the
// resolver config when private DNS is in opportunistic or strict mode.
if (useTls) {
@@ -349,7 +386,7 @@
mPrivateDnsValidationMap.remove(netId);
}
- Slog.d(TAG, String.format("setDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
+ Slog.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
+ "%d, %d, %s, %s)", paramsParcel.netId, Arrays.toString(paramsParcel.servers),
Arrays.toString(paramsParcel.domains), paramsParcel.sampleValiditySeconds,
paramsParcel.successThreshold, paramsParcel.minSamples,
@@ -363,13 +400,6 @@
Slog.e(TAG, "Error setting DNS configuration: " + e);
return;
}
-
- // TODO: netd should listen on [::1]:53 and proxy queries to the current
- // default network, and we should just set net.dns1 to ::1, not least
- // because applications attempting to use net.dns resolvers will bypass
- // the privacy protections of things like DNS-over-TLS.
- if (isDefaultNetwork) setDefaultDnsSystemProperties(lp.getDnsServers());
- flushVmDnsCache();
}
public void setDefaultDnsSystemProperties(Collection<InetAddress> dnses) {
@@ -384,7 +414,10 @@
mNumDnsEntries = last;
}
- private void flushVmDnsCache() {
+ /**
+ * Flush DNS caches and events work before boot has completed.
+ */
+ public void flushVmDnsCache() {
/*
* Tell the VMs to toss their DNS caches
*/
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 2aa53cc..a5de90c 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 @@
}
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 fe118e5..b688e09 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 @@
// Scan packages.
// Also has the side-effect of restarting providers if needed.
- mHandler.post(mScanPackagesRunnable);
+ postScanPackagesIfNeeded();
}
}
@@ -92,7 +92,7 @@
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 @@
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 43fc7ed..90c85ad 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 @@
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 d108e76..05a4a38 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 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 75229a1..6edd76f 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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9c41e6d..59ac603 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -263,6 +263,7 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.os.incremental.IncrementalManager;
+import android.os.incremental.IncrementalStorage;
import android.os.storage.DiskInfo;
import android.os.storage.IStorageManager;
import android.os.storage.StorageEventListener;
@@ -16596,6 +16597,7 @@
* locks on {@link #mLock}.
*/
private void executePostCommitSteps(CommitRequest commitRequest) {
+ final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {
final boolean instantApp = ((reconciledPkg.scanResult.request.scanFlags
& PackageManagerService.SCAN_AS_INSTANT_APP) != 0);
@@ -16603,6 +16605,14 @@
final String packageName = pkg.getPackageName();
final boolean onIncremental = mIncrementalManager != null
&& isIncrementalPath(pkg.getCodePath());
+ if (onIncremental) {
+ IncrementalStorage storage = mIncrementalManager.openStorage(pkg.getCodePath());
+ if (storage == null) {
+ throw new IllegalArgumentException(
+ "Install: null storage for incremental package " + packageName);
+ }
+ incrementalStorages.add(storage);
+ }
prepareAppDataAfterInstallLIF(pkg);
if (reconciledPkg.prepareResult.clearCodeCache) {
clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE
@@ -16700,6 +16710,7 @@
notifyPackageChangeObserversOnUpdate(reconciledPkg);
}
+ NativeLibraryHelper.waitForNativeBinariesExtraction(incrementalStorages);
}
private void notifyPackageChangeObserversOnUpdate(ReconciledPackage reconciledPkg) {
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 eb7057d..6dcf71e 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 @@
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 0000000..7cdb84b
--- /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 e9da2c4..288c22a 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 @@
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 @@
registerFullBatteryCapacity();
registerBatteryVoltage();
registerBatteryCycleCount();
+ registerSettingsStats();
}
/**
@@ -3244,6 +3247,43 @@
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 c4d0342..ed9b019 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5212,6 +5212,12 @@
updateReportedVisibilityLocked();
}
+ void onStartingWindowDrawn() {
+ if (task != null) {
+ task.setHasBeenVisible(true);
+ }
+ }
+
/** Called when the windows associated app window container are drawn. */
void onWindowsDrawn(boolean drawn, long timestampNs) {
mDrawn = drawn;
@@ -7362,7 +7368,8 @@
}
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 384fd99..bcad758 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 @@
// warning toast about it.
mAtmService.getTaskChangeNotificationController()
.notifyActivityDismissingDockedStack();
- taskDisplayArea.onSplitScreenModeDismissed();
+ taskDisplayArea.onSplitScreenModeDismissed(this);
}
}
@@ -3407,18 +3407,6 @@
outBounds.set(mBoundsAnimationSourceHintBounds);
}
- /**
- * @return the final animation bounds if the task stack is currently being animated, or the
- * current stack bounds otherwise.
- */
- void getAnimationOrCurrentBounds(Rect outBounds) {
- if ((mBoundsAnimatingRequested || mBoundsAnimating) && !mBoundsAnimationTarget.isEmpty()) {
- getFinalAnimationBounds(outBounds);
- return;
- }
- getBounds(outBounds);
- }
-
/** Bounds of the stack with other system factors taken into consideration. */
void getDimBounds(Rect out) {
getBounds(out);
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 2645969..f924bd4 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2208,7 +2208,7 @@
// 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 78ee1de7..6f1ddcd 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 @@
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 40243e8..80a1a45 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.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 @@
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/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 02a2741..56312aa 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -23,7 +23,6 @@
import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
-import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -285,13 +284,7 @@
return;
}
try {
- final Rect animatingBounds = new Rect();
- final ActivityStack pinnedStack = mDisplayContent.getDefaultTaskDisplayArea()
- .getRootPinnedTask();
- if (pinnedStack != null) {
- pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
- }
- mPinnedStackListener.onMovementBoundsChanged(animatingBounds, fromImeAdjustment);
+ mPinnedStackListener.onMovementBoundsChanged(fromImeAdjustment);
} catch (RemoteException e) {
Slog.e(TAG_WM, "Error delivering actions changed event.", e);
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index a031fe8..0a9878d 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 @@
// 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 6fda117..54210ae 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.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 @@
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 @@
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 @@
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 @@
@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 @@
}
}
+ 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 0c8e2ff..1a70de7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -37,6 +37,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -65,6 +66,7 @@
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;
@@ -663,7 +665,7 @@
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 */);
@@ -1537,6 +1539,7 @@
// 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.
@@ -2157,16 +2160,19 @@
try {
final Task task = r.getTask();
-
final ActivityStack pinnedStack = taskDisplayArea.getRootPinnedTask();
+
// This will change the pinned stack's windowing mode to its original mode, ensuring
// we only have one stack that is in pinned mode.
if (pinnedStack != null) {
pinnedStack.dismissPip();
}
- final boolean singleActivity = task.getChildCount() == 1;
+ // Set a transition to ensure that we don't immediately try and update the visibility
+ // of the activity entering PIP
+ r.getDisplayContent().prepareAppTransition(TRANSIT_NONE, false);
+ final boolean singleActivity = task.getChildCount() == 1;
final ActivityStack stack;
if (singleActivity) {
stack = r.getRootTask();
@@ -2189,11 +2195,6 @@
mService.continueWindowLayout();
}
- // TODO: revisit the following statement after the animation is moved from WM to SysUI.
- // Update the visibility of all activities after the they have been reparented to the new
- // stack. This MUST run after the animation above is scheduled to ensure that the windows
- // drawn signal is scheduled after the bounds animation start call on the bounds animator
- // thread.
ensureActivitiesVisible(null, 0, false /* preserveWindows */);
resumeFocusedStacksTopActivities();
@@ -2254,7 +2255,7 @@
// It is possible that request to finish activity might also remove its task and
// stack, so we need to be careful with indexes in the loop and check child count
// every time.
- for (int stackNdx = 0; stackNdx < display.getStackCount(); ++stackNdx) {
+ for (int stackNdx = 0; stackNdx < taskDisplayArea.getStackCount(); ++stackNdx) {
final ActivityStack stack = taskDisplayArea.getStackAt(stackNdx);
final Task t = stack.finishTopCrashedActivityLocked(app, reason);
if (stack == focusedStack || finishedTask == null) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7d8a56e..56147f2 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 @@
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 @@
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 3a68add..373abfe 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -85,6 +85,8 @@
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;
@@ -134,6 +136,7 @@
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;
@@ -3401,6 +3404,24 @@
}
@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 + " ";
@@ -4097,8 +4118,18 @@
}
void setHasBeenVisible(boolean hasBeenVisible) {
+ final boolean prevHasBeenVisible = mHasBeenVisible;
mHasBeenVisible = hasBeenVisible;
if (hasBeenVisible) {
+ // If the task is not yet visible when it is added to the task organizer, then we should
+ // hide it to allow the task organizer to show it when it is properly reparented. We
+ // skip this for tasks created by the organizer because they can synchronously update
+ // the leash before new children are added to the task.
+ if (!mCreatedByOrganizer && mTaskOrganizer != null && !prevHasBeenVisible) {
+ getPendingTransaction().hide(getSurfaceControl());
+ commitPendingTransaction();
+ }
+
sendTaskAppeared();
if (!isRootTask()) {
getRootTask().setHasBeenVisible(true);
@@ -4142,6 +4173,8 @@
// Let the old organizer know it has lost control.
sendTaskVanished();
mTaskOrganizer = organizer;
+
+
sendTaskAppeared();
onTaskOrganizerChanged();
return true;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index b91f709..ea9a362 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1167,17 +1167,23 @@
}
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/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 22702dd..9873031 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -25,6 +25,7 @@
import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.content.Intent;
@@ -38,6 +39,7 @@
import android.window.ITaskOrganizerController;
import android.window.WindowContainerToken;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
@@ -46,6 +48,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.WeakHashMap;
+import java.util.function.Consumer;
/**
* Stores the TaskOrganizers associated with a given windowing mode and
@@ -81,17 +84,105 @@
}
}
}
- };
+ }
+
+ /**
+ * A wrapper class around ITaskOrganizer to ensure that the calls are made in the right
+ * lifecycle order since we may be updating the visibility of task surface controls in a pending
+ * transaction before they are presented to the task org.
+ */
+ private class TaskOrganizerCallbacks {
+ final WindowManagerService mService;
+ final ITaskOrganizer mTaskOrganizer;
+ final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
+
+ TaskOrganizerCallbacks(WindowManagerService wm, ITaskOrganizer taskOrg,
+ Consumer<Runnable> deferTaskOrgCallbacksConsumer) {
+ mService = wm;
+ mDeferTaskOrgCallbacksConsumer = deferTaskOrgCallbacksConsumer;
+ mTaskOrganizer = taskOrg;
+ }
+
+ IBinder getBinder() {
+ return mTaskOrganizer.asBinder();
+ }
+
+ void onTaskAppeared(Task task) {
+ final RunningTaskInfo taskInfo = task.getTaskInfo();
+ mDeferTaskOrgCallbacksConsumer.accept(() -> {
+ try {
+ mTaskOrganizer.onTaskAppeared(taskInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onTaskAppeared callback", e);
+ }
+ });
+ }
+
+
+ void onTaskVanished(Task task) {
+ final RunningTaskInfo taskInfo = task.getTaskInfo();
+ mDeferTaskOrgCallbacksConsumer.accept(() -> {
+ try {
+ mTaskOrganizer.onTaskVanished(taskInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onTaskVanished callback", e);
+ }
+ });
+ }
+
+ void onTaskInfoChanged(Task task, ActivityManager.RunningTaskInfo taskInfo) {
+ if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) {
+ // Skip if the task has not yet received taskAppeared(), except for tasks created
+ // by the organizer that don't receive that signal
+ return;
+ }
+ mDeferTaskOrgCallbacksConsumer.accept(() -> {
+ if (!task.isOrganized()) {
+ // This is safe to ignore if the task is no longer organized
+ return;
+ }
+ try {
+ mTaskOrganizer.onTaskInfoChanged(taskInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onTaskInfoChanged callback", e);
+ }
+ });
+ }
+
+ void onBackPressedOnTaskRoot(Task task) {
+ if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) {
+ // Skip if the task has not yet received taskAppeared(), except for tasks created
+ // by the organizer that don't receive that signal
+ return;
+ }
+ mDeferTaskOrgCallbacksConsumer.accept(() -> {
+ if (!task.isOrganized()) {
+ // This is safe to ignore if the task is no longer organized
+ return;
+ }
+ try {
+ mTaskOrganizer.onBackPressedOnTaskRoot(task.getTaskInfo());
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception sending onBackPressedOnTaskRoot callback", e);
+ }
+ });
+ }
+ }
private class TaskOrganizerState {
- private final ITaskOrganizer mOrganizer;
+ private final TaskOrganizerCallbacks mOrganizer;
private final DeathRecipient mDeathRecipient;
private final ArrayList<Task> mOrganizedTasks = new ArrayList<>();
private final int mUid;
private boolean mInterceptBackPressedOnTaskRoot;
TaskOrganizerState(ITaskOrganizer organizer, int uid) {
- mOrganizer = organizer;
+ final Consumer<Runnable> deferTaskOrgCallbacksConsumer =
+ mDeferTaskOrgCallbacksConsumer != null
+ ? mDeferTaskOrgCallbacksConsumer
+ : mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
+ mOrganizer = new TaskOrganizerCallbacks(mService.mWindowManager, organizer,
+ deferTaskOrgCallbacksConsumer);
mDeathRecipient = new DeathRecipient(organizer);
try {
organizer.asBinder().linkToDeath(mDeathRecipient, 0);
@@ -112,23 +203,15 @@
mOrganizedTasks.add(t);
}
if (t.taskAppearedReady()) {
- try {
- t.mTaskAppearedSent = true;
- mOrganizer.onTaskAppeared(t.getTaskInfo());
- } catch (Exception e) {
- Slog.e(TAG, "Exception sending taskAppeared callback" + e);
- }
+ t.mTaskAppearedSent = true;
+ mOrganizer.onTaskAppeared(t);
}
}
void removeTask(Task t) {
if (t.mTaskAppearedSent) {
- try {
- t.mTaskAppearedSent = false;
- mOrganizer.onTaskVanished(t.getTaskInfo());
- } catch (Exception e) {
- Slog.e(TAG, "Exception sending taskVanished callback" + e);
- }
+ t.mTaskAppearedSent = false;
+ mOrganizer.onTaskVanished(t);
}
mOrganizedTasks.remove(t);
}
@@ -136,7 +219,7 @@
void dispose() {
releaseTasks();
for (int i = mTaskOrganizersForWindowingMode.size() - 1; i >= 0; --i) {
- mTaskOrganizersForWindowingMode.valueAt(i).remove(mOrganizer.asBinder());
+ mTaskOrganizersForWindowingMode.valueAt(i).remove(mOrganizer.getBinder());
}
}
@@ -149,7 +232,7 @@
}
void unlinkDeath() {
- mOrganizer.asBinder().unlinkToDeath(mDeathRecipient, 0);
+ mOrganizer.getBinder().unlinkToDeath(mDeathRecipient, 0);
}
}
@@ -159,9 +242,10 @@
private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>();
- final ActivityTaskManagerService mService;
+ private final ActivityTaskManagerService mService;
- RunningTaskInfo mTmpTaskInfo;
+ private RunningTaskInfo mTmpTaskInfo;
+ private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
TaskOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
@@ -173,6 +257,15 @@
}
/**
+ * Specifies the consumer to run to defer the task org callbacks. Can be overridden while
+ * testing to allow the callbacks to be sent synchronously.
+ */
+ @VisibleForTesting
+ public void setDeferTaskOrgCallbacksConsumer(Consumer<Runnable> consumer) {
+ mDeferTaskOrgCallbacksConsumer = consumer;
+ }
+
+ /**
* Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
* If there was already a TaskOrganizer for this windowing mode it will be evicted
* but will continue to organize it's existing tasks.
@@ -263,7 +356,7 @@
if (state == null) {
return null;
}
- return state.mOrganizer;
+ return state.mOrganizer.mTaskOrganizer;
}
void onTaskAppeared(ITaskOrganizer organizer, Task task) {
@@ -368,10 +461,15 @@
// change.
mTmpTaskInfo = null;
- if (task.mTaskOrganizer != null) {
- try {
- task.mTaskOrganizer.onTaskInfoChanged(newInfo);
- } catch (RemoteException e) {
+ if (task.isOrganized()) {
+ // Because we defer sending taskAppeared() until the app has drawn, we may receive a
+ // configuration change before the state actually has the task registered. As such we
+ // should ignore these change events to the organizer until taskAppeared(). If the task
+ // was created by the organizer, then we always send the info change.
+ final TaskOrganizerState state = mTaskOrganizerStates.get(
+ task.mTaskOrganizer.asBinder());
+ if (state != null) {
+ state.mOrganizer.onTaskInfoChanged(task, newInfo);
}
}
}
@@ -531,11 +629,7 @@
return false;
}
- try {
- state.mOrganizer.onBackPressedOnTaskRoot(task.getTaskInfo());
- } catch (Exception e) {
- Slog.e(TAG, "Exception sending interceptBackPressedOnTaskRoot callback" + e);
- }
+ state.mOrganizer.onBackPressedOnTaskRoot(task);
return true;
}
@@ -552,7 +646,7 @@
final TaskOrganizerState state = mTaskOrganizerStates.get(taskOrgs.get(j));
final ArrayList<Task> tasks = state.mOrganizedTasks;
pw.print(innerPrefix + " ");
- pw.println(state.mOrganizer + " uid=" + state.mUid + ":");
+ pw.println(state.mOrganizer.mTaskOrganizer + " uid=" + state.mUid + ":");
for (int k = 0; k < tasks.size(); k++) {
pw.println(innerPrefix + " " + tasks.get(k));
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 3c0eb88..fba22dd 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 @@
* @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 @@
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,12 +2185,37 @@
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;
}
private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
boolean isVoiceInteraction) {
+ if (isOrganized()) {
+ // Defer to the task organizer to run animations
+ return null;
+ }
+
final DisplayContent displayContent = getDisplayContent();
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
final int width = displayInfo.appWidth;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c3fb68f..9d87976 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.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 @@
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 @@
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 @@
}
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 @@
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 @@
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 3e2e9be..86bc013 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 @@
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 5a76bac..c11c29b5 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.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 @@
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 @@
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 @@
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 @@
mToken = token;
mActivityRecord = mToken.asActivityRecord();
mOwnerUid = ownerId;
+ mShowUserId = showUserId;
mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
mWindowId = new WindowId(this);
mAttrs.copyFrom(a);
@@ -3275,7 +3282,7 @@
}
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 @@
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 @@
mLastTitle = title;
mWasExiting = mAnimatingExit;
mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this))
- + " u" + UserHandle.getUserId(mOwnerUid)
+ + " u" + mShowUserId
+ " " + mLastTitle + (mAnimatingExit ? " EXITING}" : "}");
}
return mStringNameCache;
@@ -4274,9 +4281,12 @@
logPerformShow("performShow on ");
final int drawState = mWinAnimator.mDrawState;
- if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW)
- && mAttrs.type != TYPE_APPLICATION_STARTING && mActivityRecord != null) {
- mActivityRecord.onFirstWindowDrawn(this, mWinAnimator);
+ if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
+ if (mAttrs.type != TYPE_APPLICATION_STARTING) {
+ mActivityRecord.onFirstWindowDrawn(this, mWinAnimator);
+ } else {
+ mActivityRecord.onStartingWindowDrawn();
+ }
}
if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5976b48..e34b816 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 @@
// 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 @@
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 @@
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/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index e99a264..76b1713 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -135,7 +135,7 @@
static jlong android_server_SystemServer_startIncrementalService(JNIEnv* env, jclass klass,
jobject self) {
- return Incremental_IncrementalService_Start();
+ return Incremental_IncrementalService_Start(env);
}
static void android_server_SystemServer_setIncrementalServiceSystemReady(JNIEnv* env, jclass klass,
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 853eba7..e32a343 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -410,6 +410,8 @@
// Installation.
bool onPrepareImage(dataloader::DataLoaderInstallationFiles addedFiles) final {
+ ALOGE("onPrepareImage: start.");
+
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
const auto& jni = jniIds(env);
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 2dbbc5a..fc8c6fe 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>
@@ -57,10 +58,10 @@
return true;
}
-BinderIncrementalService::BinderIncrementalService(const sp<IServiceManager>& sm)
- : mImpl(RealServiceManager(sm), getIncrementalDir()) {}
+BinderIncrementalService::BinderIncrementalService(const sp<IServiceManager>& sm, JNIEnv* env)
+ : mImpl(RealServiceManager(sm, env), getIncrementalDir()) {}
-BinderIncrementalService* BinderIncrementalService::start() {
+BinderIncrementalService* BinderIncrementalService::start(JNIEnv* env) {
if (!incFsEnabled()) {
return nullptr;
}
@@ -80,7 +81,7 @@
return nullptr;
}
- sp<BinderIncrementalService> self(new BinderIncrementalService(sm));
+ sp<BinderIncrementalService> self(new BinderIncrementalService(sm, env));
status_t ret = sm->addService(String16{getServiceName()}, self);
if (ret != android::OK) {
return nullptr;
@@ -93,8 +94,8 @@
}
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);
@@ -115,11 +116,14 @@
return ok();
}
-binder::Status BinderIncrementalService::createStorage(const std::string& path,
- const DataLoaderParamsParcel& params,
- const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& listener,
- int32_t createMode, int32_t* _aidl_return) {
- *_aidl_return = mImpl.createStorage(path, const_cast<DataLoaderParamsParcel&&>(params), listener, android::incremental::IncrementalService::CreateOptions(createMode));
+binder::Status BinderIncrementalService::createStorage(
+ const std::string& path, const DataLoaderParamsParcel& params,
+ const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& listener,
+ int32_t createMode, int32_t* _aidl_return) {
+ *_aidl_return =
+ mImpl.createStorage(path, const_cast<DataLoaderParamsParcel&&>(params), listener,
+ android::incremental::IncrementalService::CreateOptions(
+ createMode));
return ok();
}
@@ -180,7 +184,8 @@
if (!params.signature) {
nfp.signature = {};
} else {
- nfp.signature = {(const char*)params.signature->data(), (IncFsSize)params.signature->size()};
+ nfp.signature = {(const char*)params.signature->data(),
+ (IncFsSize)params.signature->size()};
}
return {0, id, nfp};
}
@@ -277,10 +282,16 @@
return ok();
}
+binder::Status BinderIncrementalService::waitForNativeBinariesExtraction(int storageId,
+ bool* _aidl_return) {
+ *_aidl_return = mImpl.waitForNativeBinariesExtraction(storageId);
+ return ok();
+}
+
} // namespace android::os::incremental
-jlong Incremental_IncrementalService_Start() {
- return (jlong)android::os::incremental::BinderIncrementalService::start();
+jlong Incremental_IncrementalService_Start(JNIEnv* env) {
+ return (jlong)android::os::incremental::BinderIncrementalService::start(env);
}
void Incremental_IncrementalService_OnSystemReady(jlong self) {
if (self) {
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 28613e1..5a7d5da 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -18,6 +18,7 @@
#include <binder/BinderService.h>
#include <binder/IServiceManager.h>
+#include <jni.h>
#include "IncrementalService.h"
#include "android/os/incremental/BnIncrementalService.h"
@@ -28,9 +29,9 @@
class BinderIncrementalService : public BnIncrementalService,
public BinderService<BinderIncrementalService> {
public:
- BinderIncrementalService(const sp<IServiceManager>& sm);
+ BinderIncrementalService(const sp<IServiceManager>& sm, JNIEnv* env);
- static BinderIncrementalService* start();
+ static BinderIncrementalService* start(JNIEnv* env);
static const char16_t* getServiceName() { return u"incremental"; }
status_t dump(int fd, const Vector<String16>& args) final;
@@ -38,7 +39,10 @@
void onInvalidStorage(int mountId);
binder::Status openStorage(const std::string& path, int32_t* _aidl_return) final;
- binder::Status createStorage(const ::std::string& path, const ::android::content::pm::DataLoaderParamsParcel& params, const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& listener, int32_t createMode, int32_t* _aidl_return) final;
+ binder::Status createStorage(
+ const ::std::string& path, const ::android::content::pm::DataLoaderParamsParcel& params,
+ const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& listener,
+ int32_t createMode, int32_t* _aidl_return) final;
binder::Status createLinkedStorage(const std::string& path, int32_t otherStorageId,
int32_t createMode, int32_t* _aidl_return) final;
binder::Status makeBindMount(int32_t storageId, const std::string& sourcePath,
@@ -68,9 +72,11 @@
std::vector<uint8_t>* _aidl_return) final;
binder::Status startLoading(int32_t storageId, bool* _aidl_return) final;
binder::Status deleteStorage(int32_t storageId) final;
+
binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath,
const std::string& libDirRelativePath,
const std::string& abi, bool* _aidl_return) final;
+ binder::Status waitForNativeBinariesExtraction(int storageId, bool* _aidl_return) final;
private:
android::incremental::IncrementalService mImpl;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 92366e5..2c6bf0a 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -20,28 +20,24 @@
#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>
#include <android/content/pm/IDataLoaderStatusListener.h>
#include <android/os/IVold.h>
-#include <androidfw/ZipFileRO.h>
-#include <androidfw/ZipUtils.h>
#include <binder/BinderService.h>
#include <binder/Nullable.h>
#include <binder/ParcelFileDescriptor.h>
#include <binder/Status.h>
#include <sys/stat.h>
#include <uuid/uuid.h>
-#include <zlib.h>
#include <charconv>
#include <ctime>
#include <filesystem>
#include <iterator>
#include <span>
-#include <stack>
-#include <thread>
#include <type_traits>
#include "Metadata.pb.h"
@@ -163,7 +159,9 @@
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;
@@ -239,6 +237,7 @@
mDataLoaderManager(sm.getDataLoaderManager()),
mIncFs(sm.getIncFs()),
mAppOpsManager(sm.getAppOpsManager()),
+ mJni(sm.getJni()),
mIncrementalDir(rootDir) {
if (!mVold) {
LOG(FATAL) << "Vold service is unavailable";
@@ -249,6 +248,13 @@
if (!mAppOpsManager) {
LOG(FATAL) << "AppOpsManager is unavailable";
}
+
+ mJobQueue.reserve(16);
+ mJobProcessor = std::thread([this]() {
+ mJni->initializeForCurrentThread();
+ runJobProcessing();
+ });
+
mountExistingImages();
}
@@ -256,7 +262,14 @@
return IncFs_FileIdFromMetadata({(const char*)metadata.data(), metadata.size()});
}
-IncrementalService::~IncrementalService() = default;
+IncrementalService::~IncrementalService() {
+ {
+ std::lock_guard lock(mJobMutex);
+ mRunning = false;
+ }
+ mJobCondition.notify_all();
+ mJobProcessor.join();
+}
inline const char* toString(TimePoint t) {
using SystemClock = std::chrono::system_clock;
@@ -288,9 +301,12 @@
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 +337,9 @@
}
}
-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 +353,8 @@
}
}
+ /* 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 +363,8 @@
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 +481,13 @@
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 +515,20 @@
// 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 +602,10 @@
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 +618,7 @@
}
if (enableReadLogs) {
- registerAppOpsCallback(ifs->dataLoaderParams.packageName);
+ registerAppOpsCallback(params.packageName);
}
return 0;
@@ -701,8 +718,8 @@
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 +1001,19 @@
}
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 +1059,13 @@
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 +1137,13 @@
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 +1153,10 @@
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>
@@ -1177,8 +1168,6 @@
bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_view apkFullPath,
std::string_view libDirRelativePath,
std::string_view abi) {
- namespace sc = std::chrono;
- using Clock = sc::steady_clock;
auto start = Clock::now();
const auto ifs = getIfs(storage);
@@ -1195,33 +1184,35 @@
}
auto mkDirsTs = Clock::now();
-
- std::unique_ptr<ZipFileRO> zipFile(ZipFileRO::open(path::c_str(apkFullPath)));
- if (!zipFile) {
+ ZipArchiveHandle zipFileHandle;
+ if (OpenArchive(path::c_str(apkFullPath), &zipFileHandle)) {
LOG(ERROR) << "Failed to open zip file at " << apkFullPath;
return false;
}
+
+ // Need a shared pointer: will be passing it into all unpacking jobs.
+ std::shared_ptr<ZipArchive> zipFile(zipFileHandle, [](ZipArchiveHandle h) { CloseArchive(h); });
void* cookie = nullptr;
const auto libFilePrefix = path::join(constants().libDir, abi);
- if (!zipFile->startIteration(&cookie, libFilePrefix.c_str() /* prefix */,
- constants().libSuffix.data() /* suffix */)) {
+ if (StartIteration(zipFile.get(), &cookie, libFilePrefix, constants().libSuffix)) {
LOG(ERROR) << "Failed to start zip iteration for " << apkFullPath;
return false;
}
- auto endIteration = [&zipFile](void* cookie) { zipFile->endIteration(cookie); };
+ auto endIteration = [](void* cookie) { EndIteration(cookie); };
auto iterationCleaner = std::unique_ptr<void, decltype(endIteration)>(cookie, endIteration);
auto openZipTs = Clock::now();
- std::vector<IncFsDataBlock> instructions;
- ZipEntryRO entry = nullptr;
- while ((entry = zipFile->nextEntry(cookie)) != nullptr) {
- auto startFileTs = Clock::now();
-
- char fileName[PATH_MAX];
- if (zipFile->getEntryFileName(entry, fileName, sizeof(fileName))) {
+ std::vector<Job> jobQueue;
+ ZipEntry entry;
+ std::string_view fileName;
+ while (!Next(cookie, &entry, &fileName)) {
+ if (fileName.empty()) {
continue;
}
+
+ auto startFileTs = Clock::now();
+
const auto libName = path::basename(fileName);
const auto targetLibPath = path::join(libDirRelativePath, libName);
const auto targetLibPathAbsolute = normalizePathToStorage(ifs, storage, targetLibPath);
@@ -1235,16 +1226,9 @@
continue;
}
- uint32_t uncompressedLen, compressedLen;
- if (!zipFile->getEntryInfo(entry, nullptr, &uncompressedLen, &compressedLen, nullptr,
- nullptr, nullptr)) {
- LOG(ERROR) << "Failed to read native lib entry: " << fileName;
- return false;
- }
-
// Create new lib file without signature info
incfs::NewFileParams libFileParams = {
- .size = uncompressedLen,
+ .size = entry.uncompressed_length,
.signature = {},
// Metadata of the new lib file is its relative path
.metadata = {targetLibPath.c_str(), (IncFsSize)targetLibPath.size()},
@@ -1260,81 +1244,158 @@
auto makeFileTs = Clock::now();
// If it is a zero-byte file, skip data writing
- if (uncompressedLen == 0) {
+ if (entry.uncompressed_length == 0) {
if (sEnablePerfLogging) {
- LOG(INFO) << "incfs: Extracted " << libName << "(" << compressedLen << " -> "
- << uncompressedLen << " bytes): " << elapsedMcs(startFileTs, makeFileTs)
- << "mcs, make: " << elapsedMcs(startFileTs, makeFileTs);
+ LOG(INFO) << "incfs: Extracted " << libName
+ << "(0 bytes): " << elapsedMcs(startFileTs, makeFileTs) << "mcs";
}
continue;
}
- // Write extracted data to new file
- // NOTE: don't zero-initialize memory, it may take a while
- auto libData = std::unique_ptr<uint8_t[]>(new uint8_t[uncompressedLen]);
- if (!zipFile->uncompressEntry(entry, libData.get(), uncompressedLen)) {
- LOG(ERROR) << "Failed to extract native lib zip entry: " << fileName;
- return false;
- }
-
- auto extractFileTs = Clock::now();
-
- const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId);
- if (!writeFd.ok()) {
- LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd;
- return false;
- }
-
- auto openFileTs = Clock::now();
-
- const int numBlocks = (uncompressedLen + constants().blockSize - 1) / constants().blockSize;
- instructions.clear();
- instructions.reserve(numBlocks);
- auto remainingData = std::span(libData.get(), uncompressedLen);
- for (int i = 0; i < numBlocks; i++) {
- const auto blockSize = std::min<uint16_t>(constants().blockSize, remainingData.size());
- auto inst = IncFsDataBlock{
- .fileFd = writeFd.get(),
- .pageIndex = static_cast<IncFsBlockIndex>(i),
- .compression = INCFS_COMPRESSION_KIND_NONE,
- .kind = INCFS_BLOCK_KIND_DATA,
- .dataSize = blockSize,
- .data = reinterpret_cast<const char*>(remainingData.data()),
- };
- instructions.push_back(inst);
- remainingData = remainingData.subspan(blockSize);
- }
- auto prepareInstsTs = Clock::now();
-
- size_t res = mIncFs->writeBlocks(instructions);
- if (res != instructions.size()) {
- LOG(ERROR) << "Failed to write data into: " << targetLibPath;
- return false;
- }
+ jobQueue.emplace_back([this, zipFile, entry, ifs = std::weak_ptr<IncFsMount>(ifs),
+ libFileId, libPath = std::move(targetLibPath),
+ makeFileTs]() mutable {
+ extractZipFile(ifs.lock(), zipFile.get(), entry, libFileId, libPath, makeFileTs);
+ });
if (sEnablePerfLogging) {
- auto endFileTs = Clock::now();
- LOG(INFO) << "incfs: Extracted " << libName << "(" << compressedLen << " -> "
- << uncompressedLen << " bytes): " << elapsedMcs(startFileTs, endFileTs)
- << "mcs, make: " << elapsedMcs(startFileTs, makeFileTs)
- << " extract: " << elapsedMcs(makeFileTs, extractFileTs)
- << " open: " << elapsedMcs(extractFileTs, openFileTs)
- << " prepare: " << elapsedMcs(openFileTs, prepareInstsTs)
- << " write:" << elapsedMcs(prepareInstsTs, endFileTs);
+ auto prepareJobTs = Clock::now();
+ LOG(INFO) << "incfs: Processed " << libName << ": "
+ << elapsedMcs(startFileTs, prepareJobTs)
+ << "mcs, make file: " << elapsedMcs(startFileTs, makeFileTs)
+ << " prepare job: " << elapsedMcs(makeFileTs, prepareJobTs);
}
}
+ auto processedTs = Clock::now();
+
+ if (!jobQueue.empty()) {
+ {
+ std::lock_guard lock(mJobMutex);
+ if (mRunning) {
+ auto& existingJobs = mJobQueue[storage];
+ if (existingJobs.empty()) {
+ existingJobs = std::move(jobQueue);
+ } else {
+ existingJobs.insert(existingJobs.end(), std::move_iterator(jobQueue.begin()),
+ std::move_iterator(jobQueue.end()));
+ }
+ }
+ }
+ mJobCondition.notify_all();
+ }
+
if (sEnablePerfLogging) {
auto end = Clock::now();
LOG(INFO) << "incfs: configureNativeBinaries complete in " << elapsedMcs(start, end)
<< "mcs, make dirs: " << elapsedMcs(start, mkDirsTs)
<< " open zip: " << elapsedMcs(mkDirsTs, openZipTs)
- << " extract all: " << elapsedMcs(openZipTs, end);
+ << " make files: " << elapsedMcs(openZipTs, processedTs)
+ << " schedule jobs: " << elapsedMcs(processedTs, end);
}
return true;
}
+void IncrementalService::extractZipFile(const IfsMountPtr& ifs, ZipArchiveHandle zipFile,
+ ZipEntry& entry, const incfs::FileId& libFileId,
+ std::string_view targetLibPath,
+ Clock::time_point scheduledTs) {
+ if (!ifs) {
+ LOG(INFO) << "Skipping zip file " << targetLibPath << " extraction for an expired mount";
+ return;
+ }
+
+ auto libName = path::basename(targetLibPath);
+ auto startedTs = Clock::now();
+
+ // Write extracted data to new file
+ // NOTE: don't zero-initialize memory, it may take a while for nothing
+ auto libData = std::unique_ptr<uint8_t[]>(new uint8_t[entry.uncompressed_length]);
+ if (ExtractToMemory(zipFile, &entry, libData.get(), entry.uncompressed_length)) {
+ LOG(ERROR) << "Failed to extract native lib zip entry: " << libName;
+ return;
+ }
+
+ auto extractFileTs = Clock::now();
+
+ const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId);
+ if (!writeFd.ok()) {
+ LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd;
+ return;
+ }
+
+ auto openFileTs = Clock::now();
+ const int numBlocks =
+ (entry.uncompressed_length + constants().blockSize - 1) / constants().blockSize;
+ std::vector<IncFsDataBlock> instructions(numBlocks);
+ auto remainingData = std::span(libData.get(), entry.uncompressed_length);
+ for (int i = 0; i < numBlocks; i++) {
+ const auto blockSize = std::min<uint16_t>(constants().blockSize, remainingData.size());
+ instructions[i] = IncFsDataBlock{
+ .fileFd = writeFd.get(),
+ .pageIndex = static_cast<IncFsBlockIndex>(i),
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .kind = INCFS_BLOCK_KIND_DATA,
+ .dataSize = blockSize,
+ .data = reinterpret_cast<const char*>(remainingData.data()),
+ };
+ remainingData = remainingData.subspan(blockSize);
+ }
+ auto prepareInstsTs = Clock::now();
+
+ size_t res = mIncFs->writeBlocks(instructions);
+ if (res != instructions.size()) {
+ LOG(ERROR) << "Failed to write data into: " << targetLibPath;
+ return;
+ }
+
+ if (sEnablePerfLogging) {
+ auto endFileTs = Clock::now();
+ LOG(INFO) << "incfs: Extracted " << libName << "(" << entry.compressed_length << " -> "
+ << entry.uncompressed_length << " bytes): " << elapsedMcs(startedTs, endFileTs)
+ << "mcs, scheduling delay: " << elapsedMcs(scheduledTs, startedTs)
+ << " extract: " << elapsedMcs(startedTs, extractFileTs)
+ << " open: " << elapsedMcs(extractFileTs, openFileTs)
+ << " prepare: " << elapsedMcs(openFileTs, prepareInstsTs)
+ << " write: " << elapsedMcs(prepareInstsTs, endFileTs);
+ }
+}
+
+bool IncrementalService::waitForNativeBinariesExtraction(StorageId storage) {
+ std::unique_lock lock(mJobMutex);
+ mJobCondition.wait(lock, [this, storage] {
+ return !mRunning ||
+ (mPendingJobsStorage != storage && mJobQueue.find(storage) == mJobQueue.end());
+ });
+ return mPendingJobsStorage != storage && mJobQueue.find(storage) == mJobQueue.end();
+}
+
+void IncrementalService::runJobProcessing() {
+ for (;;) {
+ std::unique_lock lock(mJobMutex);
+ mJobCondition.wait(lock, [this]() { return !mRunning || !mJobQueue.empty(); });
+ if (!mRunning) {
+ return;
+ }
+
+ auto it = mJobQueue.begin();
+ mPendingJobsStorage = it->first;
+ auto queue = std::move(it->second);
+ mJobQueue.erase(it);
+ lock.unlock();
+
+ for (auto&& job : queue) {
+ job();
+ }
+
+ lock.lock();
+ mPendingJobsStorage = kInvalidStorageId;
+ lock.unlock();
+ mJobCondition.notify_all();
+ }
+}
+
void IncrementalService::registerAppOpsCallback(const std::string& packageName) {
sp<IAppOpsCallback> listener;
{
@@ -1347,7 +1408,8 @@
listener = cb;
}
- mAppOpsManager->startWatchingMode(AppOpsManager::OP_GET_USAGE_STATS, String16(packageName.c_str()), listener);
+ mAppOpsManager->startWatchingMode(AppOpsManager::OP_GET_USAGE_STATS,
+ String16(packageName.c_str()), listener);
}
bool IncrementalService::unregisterAppOpsCallback(const std::string& packageName) {
@@ -1376,7 +1438,7 @@
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 +1448,79 @@
}
}
-binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId,
- int newStatus) {
- if (externalListener) {
- // Give an external listener a chance to act before we destroy something.
- externalListener->onStatusChanged(mountId, newStatus);
+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;
}
- bool startRequested = false;
+ 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.
+ mListener->onStatusChanged(mountId, newStatus);
+ }
+
{
- 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 db14a79..e7705df 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -23,16 +23,19 @@
#include <utils/String16.h>
#include <utils/StrongPointer.h>
#include <utils/Vector.h>
+#include <ziparchive/zip_archive.h>
#include <atomic>
#include <chrono>
-#include <future>
+#include <condition_variable>
+#include <functional>
#include <limits>
#include <map>
#include <mutex>
#include <span>
#include <string>
#include <string_view>
+#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>
@@ -60,7 +63,8 @@
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 +99,7 @@
void onDump(int fd);
- std::optional<std::future<void>> onSystemReady();
+ void onSystemReady();
StorageId createStorage(std::string_view mountPoint, DataLoaderParamsParcel&& dataLoaderParams,
const DataLoaderStatusListener& dataLoaderStatusListener,
@@ -131,25 +135,15 @@
std::vector<std::string> listFiles(StorageId storage) const;
bool startLoading(StorageId storage) const;
+
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;
- };
+ bool waitForNativeBinariesExtraction(StorageId storage);
class AppOpsListener : public android::BnAppOpsCallback {
public:
- AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {}
+ AppOpsListener(IncrementalService& incrementalService, std::string packageName)
+ : incrementalService(incrementalService), packageName(std::move(packageName)) {}
void opChanged(int32_t op, const String16& packageName) final;
private:
@@ -171,6 +165,45 @@
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 +227,8 @@
/*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 +263,8 @@
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;
@@ -252,11 +283,17 @@
bool unregisterAppOpsCallback(const std::string& packageName);
void onAppOpChanged(const std::string& packageName);
- // Member variables
- std::unique_ptr<VoldServiceWrapper> const mVold;
- std::unique_ptr<DataLoaderManagerWrapper> const mDataLoaderManager;
- std::unique_ptr<IncFsWrapper> const mIncFs;
- std::unique_ptr<AppOpsManagerWrapper> const mAppOpsManager;
+ void runJobProcessing();
+ void extractZipFile(const IfsMountPtr& ifs, ZipArchiveHandle zipFile, ZipEntry& entry,
+ const incfs::FileId& libFileId, std::string_view targetLibPath,
+ Clock::time_point scheduledTs);
+
+private:
+ const std::unique_ptr<VoldServiceWrapper> mVold;
+ const std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager;
+ const std::unique_ptr<IncFsWrapper> mIncFs;
+ const std::unique_ptr<AppOpsManagerWrapper> mAppOpsManager;
+ const std::unique_ptr<JniWrapper> mJni;
const std::string mIncrementalDir;
mutable std::mutex mLock;
@@ -269,7 +306,14 @@
std::atomic_bool mSystemReady = false;
StorageId mNextId = 0;
- std::promise<void> mPrepareDataLoaders;
+
+ using Job = std::function<void()>;
+ std::unordered_map<StorageId, std::vector<Job>> mJobQueue;
+ StorageId mPendingJobsStorage = kInvalidStorageId;
+ std::condition_variable mJobCondition;
+ std::mutex mJobMutex;
+ std::thread mJobProcessor;
+ bool mRunning = true;
};
} // namespace android::incremental
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 9f4192f..bf8e696 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -14,8 +14,11 @@
* limitations under the License.
*/
+#define LOG_TAG "IncrementalService"
+
#include "ServiceWrappers.h"
+#include <android-base/logging.h>
#include <utils/String16.h>
using namespace std::literals;
@@ -25,8 +28,123 @@
static constexpr auto kVoldServiceName = "vold"sv;
static constexpr auto kDataLoaderManagerName = "dataloader_manager"sv;
-RealServiceManager::RealServiceManager(sp<IServiceManager> serviceManager)
- : mServiceManager(std::move(serviceManager)) {}
+class RealVoldService : public VoldServiceWrapper {
+public:
+ RealVoldService(const sp<os::IVold> vold) : mInterface(std::move(vold)) {}
+ ~RealVoldService() = default;
+ binder::Status mountIncFs(const std::string& backingPath, const std::string& targetDir,
+ int32_t flags,
+ IncrementalFileSystemControlParcel* _aidl_return) const final {
+ return mInterface->mountIncFs(backingPath, targetDir, flags, _aidl_return);
+ }
+ binder::Status unmountIncFs(const std::string& dir) const final {
+ return mInterface->unmountIncFs(dir);
+ }
+ binder::Status bindMount(const std::string& sourceDir,
+ const std::string& targetDir) const final {
+ return mInterface->bindMount(sourceDir, targetDir);
+ }
+ binder::Status setIncFsMountOptions(
+ const ::android::os::incremental::IncrementalFileSystemControlParcel& control,
+ bool enableReadLogs) const final {
+ return mInterface->setIncFsMountOptions(control, enableReadLogs);
+ }
+
+private:
+ sp<os::IVold> mInterface;
+};
+
+class RealDataLoaderManager : public DataLoaderManagerWrapper {
+public:
+ RealDataLoaderManager(const sp<content::pm::IDataLoaderManager> manager)
+ : mInterface(manager) {}
+ ~RealDataLoaderManager() = default;
+ binder::Status initializeDataLoader(MountId mountId, const DataLoaderParamsParcel& params,
+ const FileSystemControlParcel& control,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) const final {
+ return mInterface->initializeDataLoader(mountId, params, control, listener, _aidl_return);
+ }
+ binder::Status getDataLoader(MountId mountId, sp<IDataLoader>* _aidl_return) const final {
+ return mInterface->getDataLoader(mountId, _aidl_return);
+ }
+ binder::Status destroyDataLoader(MountId mountId) const final {
+ return mInterface->destroyDataLoader(mountId);
+ }
+
+private:
+ sp<content::pm::IDataLoaderManager> mInterface;
+};
+
+class RealAppOpsManager : public AppOpsManagerWrapper {
+public:
+ ~RealAppOpsManager() = default;
+ binder::Status checkPermission(const char* permission, const char* operation,
+ const char* package) const final {
+ return android::incremental::CheckPermissionForDataDelivery(permission, operation, package);
+ }
+ void startWatchingMode(int32_t op, const String16& packageName,
+ const sp<IAppOpsCallback>& callback) final {
+ mAppOpsManager.startWatchingMode(op, packageName, callback);
+ }
+ void stopWatchingMode(const sp<IAppOpsCallback>& callback) final {
+ mAppOpsManager.stopWatchingMode(callback);
+ }
+
+private:
+ android::AppOpsManager mAppOpsManager;
+};
+
+class RealJniWrapper final : public JniWrapper {
+public:
+ RealJniWrapper(JavaVM* jvm);
+ void initializeForCurrentThread() const final;
+
+ static JavaVM* getJvm(JNIEnv* env);
+
+private:
+ JavaVM* const mJvm;
+};
+
+class RealIncFs : public IncFsWrapper {
+public:
+ RealIncFs() = default;
+ ~RealIncFs() = default;
+ Control createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs) const final {
+ return incfs::createControl(cmd, pendingReads, logs);
+ }
+ ErrorCode makeFile(const Control& control, std::string_view path, int mode, FileId id,
+ NewFileParams params) const final {
+ return incfs::makeFile(control, path, mode, id, params);
+ }
+ ErrorCode makeDir(const Control& control, std::string_view path, int mode) const final {
+ return incfs::makeDir(control, path, mode);
+ }
+ RawMetadata getMetadata(const Control& control, FileId fileid) const final {
+ return incfs::getMetadata(control, fileid);
+ }
+ RawMetadata getMetadata(const Control& control, std::string_view path) const final {
+ return incfs::getMetadata(control, path);
+ }
+ FileId getFileId(const Control& control, std::string_view path) const final {
+ return incfs::getFileId(control, path);
+ }
+ ErrorCode link(const Control& control, std::string_view from, std::string_view to) const final {
+ return incfs::link(control, from, to);
+ }
+ ErrorCode unlink(const Control& control, std::string_view path) const final {
+ return incfs::unlink(control, path);
+ }
+ base::unique_fd openForSpecialOps(const Control& control, FileId id) const final {
+ return base::unique_fd{incfs::openForSpecialOps(control, id).release()};
+ }
+ ErrorCode writeBlocks(Span<const DataBlock> blocks) const final {
+ return incfs::writeBlocks(blocks);
+ }
+};
+
+RealServiceManager::RealServiceManager(sp<IServiceManager> serviceManager, JNIEnv* env)
+ : mServiceManager(std::move(serviceManager)), mJvm(RealJniWrapper::getJvm(env)) {}
template <class INTERFACE>
sp<INTERFACE> RealServiceManager::getRealService(std::string_view serviceName) const {
@@ -63,4 +181,62 @@
return std::make_unique<RealAppOpsManager>();
}
+std::unique_ptr<JniWrapper> RealServiceManager::getJni() {
+ return std::make_unique<RealJniWrapper>(mJvm);
+}
+
+static JavaVM* getJavaVm(JNIEnv* env) {
+ CHECK(env);
+ JavaVM* jvm = nullptr;
+ env->GetJavaVM(&jvm);
+ CHECK(jvm);
+ return jvm;
+}
+
+static JNIEnv* getJniEnv(JavaVM* vm) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ return nullptr;
+ }
+ return env;
+}
+
+static JNIEnv* getOrAttachJniEnv(JavaVM* jvm) {
+ if (!jvm) {
+ LOG(ERROR) << "No JVM instance";
+ return nullptr;
+ }
+
+ JNIEnv* env = getJniEnv(jvm);
+ if (!env) {
+ int result = jvm->AttachCurrentThread(&env, nullptr);
+ if (result != JNI_OK) {
+ LOG(ERROR) << "JVM thread attach failed: " << result;
+ return nullptr;
+ }
+ struct VmDetacher {
+ VmDetacher(JavaVM* vm) : mVm(vm) {}
+ ~VmDetacher() { mVm->DetachCurrentThread(); }
+
+ private:
+ JavaVM* const mVm;
+ };
+ static thread_local VmDetacher detacher(jvm);
+ }
+
+ return env;
+}
+
+RealJniWrapper::RealJniWrapper(JavaVM* jvm) : mJvm(jvm) {
+ CHECK(!!mJvm) << "JVM is unavailable";
+}
+
+void RealJniWrapper::initializeForCurrentThread() const {
+ (void)getOrAttachJniEnv(mJvm);
+}
+
+JavaVM* RealJniWrapper::getJvm(JNIEnv* env) {
+ return getJavaVm(env);
+}
+
} // namespace android::os::incremental
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 84bf1ff..142bf2e 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -29,6 +29,7 @@
#include <binder/AppOpsManager.h>
#include <binder/IServiceManager.h>
#include <incfs.h>
+#include <jni.h>
#include <memory>
#include <string>
@@ -93,6 +94,12 @@
virtual void stopWatchingMode(const sp<IAppOpsCallback>& callback) = 0;
};
+class JniWrapper {
+public:
+ virtual ~JniWrapper() = default;
+ virtual void initializeForCurrentThread() const = 0;
+};
+
class ServiceManagerWrapper {
public:
virtual ~ServiceManagerWrapper() = default;
@@ -100,127 +107,26 @@
virtual std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() = 0;
virtual std::unique_ptr<IncFsWrapper> getIncFs() = 0;
virtual std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() = 0;
+ virtual std::unique_ptr<JniWrapper> getJni() = 0;
};
// --- Real stuff ---
-class RealVoldService : public VoldServiceWrapper {
-public:
- RealVoldService(const sp<os::IVold> vold) : mInterface(std::move(vold)) {}
- ~RealVoldService() = default;
- binder::Status mountIncFs(const std::string& backingPath, const std::string& targetDir,
- int32_t flags,
- IncrementalFileSystemControlParcel* _aidl_return) const final {
- return mInterface->mountIncFs(backingPath, targetDir, flags, _aidl_return);
- }
- binder::Status unmountIncFs(const std::string& dir) const final {
- return mInterface->unmountIncFs(dir);
- }
- binder::Status bindMount(const std::string& sourceDir,
- const std::string& targetDir) const final {
- return mInterface->bindMount(sourceDir, targetDir);
- }
- binder::Status setIncFsMountOptions(
- const ::android::os::incremental::IncrementalFileSystemControlParcel& control,
- bool enableReadLogs) const final {
- return mInterface->setIncFsMountOptions(control, enableReadLogs);
- }
-
-private:
- sp<os::IVold> mInterface;
-};
-
-class RealDataLoaderManager : public DataLoaderManagerWrapper {
-public:
- RealDataLoaderManager(const sp<content::pm::IDataLoaderManager> manager)
- : mInterface(manager) {}
- ~RealDataLoaderManager() = default;
- binder::Status initializeDataLoader(MountId mountId, const DataLoaderParamsParcel& params,
- const FileSystemControlParcel& control,
- const sp<IDataLoaderStatusListener>& listener,
- bool* _aidl_return) const final {
- return mInterface->initializeDataLoader(mountId, params, control, listener, _aidl_return);
- }
- binder::Status getDataLoader(MountId mountId, sp<IDataLoader>* _aidl_return) const final {
- return mInterface->getDataLoader(mountId, _aidl_return);
- }
- binder::Status destroyDataLoader(MountId mountId) const final {
- return mInterface->destroyDataLoader(mountId);
- }
-
-private:
- sp<content::pm::IDataLoaderManager> mInterface;
-};
-
-class RealAppOpsManager : public AppOpsManagerWrapper {
-public:
- ~RealAppOpsManager() = default;
- binder::Status checkPermission(const char* permission, const char* operation,
- const char* package) const final {
- return android::incremental::CheckPermissionForDataDelivery(permission, operation, package);
- }
- void startWatchingMode(int32_t op, const String16& packageName,
- const sp<IAppOpsCallback>& callback) final {
- mAppOpsManager.startWatchingMode(op, packageName, callback);
- }
- void stopWatchingMode(const sp<IAppOpsCallback>& callback) final {
- mAppOpsManager.stopWatchingMode(callback);
- }
-
-private:
- android::AppOpsManager mAppOpsManager;
-};
-
class RealServiceManager : public ServiceManagerWrapper {
public:
- RealServiceManager(sp<IServiceManager> serviceManager);
+ RealServiceManager(sp<IServiceManager> serviceManager, JNIEnv* env);
~RealServiceManager() = default;
std::unique_ptr<VoldServiceWrapper> getVoldService() final;
std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final;
std::unique_ptr<IncFsWrapper> getIncFs() final;
std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() final;
+ std::unique_ptr<JniWrapper> getJni() final;
private:
template <class INTERFACE>
sp<INTERFACE> getRealService(std::string_view serviceName) const;
sp<android::IServiceManager> mServiceManager;
-};
-
-class RealIncFs : public IncFsWrapper {
-public:
- RealIncFs() = default;
- ~RealIncFs() = default;
- Control createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs) const final {
- return incfs::createControl(cmd, pendingReads, logs);
- }
- ErrorCode makeFile(const Control& control, std::string_view path, int mode, FileId id,
- NewFileParams params) const final {
- return incfs::makeFile(control, path, mode, id, params);
- }
- ErrorCode makeDir(const Control& control, std::string_view path, int mode) const final {
- return incfs::makeDir(control, path, mode);
- }
- RawMetadata getMetadata(const Control& control, FileId fileid) const final {
- return incfs::getMetadata(control, fileid);
- }
- RawMetadata getMetadata(const Control& control, std::string_view path) const final {
- return incfs::getMetadata(control, path);
- }
- FileId getFileId(const Control& control, std::string_view path) const final {
- return incfs::getFileId(control, path);
- }
- ErrorCode link(const Control& control, std::string_view from, std::string_view to) const final {
- return incfs::link(control, from, to);
- }
- ErrorCode unlink(const Control& control, std::string_view path) const final {
- return incfs::unlink(control, path);
- }
- base::unique_fd openForSpecialOps(const Control& control, FileId id) const final {
- return base::unique_fd{incfs::openForSpecialOps(control, id).release()};
- }
- ErrorCode writeBlocks(Span<const DataBlock> blocks) const final {
- return incfs::writeBlocks(blocks);
- }
+ JavaVM* const mJvm;
};
} // namespace android::os::incremental
diff --git a/services/incremental/include/incremental_service.h b/services/incremental/include/incremental_service.h
index 4a34b11..3213875 100644
--- a/services/incremental/include/incremental_service.h
+++ b/services/incremental/include/incremental_service.h
@@ -24,7 +24,7 @@
#define INCREMENTAL_LIBRARY_NAME "service.incremental.so"
-jlong Incremental_IncrementalService_Start();
+jlong Incremental_IncrementalService_Start(JNIEnv* env);
void Incremental_IncrementalService_OnSystemReady(jlong self);
void Incremental_IncrementalService_OnDump(jlong self, jint fd);
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 18ae4b5..117dca8 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -131,6 +131,23 @@
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 @@
*_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);
@@ -254,28 +261,39 @@
sp<IAppOpsCallback> mStoredCallback;
};
+class MockJniWrapper : public JniWrapper {
+public:
+ MOCK_CONST_METHOD0(initializeForCurrentThread, void());
+
+ MockJniWrapper() { EXPECT_CALL(*this, initializeForCurrentThread()).Times(1); }
+};
+
class MockServiceManager : public ServiceManagerWrapper {
public:
MockServiceManager(std::unique_ptr<MockVoldService> vold,
- std::unique_ptr<MockDataLoaderManager> manager,
+ std::unique_ptr<MockDataLoaderManager> dataLoaderManager,
std::unique_ptr<MockIncFs> incfs,
- std::unique_ptr<MockAppOpsManager> appOpsManager)
+ std::unique_ptr<MockAppOpsManager> appOpsManager,
+ std::unique_ptr<MockJniWrapper> jni)
: mVold(std::move(vold)),
- mDataLoaderManager(std::move(manager)),
+ mDataLoaderManager(std::move(dataLoaderManager)),
mIncFs(std::move(incfs)),
- mAppOpsManager(std::move(appOpsManager)) {}
+ mAppOpsManager(std::move(appOpsManager)),
+ mJni(std::move(jni)) {}
std::unique_ptr<VoldServiceWrapper> getVoldService() final { return std::move(mVold); }
std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final {
return std::move(mDataLoaderManager);
}
std::unique_ptr<IncFsWrapper> getIncFs() final { return std::move(mIncFs); }
std::unique_ptr<AppOpsManagerWrapper> getAppOpsManager() final { return std::move(mAppOpsManager); }
+ std::unique_ptr<JniWrapper> getJni() final { return std::move(mJni); }
private:
std::unique_ptr<MockVoldService> mVold;
std::unique_ptr<MockDataLoaderManager> mDataLoaderManager;
std::unique_ptr<MockIncFs> mIncFs;
std::unique_ptr<MockAppOpsManager> mAppOpsManager;
+ std::unique_ptr<MockJniWrapper> mJni;
};
// --- IncrementalServiceTest ---
@@ -291,14 +309,19 @@
mIncFs = incFs.get();
auto appOps = std::make_unique<NiceMock<MockAppOpsManager>>();
mAppOpsManager = appOps.get();
+ auto jni = std::make_unique<NiceMock<MockJniWrapper>>();
+ mJni = jni.get();
mIncrementalService =
std::make_unique<IncrementalService>(MockServiceManager(std::move(vold),
- std::move(dataloaderManager),
+ std::move(
+ dataloaderManager),
std::move(incFs),
- std::move(appOps)),
+ std::move(appOps),
+ std::move(jni)),
mRootDir.path);
mDataLoaderParcel.packageName = "com.test";
mDataLoaderParcel.arguments = "uri";
+ mDataLoaderManager->destroyDataLoaderOk();
mIncrementalService->onSystemReady();
}
@@ -328,6 +351,7 @@
NiceMock<MockIncFs>* mIncFs;
NiceMock<MockDataLoaderManager>* mDataLoaderManager;
NiceMock<MockAppOpsManager>* mAppOpsManager;
+ NiceMock<MockJniWrapper>* mJni;
std::unique_ptr<IncrementalService> mIncrementalService;
TemporaryDir mRootDir;
DataLoaderParamsParcel mDataLoaderParcel;
@@ -346,6 +370,7 @@
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 +382,7 @@
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 +396,7 @@
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 +410,7 @@
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 +424,7 @@
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 c54102f..9f29799 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -12,8 +12,8 @@
":services.net-sources",
],
static_libs: [
- "dnsresolver_aidl_interface-V2-java",
- "netd_aidl_interface-unstable-java",
+ "dnsresolver_aidl_interface-V4-java",
+ "netd_aidl_interface-V3-java",
"netlink-client",
"networkstack-client",
"net-utils-services-common",
@@ -44,7 +44,7 @@
],
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 f4d7b8b..d338b58 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 @@
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 ccbaee4..aa923e2 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 @@
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 820e61c..9eda718 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 @@
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 0000000..cfeadc6
--- /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 881561f..1f6ba7a 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 @@
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 @@
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/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 2e7dd6a..7613655 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -26,6 +26,7 @@
import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.TYPE_VIRTUAL;
+import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -247,6 +248,44 @@
assertEquals(originalStackCount, defaultTaskDisplayArea.getStackCount());
}
+ /**
+ * Verifies that removal of activities with task and stack is done correctly when there are
+ * several task display areas.
+ */
+ @Test
+ public void testRemovingStackOnAppCrash_multipleDisplayAreas() {
+ final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
+ .getDefaultTaskDisplayArea();
+ final int originalStackCount = defaultTaskDisplayArea.getStackCount();
+ final ActivityStack stack = defaultTaskDisplayArea.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+ .setStack(stack).build();
+ assertEquals(originalStackCount + 1, defaultTaskDisplayArea.getStackCount());
+
+ final DisplayContent dc = defaultTaskDisplayArea.getDisplayContent();
+ doReturn(2).when(dc).getTaskDisplayAreaCount();
+ final TaskDisplayArea secondTaskDisplayArea = new TaskDisplayArea(dc,
+ mRootWindowContainer.mWmService, "SecondaryTaskDisplayArea", FEATURE_VENDOR_FIRST);
+ // Add second display area right above the default one
+ defaultTaskDisplayArea.getParent().addChild(secondTaskDisplayArea,
+ defaultTaskDisplayArea.getParent().mChildren.indexOf(defaultTaskDisplayArea) + 1);
+ doReturn(secondTaskDisplayArea).when(dc).getTaskDisplayAreaAt(1);
+ final ActivityStack secondStack = secondTaskDisplayArea.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ new ActivityBuilder(mService).setCreateTask(true).setStack(secondStack)
+ .setUseProcess(firstActivity.app).build();
+ assertEquals(1, secondTaskDisplayArea.getStackCount());
+
+ // Let's pretend that the app has crashed.
+ firstActivity.app.setThread(null);
+ mRootWindowContainer.finishTopCrashedActivities(firstActivity.app, "test");
+
+ // Verify that the stacks were removed.
+ assertEquals(originalStackCount, defaultTaskDisplayArea.getStackCount());
+ assertEquals(0, secondTaskDisplayArea.getStackCount());
+ }
+
@Test
public void testFocusability() {
final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 06ca6c1..f275e37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -69,6 +69,8 @@
import androidx.test.filters.SmallTest;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -117,6 +119,13 @@
return createTaskStackOnDisplay(mDisplayContent);
}
+ @Before
+ public void setUp() {
+ // We defer callbacks since we need to adjust task surface visibility, but for these tests,
+ // just run the callbacks synchronously
+ mWm.mAtmService.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer((r) -> r.run());
+ }
+
@Test
public void testAppearVanish() throws RemoteException {
final ActivityStack stack = createStack();
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 7a075a2..4a8e8da 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 @@
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 084216a..fc95556 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 @@
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 397f73c..e561c13 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 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 @@
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 @@
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 76479cb..535d53e 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.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 @@
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 bce06e4..4e14fd3 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -1092,16 +1092,16 @@
* 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 0d66013..73296986 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1865,25 +1865,23 @@
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 @@
}
}
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 90b69a3..1f8aafb 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -22,6 +22,7 @@
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 @@
*/
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 @@
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 @@
mAddressPresentation = addressPresentation;
mCallerDisplayName = callerDisplayName;
mCallerDisplayNamePresentation = callerDisplayNamePresentation;
- mDisconnectCause = null;
- mRingbackRequested = false;
+ mDisconnectCause = disconnectCause;
+ mRingbackRequested = ringbackRequested;
+ mCallDirection = callDirection;
}
@Override
@@ -134,6 +218,8 @@
.append(mRingbackRequested)
.append(", disconnectCause: ")
.append(mDisconnectCause)
+ .append(", callDirection: ")
+ .append(mCallDirection)
.toString();
}
@@ -192,10 +278,15 @@
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 @@
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 @@
destination.writeInt(mCallerDisplayNamePresentation);
destination.writeParcelable(mDisconnectCause, 0);
destination.writeInt(mRingbackRequested ? 1 : 0);
+ destination.writeInt(mCallDirection);
}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 56f3c3e..7f6e123 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3074,6 +3074,28 @@
public static final String KEY_UNMETERED_NR_NSA_SUB6_BOOL = "unmetered_nr_nsa_sub6_bool";
/**
+ * Whether NR (standalone) should be unmetered for all frequencies.
+ * If either {@link #KEY_UNMETERED_NR_SA_MMWAVE_BOOL} or
+ * {@link #KEY_UNMETERED_NR_SA_SUB6_BOOL} are true, then this value will be ignored.
+ * @hide
+ */
+ public static final String KEY_UNMETERED_NR_SA_BOOL = "unmetered_nr_sa_bool";
+
+ /**
+ * Whether NR (standalone) frequencies above 6GHz (millimeter wave) should be unmetered.
+ * If this is true, then the value for {@link #KEY_UNMETERED_NR_SA_BOOL} will be ignored.
+ * @hide
+ */
+ public static final String KEY_UNMETERED_NR_SA_MMWAVE_BOOL = "unmetered_nr_sa_mmwave_bool";
+
+ /**
+ * Whether NR (standalone) frequencies below 6GHz (sub6) should be unmetered.
+ * If this is true, then the value for {@link #KEY_UNMETERED_NR_SA_BOOL} will be ignored.
+ * @hide
+ */
+ public static final String KEY_UNMETERED_NR_SA_SUB6_BOOL = "unmetered_nr_sa_sub6_bool";
+
+ /**
* Support ASCII 7-BIT encoding for long SMS. This carrier config is used to enable
* this feature.
* @hide
@@ -3625,6 +3647,17 @@
public static final String KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY =
"missed_incoming_call_sms_originator_string_array";
+
+ /**
+ * String array of Apn Type configurations.
+ * The entries should be of form "APN_TYPE_NAME:priority".
+ * priority is an integer that is sorted from highest to lowest.
+ * example: cbs:5
+ *
+ * @hide
+ */
+ public static final String KEY_APN_PRIORITY_STRING_ARRAY = "apn_priority_string_array";
+
/**
* The patterns of missed incoming call sms. This is the regular expression used for
* matching the missed incoming call's date, time, and caller id. The pattern should match
@@ -4090,6 +4123,9 @@
sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false);
sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, false);
sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_SUB6_BOOL, false);
+ sDefaults.putBoolean(KEY_UNMETERED_NR_SA_BOOL, false);
+ sDefaults.putBoolean(KEY_UNMETERED_NR_SA_MMWAVE_BOOL, false);
+ sDefaults.putBoolean(KEY_UNMETERED_NR_SA_SUB6_BOOL, false);
sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_WIFI_CALLING_ICON_IN_STATUS_BAR_BOOL, false);
/* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */
@@ -4154,6 +4190,10 @@
sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG, TimeUnit.DAYS.toMillis(1));
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
new String[0]);
+ sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
+ "default:0", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2",
+ "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
+ });
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 912a27f..b864e37 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -4908,6 +4908,29 @@
}
@Test
+ public void testDnsConfigurationTransTypesPushed() throws Exception {
+ // Clear any interactions that occur as a result of CS starting up.
+ reset(mMockDnsResolver);
+
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ verify(mMockDnsResolver, times(1)).createNetworkCache(
+ eq(mWiFiNetworkAgent.getNetwork().netId));
+ verify(mMockDnsResolver, times(2)).setResolverConfiguration(
+ mResolverParamsParcelCaptor.capture());
+ final ResolverParamsParcel resolverParams = mResolverParamsParcelCaptor.getValue();
+ assertContainsExactly(resolverParams.transportTypes, TRANSPORT_WIFI);
+ reset(mMockDnsResolver);
+ }
+
+ @Test
public void testPrivateDnsNotification() throws Exception {
NetworkRequest request = new NetworkRequest.Builder()
.clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index 8fa0ab9..a392ae3 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -18,22 +18,34 @@
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
+import static com.android.testutils.MiscAssertsKt.assertContainsExactly;
+import static com.android.testutils.MiscAssertsKt.assertContainsStringsExactly;
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.content.Context;
import android.net.IDnsResolver;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.ResolverOptionsParcel;
+import android.net.ResolverParamsParcel;
import android.net.RouteInfo;
import android.net.shared.PrivateDnsConfig;
import android.provider.Settings;
@@ -47,6 +59,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -66,8 +79,11 @@
static final int TEST_NETID = 100;
static final int TEST_NETID_ALTERNATE = 101;
static final int TEST_NETID_UNTRACKED = 102;
- final boolean IS_DEFAULT = true;
- final boolean NOT_DEFAULT = false;
+ static final int TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800;
+ static final int TEST_DEFAULT_SUCCESS_THRESHOLD_PERCENT = 25;
+ static final int TEST_DEFAULT_MIN_SAMPLES = 8;
+ static final int TEST_DEFAULT_MAX_SAMPLES = 64;
+ static final int[] TEST_TRANSPORT_TYPES = {TRANSPORT_WIFI, TRANSPORT_VPN};
DnsManager mDnsManager;
MockContentResolver mContentResolver;
@@ -76,6 +92,35 @@
@Mock IDnsResolver mMockDnsResolver;
@Mock MockableSystemProperties mSystemProperties;
+ private void assertResolverOptionsEquals(
+ @NonNull ResolverOptionsParcel actual,
+ @NonNull ResolverOptionsParcel expected) {
+ assertEquals(actual.hosts, expected.hosts);
+ assertEquals(actual.tcMode, expected.tcMode);
+ assertFieldCountEquals(2, ResolverOptionsParcel.class);
+ }
+
+ private void assertResolverParamsEquals(@NonNull ResolverParamsParcel actual,
+ @NonNull ResolverParamsParcel expected) {
+ assertEquals(actual.netId, expected.netId);
+ assertEquals(actual.sampleValiditySeconds, expected.sampleValiditySeconds);
+ assertEquals(actual.successThreshold, expected.successThreshold);
+ assertEquals(actual.minSamples, expected.minSamples);
+ assertEquals(actual.maxSamples, expected.maxSamples);
+ assertEquals(actual.baseTimeoutMsec, expected.baseTimeoutMsec);
+ assertEquals(actual.retryCount, expected.retryCount);
+ assertContainsStringsExactly(actual.servers, expected.servers);
+ assertContainsStringsExactly(actual.domains, expected.domains);
+ assertEquals(actual.tlsName, expected.tlsName);
+ assertContainsStringsExactly(actual.tlsServers, expected.tlsServers);
+ assertContainsStringsExactly(actual.tlsFingerprints, expected.tlsFingerprints);
+ assertEquals(actual.caCertificate, expected.caCertificate);
+ assertEquals(actual.tlsConnectTimeoutMs, expected.tlsConnectTimeoutMs);
+ assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions);
+ assertContainsExactly(actual.transportTypes, expected.transportTypes);
+ assertFieldCountEquals(16, ResolverParamsParcel.class);
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -103,8 +148,13 @@
lp.addDnsServer(InetAddress.getByName("4.4.4.4"));
// Send a validation event that is tracked on the alternate netId
- mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT);
- mDnsManager.setDnsConfigurationForNetwork(TEST_NETID_ALTERNATE, lp, NOT_DEFAULT);
+ mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
+ mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers());
+ mDnsManager.flushVmDnsCache();
+ mDnsManager.updateTransportsForNetwork(TEST_NETID_ALTERNATE, TEST_TRANSPORT_TYPES);
+ mDnsManager.noteDnsServersForNetwork(TEST_NETID_ALTERNATE, lp);
+ mDnsManager.flushVmDnsCache();
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_ALTERNATE,
InetAddress.parseNumericAddress("4.4.4.4"), "", true));
@@ -135,7 +185,10 @@
InetAddress.parseNumericAddress("6.6.6.6"),
InetAddress.parseNumericAddress("2001:db8:66:66::1")
}));
- mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT);
+ mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
+ mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers());
+ mDnsManager.flushVmDnsCache();
fixedLp = new LinkProperties(lp);
mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp);
assertTrue(fixedLp.isPrivateDnsActive());
@@ -168,7 +221,10 @@
// be tracked.
LinkProperties lp = new LinkProperties();
lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
- mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT);
+ mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
+ mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers());
+ mDnsManager.flushVmDnsCache();
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
InetAddress.parseNumericAddress("3.3.3.3"), "", true));
@@ -179,7 +235,10 @@
// Validation event has untracked netId
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
mDnsManager.getPrivateDnsConfig());
- mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT);
+ mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
+ mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers());
+ mDnsManager.flushVmDnsCache();
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_UNTRACKED,
InetAddress.parseNumericAddress("3.3.3.3"), "", true));
@@ -225,7 +284,10 @@
Settings.Global.putString(mContentResolver, PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_OFF);
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
mDnsManager.getPrivateDnsConfig());
- mDnsManager.setDnsConfigurationForNetwork(TEST_NETID, lp, IS_DEFAULT);
+ mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
+ mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers());
+ mDnsManager.flushVmDnsCache();
mDnsManager.updatePrivateDnsValidation(
new DnsManager.PrivateDnsValidationUpdate(TEST_NETID,
InetAddress.parseNumericAddress("3.3.3.3"), "", true));
@@ -258,4 +320,38 @@
assertEquals("strictmode.com", cfgStrict.hostname);
assertEquals(new InetAddress[0], cfgStrict.ips);
}
+
+ @Test
+ public void testSendDnsConfiguration() throws Exception {
+ reset(mMockDnsResolver);
+ mDnsManager.updatePrivateDns(new Network(TEST_NETID),
+ mDnsManager.getPrivateDnsConfig());
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(TEST_IFACENAME);
+ lp.addDnsServer(InetAddress.getByName("3.3.3.3"));
+ lp.addDnsServer(InetAddress.getByName("4.4.4.4"));
+ mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES);
+ mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
+ mDnsManager.setDefaultDnsSystemProperties(lp.getDnsServers());
+ mDnsManager.flushVmDnsCache();
+
+ final ArgumentCaptor<ResolverParamsParcel> resolverParamsParcelCaptor =
+ ArgumentCaptor.forClass(ResolverParamsParcel.class);
+ verify(mMockDnsResolver, times(1)).setResolverConfiguration(
+ resolverParamsParcelCaptor.capture());
+ final ResolverParamsParcel actualParams = resolverParamsParcelCaptor.getValue();
+ final ResolverParamsParcel expectedParams = new ResolverParamsParcel();
+ expectedParams.netId = TEST_NETID;
+ expectedParams.sampleValiditySeconds = TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS;
+ expectedParams.successThreshold = TEST_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
+ expectedParams.minSamples = TEST_DEFAULT_MIN_SAMPLES;
+ expectedParams.maxSamples = TEST_DEFAULT_MAX_SAMPLES;
+ expectedParams.servers = new String[]{"3.3.3.3", "4.4.4.4"};
+ expectedParams.domains = new String[]{};
+ expectedParams.tlsName = "";
+ expectedParams.tlsServers = new String[]{"3.3.3.3", "4.4.4.4"};
+ expectedParams.transportTypes = TEST_TRANSPORT_TYPES;
+ expectedParams.resolverOptions = new ResolverOptionsParcel();
+ assertResolverParamsEquals(actualParams, expectedParams);
+ }
}
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
index cf11f43..f852311 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.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.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 @@
# 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/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index b110a61..71f0ab8 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -2973,4 +2973,15 @@
*/
public boolean isMostRecentlyConnected = false;
+ /**
+ * Whether the key mgmt indicates if the WifiConfiguration needs a preSharedKey or not.
+ * @return true if preSharedKey is needed, false otherwise.
+ * @hide
+ */
+ public boolean needsPreSharedKey() {
+ return allowedKeyManagement.get(KeyMgmt.WPA_PSK)
+ || allowedKeyManagement.get(KeyMgmt.SAE)
+ || allowedKeyManagement.get(KeyMgmt.WAPI_PSK);
+ }
+
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index f1be8b2..6c8dc00 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -179,6 +179,8 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
* <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
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index e210e4f..a7b6765 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -16,9 +16,13 @@
package android.net.wifi;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP;
import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OPEN;
import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OWE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_PSK;
import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_SAE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_WAPI_PSK;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -507,4 +511,30 @@
assertEquals(NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED,
status2.getNetworkSelectionStatus());
}
+
+ @Test
+ public void testNeedsPreSharedKey() throws Exception {
+ WifiConfiguration configuration = new WifiConfiguration();
+
+ configuration.setSecurityParams(SECURITY_TYPE_PSK);
+ assertTrue(configuration.needsPreSharedKey());
+
+ configuration.setSecurityParams(SECURITY_TYPE_SAE);
+ assertTrue(configuration.needsPreSharedKey());
+
+ configuration.setSecurityParams(SECURITY_TYPE_WAPI_PSK);
+ assertTrue(configuration.needsPreSharedKey());
+
+ configuration.setSecurityParams(SECURITY_TYPE_OPEN);
+ assertFalse(configuration.needsPreSharedKey());
+
+ configuration.setSecurityParams(SECURITY_TYPE_OWE);
+ assertFalse(configuration.needsPreSharedKey());
+
+ configuration.setSecurityParams(SECURITY_TYPE_EAP);
+ assertFalse(configuration.needsPreSharedKey());
+
+ configuration.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B);
+ assertFalse(configuration.needsPreSharedKey());
+ }
}