Merge "Change "Storage" permission group label"
diff --git a/Android.bp b/Android.bp
index a7ac094..f6e0104 100644
--- a/Android.bp
+++ b/Android.bp
@@ -409,6 +409,7 @@
filegroup {
name: "libincident_aidl",
srcs: [
+ "core/java/android/os/IIncidentDumpCallback.aidl",
"core/java/android/os/IIncidentManager.aidl",
"core/java/android/os/IIncidentReportStatusListener.aidl",
],
@@ -491,8 +492,7 @@
"framework-sdkextensions-stubs-systemapi",
// TODO(b/146167933): Use framework-statsd-stubs instead.
"framework-statsd",
- // TODO(b/140299412): should be framework-wifi-stubs
- "framework-wifi",
+ "framework-wifi-stubs",
"ike-stubs",
// TODO(b/147200698): should be the stub of framework-tethering
"framework-tethering",
@@ -528,7 +528,8 @@
"compat-changeid-annotation-processor",
],
static_libs: [
- "exoplayer2-core"
+ "exoplayer2-core",
+ "android.hardware.wifi-V1.0-java-constants",
]
}
@@ -637,6 +638,7 @@
name: "framework-ike-shared-srcs",
visibility: ["//frameworks/opt/net/ike"],
srcs: [
+ "core/java/android/annotation/StringDef.java",
"core/java/android/net/annotations/PolicyDirection.java",
"core/java/com/android/internal/util/IState.java",
"core/java/com/android/internal/util/State.java",
@@ -1124,6 +1126,7 @@
"core/java/android/util/TimeUtils.java",
"core/java/com/android/internal/os/SomeArgs.java",
"core/java/com/android/internal/util/AsyncChannel.java",
+ "core/java/com/android/internal/util/AsyncService.java",
"core/java/com/android/internal/util/BitwiseInputStream.java",
"core/java/com/android/internal/util/FastXmlSerializer.java",
"core/java/com/android/internal/util/HexDump.java",
diff --git a/ApiDocs.bp b/ApiDocs.bp
index e373db6..c40004c 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -121,8 +121,10 @@
doc_defaults {
name: "framework-docs-default",
- libs: framework_docs_only_libs +
- ["stub-annotations"],
+ libs: framework_docs_only_libs + [
+ "stub-annotations",
+ "unsupportedappusage",
+ ],
html_dirs: [
"docs/html",
],
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
index b5d903a..7e5f187 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
@@ -19,6 +19,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import com.android.internal.annotations.VisibleForTesting;
+
import com.google.android.icing.proto.PropertyConfigProto;
import com.google.android.icing.proto.SchemaProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;
@@ -70,7 +72,8 @@
* @hide
*/
@NonNull
- SchemaProto getProto() {
+ @VisibleForTesting
+ public SchemaProto getProto() {
return mProto;
}
@@ -288,7 +291,7 @@
@NonNull
public PropertyConfig build() {
if (mProtoBuilder.getDataType() == PropertyConfigProto.DataType.Code.UNKNOWN) {
- throw new IllegalSchemaException("Missing dataType field");
+ throw new IllegalSchemaException("Missing field: dataType");
}
if (mProtoBuilder.getSchemaType().isEmpty()
&& mProtoBuilder.getDataType()
@@ -299,7 +302,7 @@
}
if (mProtoBuilder.getCardinality()
== PropertyConfigProto.Cardinality.Code.UNKNOWN) {
- throw new IllegalSchemaException("Missing cardinality field");
+ throw new IllegalSchemaException("Missing field: cardinality");
}
return new PropertyConfig(mProtoBuilder.build());
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
index 08811f8..ca5b884 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
+++ b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
@@ -10,6 +10,14 @@
"include-filter": "com.android.server.appsearch"
}
]
+ },
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "android.app.appsearch"
+ }
+ ]
}
]
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index f6512a6..102e848 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -257,7 +257,7 @@
private final List<JobRestriction> mJobRestrictions;
private final CountQuotaTracker mQuotaTracker;
- private static final String QUOTA_TRACKER_SCHEDULE_TAG = ".schedule()";
+ private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
private final PlatformCompat mPlatformCompat;
/**
@@ -522,7 +522,7 @@
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
- private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 500;
+ private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
@@ -740,6 +740,8 @@
ENABLE_API_QUOTAS = mParser.getBoolean(KEY_ENABLE_API_QUOTAS,
DEFAULT_ENABLE_API_QUOTAS);
+ // Set a minimum value on the quota limit so it's not so low that it interferes with
+ // legitimate use cases.
API_QUOTA_SCHEDULE_COUNT = Math.max(250,
mParser.getInt(KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT));
API_QUOTA_SCHEDULE_WINDOW_MS = mParser.getDurationMillis(
@@ -1054,44 +1056,48 @@
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
- final String pkg = packageName == null ? job.getService().getPackageName() : packageName;
- if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_TAG)) {
- Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
- // TODO(b/145551233): attempt to restrict app
- if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
- && mPlatformCompat.isChangeEnabledByPackageName(
- CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
- final boolean isDebuggable;
- synchronized (mLock) {
- if (!mDebuggableApps.containsKey(packageName)) {
- try {
- final ApplicationInfo appInfo = AppGlobals.getPackageManager()
- .getApplicationInfo(pkg, 0, userId);
- if (appInfo != null) {
- mDebuggableApps.put(packageName,
- (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
- } else {
- return JobScheduler.RESULT_FAILURE;
+ if (job.isPersisted()) {
+ // Only limit schedule calls for persisted jobs.
+ final String pkg =
+ packageName == null ? job.getService().getPackageName() : packageName;
+ if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
+ Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
+ // TODO(b/145551233): attempt to restrict app
+ if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
+ && mPlatformCompat.isChangeEnabledByPackageName(
+ CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
+ final boolean isDebuggable;
+ synchronized (mLock) {
+ if (!mDebuggableApps.containsKey(packageName)) {
+ try {
+ final ApplicationInfo appInfo = AppGlobals.getPackageManager()
+ .getApplicationInfo(pkg, 0, userId);
+ if (appInfo != null) {
+ mDebuggableApps.put(packageName,
+ (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
+ } else {
+ return JobScheduler.RESULT_FAILURE;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
}
- } catch (RemoteException e) {
- throw new RuntimeException(e);
}
+ isDebuggable = mDebuggableApps.get(packageName);
}
- isDebuggable = mDebuggableApps.get(packageName);
+ if (isDebuggable) {
+ // Only throw the exception for debuggable apps.
+ throw new IllegalStateException(
+ "schedule()/enqueue() called more than "
+ + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY)
+ + " times in the past "
+ + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY)
+ + "ms");
+ }
}
- if (isDebuggable) {
- // Only throw the exception for debuggable apps.
- throw new IllegalStateException(
- "schedule()/enqueue() called more than "
- + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY)
- + " times in the past "
- + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY)
- + "ms");
- }
+ return JobScheduler.RESULT_FAILURE;
}
- return JobScheduler.RESULT_FAILURE;
+ mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
}
- mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_TAG);
try {
if (ActivityManager.getService().isAppStartModeDisabled(uId,
diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp
index 5945fb3..f232e62 100644
--- a/apex/permission/Android.bp
+++ b/apex/permission/Android.bp
@@ -22,6 +22,7 @@
name: "com.android.permission-defaults",
key: "com.android.permission.key",
certificate: ":com.android.permission.certificate",
+ apps: ["PermissionController"],
}
apex_key {
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 bbb87ab..6444b4e 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -744,97 +744,6 @@
return null;
}
- private void pullKernelWakelock(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- final KernelWakelockStats wakelockStats =
- mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
- for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
- String name = ent.getKey();
- KernelWakelockStats.Entry kws = ent.getValue();
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeString(name);
- e.writeInt(kws.mCount);
- e.writeInt(kws.mVersion);
- e.writeLong(kws.mTotalTime);
- pulledData.add(e);
- }
- }
-
- private void pullCpuTimePerFreq(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
- long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
- if (clusterTimeMs != null) {
- for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(cluster);
- e.writeInt(speed);
- e.writeLong(clusterTimeMs[speed]);
- pulledData.add(e);
- }
- }
- }
- }
-
- private void pullKernelUidCpuTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
- long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeInt(uid);
- e.writeLong(userTimeUs);
- e.writeLong(systemTimeUs);
- pulledData.add(e);
- });
- }
-
- private void pullKernelUidCpuFreqTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
- for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
- if (cpuFreqTimeMs[freqIndex] != 0) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(uid);
- e.writeInt(freqIndex);
- e.writeLong(cpuFreqTimeMs[freqIndex]);
- pulledData.add(e);
- }
- }
- });
- }
-
- private void pullKernelUidCpuClusterTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
- for (int i = 0; i < cpuClusterTimesMs.length; i++) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(uid);
- e.writeInt(i);
- e.writeLong(cpuClusterTimesMs[i]);
- pulledData.add(e);
- }
- });
- }
-
- private void pullKernelUidCpuActiveTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeInt(uid);
- e.writeLong((long) cpuActiveTimesMs);
- pulledData.add(e);
- });
- }
-
private void pullWifiActivityInfo(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
@@ -918,13 +827,6 @@
pulledData.add(e);
}
- private void pullSystemUpTime(int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(SystemClock.uptimeMillis());
- pulledData.add(e);
- }
-
private void pullProcessMemoryState(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
@@ -1502,21 +1404,6 @@
}
}
- private void pullBuildInformation(int tagId,
- long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeString(Build.FINGERPRINT);
- e.writeString(Build.BRAND);
- e.writeString(Build.PRODUCT);
- e.writeString(Build.DEVICE);
- e.writeString(Build.VERSION.RELEASE);
- e.writeString(Build.ID);
- e.writeString(Build.VERSION.INCREMENTAL);
- e.writeString(Build.TYPE);
- e.writeString(Build.TAGS);
- pulledData.add(e);
- }
-
private BatteryStatsHelper getBatteryStatsHelper() {
if (mBatteryStatsHelper == null) {
final long callingToken = Binder.clearCallingIdentity();
@@ -2108,36 +1995,6 @@
long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
switch (tagId) {
- case StatsLog.KERNEL_WAKELOCK: {
- pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
- case StatsLog.CPU_TIME_PER_FREQ: {
- pullCpuTimePerFreq(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
- case StatsLog.CPU_TIME_PER_UID: {
- pullKernelUidCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
- case StatsLog.CPU_TIME_PER_UID_FREQ: {
- pullKernelUidCpuFreqTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
- case StatsLog.CPU_CLUSTER_TIME: {
- pullKernelUidCpuClusterTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
- case StatsLog.CPU_ACTIVE_TIME: {
- pullKernelUidCpuActiveTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
case StatsLog.WIFI_ACTIVITY_INFO: {
pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
break;
@@ -2148,11 +2005,6 @@
break;
}
- case StatsLog.SYSTEM_UPTIME: {
- pullSystemUpTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
case StatsLog.SYSTEM_ELAPSED_REALTIME: {
pullSystemElapsedRealtime(tagId, elapsedNanos, wallClockNanos, ret);
break;
@@ -2246,11 +2098,6 @@
break;
}
- case StatsLog.BUILD_INFORMATION: {
- pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
-
case StatsLog.PROCESS_CPU_TIME: {
pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
break;
diff --git a/api/current.txt b/api/current.txt
index ba14b4e6..1f3a4f6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -386,6 +386,7 @@
field public static final int canRequestFingerprintGestures = 16844109; // 0x101054d
field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
field public static final int canRetrieveWindowContent = 16843653; // 0x1010385
+ field public static final int canTakeScreenshot = 16844304; // 0x1010610
field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
field public static final int cantSaveState = 16844142; // 0x101056e
field @Deprecated public static final int capitalize = 16843113; // 0x1010169
@@ -2864,6 +2865,7 @@
method protected void onServiceConnected();
method public final boolean performGlobalAction(int);
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
+ method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>);
field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2
field public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; // 0xf
field public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; // 0x10
@@ -2960,6 +2962,7 @@
field public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 64; // 0x40
field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1
+ field public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 128; // 0x80
field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityServiceInfo> CREATOR;
field public static final int DEFAULT = 1; // 0x1
field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
@@ -11418,6 +11421,7 @@
public final class InstallSourceInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getInitiatingPackageName();
+ method @Nullable public android.content.pm.SigningInfo getInitiatingPackageSigningInfo();
method @Nullable public String getInstallingPackageName();
method @Nullable public String getOriginatingPackageName();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -11784,6 +11788,7 @@
method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public CharSequence getBackgroundPermissionButtonLabel();
method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
@@ -16852,6 +16857,8 @@
method @Nullable public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
method public boolean isConfirmationRequired();
+ field public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; // 0x2
+ field public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; // 0x1
field public static final int BIOMETRIC_ACQUIRED_GOOD = 0; // 0x0
field public static final int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
field public static final int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; // 0x2
@@ -16881,6 +16888,7 @@
}
public static class BiometricPrompt.AuthenticationResult {
+ method public int getAuthenticationType();
method public android.hardware.biometrics.BiometricPrompt.CryptoObject getCryptoObject();
}
@@ -27138,9 +27146,11 @@
public abstract class VolumeProvider {
ctor public VolumeProvider(int, int, int);
+ ctor public VolumeProvider(int, int, int, @Nullable String);
method public final int getCurrentVolume();
method public final int getMaxVolume();
method public final int getVolumeControl();
+ method @Nullable public final String getVolumeControlId();
method public void onAdjustVolume(int);
method public void onSetVolumeTo(int);
method public final void setCurrentVolume(int);
@@ -28000,6 +28010,7 @@
method public int getMaxVolume();
method public int getPlaybackType();
method public int getVolumeControl();
+ method @Nullable public String getVolumeControlId();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.session.MediaController.PlaybackInfo> CREATOR;
field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
@@ -29641,7 +29652,7 @@
method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
method public android.net.NetworkRequest.Builder removeCapability(int);
method public android.net.NetworkRequest.Builder removeTransportType(int);
- method public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
+ method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
}
@@ -29740,6 +29751,19 @@
method public void onStopped();
}
+ public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getSubscriptionId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.TelephonyNetworkSpecifier> CREATOR;
+ }
+
+ public static final class TelephonyNetworkSpecifier.Builder {
+ ctor public TelephonyNetworkSpecifier.Builder();
+ method @NonNull public android.net.TelephonyNetworkSpecifier build();
+ method @NonNull public android.net.TelephonyNetworkSpecifier.Builder setSubscriptionId(int);
+ }
+
public class TrafficStats {
ctor public TrafficStats();
method public static void clearThreadStatsTag();
@@ -30497,6 +30521,11 @@
field @Deprecated public static final String[] strings;
}
+ @Deprecated public static class WifiConfiguration.SuiteBCipher {
+ field @Deprecated public static final int ECDHE_ECDSA = 0; // 0x0
+ field @Deprecated public static final int ECDHE_RSA = 1; // 0x1
+ }
+
public class WifiEnterpriseConfig implements android.os.Parcelable {
ctor public WifiEnterpriseConfig();
ctor public WifiEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig);
@@ -39517,7 +39546,8 @@
field public static final String DISPLAY_NAME = "_display_name";
field public static final String DOCUMENT_ID = "document_id";
field public static final String DURATION = "duration";
- field public static final String GENERATION = "generation";
+ field public static final String GENERATION_ADDED = "generation_added";
+ field public static final String GENERATION_MODIFIED = "generation_modified";
field public static final String GENRE = "genre";
field public static final String HEIGHT = "height";
field public static final String INSTANCE_ID = "instance_id";
@@ -44050,6 +44080,7 @@
method public java.util.List<android.telecom.Call> getChildren();
method public java.util.List<android.telecom.Call> getConferenceableCalls();
method public android.telecom.Call.Details getDetails();
+ method @Nullable public android.telecom.Call getGenericConferenceActiveChildCall();
method public android.telecom.Call getParent();
method public String getRemainingPostDialSequence();
method @Nullable public android.telecom.Call.RttCall getRttCall();
@@ -44133,6 +44164,7 @@
method public int getCallerDisplayNamePresentation();
method public int getCallerNumberVerificationStatus();
method public final long getConnectTimeMillis();
+ method @Nullable public String getContactDisplayName();
method public long getCreationTimeMillis();
method public android.telecom.DisconnectCause getDisconnectCause();
method public android.os.Bundle getExtras();
@@ -58897,7 +58929,7 @@
method @android.view.ViewDebug.ExportedProperty public CharSequence getFormat24Hour();
method public String getTimeZone();
method public boolean is24HourModeEnabled();
- method public void refresh();
+ method public void refreshTime();
method public void setFormat12Hour(CharSequence);
method public void setFormat24Hour(CharSequence);
method public void setTimeZone(String);
@@ -59212,6 +59244,7 @@
public class Toast {
ctor public Toast(android.content.Context);
+ method public void addCallback(@NonNull android.widget.Toast.Callback);
method public void cancel();
method public int getDuration();
method public int getGravity();
@@ -59222,6 +59255,7 @@
method public int getYOffset();
method public static android.widget.Toast makeText(android.content.Context, CharSequence, int);
method public static android.widget.Toast makeText(android.content.Context, @StringRes int, int) throws android.content.res.Resources.NotFoundException;
+ method public void removeCallback(@NonNull android.widget.Toast.Callback);
method public void setDuration(int);
method public void setGravity(int, int, int);
method public void setMargin(float, float);
@@ -59233,6 +59267,12 @@
field public static final int LENGTH_SHORT = 0; // 0x0
}
+ public abstract static class Toast.Callback {
+ ctor public Toast.Callback();
+ method public void onToastHidden();
+ method public void onToastShown();
+ }
+
public class ToggleButton extends android.widget.CompoundButton {
ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int, int);
ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int);
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 0a67065..c8253a0 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -79,3 +79,48 @@
}
+package android.timezone {
+
+ public final class CountryTimeZones {
+ method @Nullable public android.icu.util.TimeZone getDefaultTimeZone();
+ method @Nullable public String getDefaultTimeZoneId();
+ method @NonNull public java.util.List<android.timezone.CountryTimeZones.TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long);
+ method public boolean hasUtcZone(long);
+ method public boolean isDefaultTimeZoneBoosted();
+ method public boolean isForCountryCode(@NonNull String);
+ method @Nullable public android.timezone.CountryTimeZones.OffsetResult lookupByOffsetWithBias(int, @Nullable Boolean, @Nullable Integer, long, @Nullable android.icu.util.TimeZone);
+ }
+
+ public static final class CountryTimeZones.OffsetResult {
+ ctor public CountryTimeZones.OffsetResult(@NonNull android.icu.util.TimeZone, boolean);
+ method @NonNull public android.icu.util.TimeZone getTimeZone();
+ method public boolean isOnlyMatch();
+ }
+
+ public static final class CountryTimeZones.TimeZoneMapping {
+ method @Nullable public android.icu.util.TimeZone getTimeZone();
+ method @NonNull public String getTimeZoneId();
+ }
+
+ public class TelephonyLookup {
+ method @NonNull public static android.timezone.TelephonyLookup getInstance();
+ method @Nullable public android.timezone.TelephonyNetworkFinder getTelephonyNetworkFinder();
+ }
+
+ public class TelephonyNetwork {
+ method @NonNull public String getCountryIsoCode();
+ method @NonNull public String getMcc();
+ method @NonNull public String getMnc();
+ }
+
+ public class TelephonyNetworkFinder {
+ method @Nullable public android.timezone.TelephonyNetwork findNetworkByMccMnc(@NonNull String, @NonNull String);
+ }
+
+ public final class TimeZoneFinder {
+ method @NonNull public static android.timezone.TimeZoneFinder getInstance();
+ method @Nullable public android.timezone.CountryTimeZones lookupCountryTimeZones(@NonNull String);
+ }
+
+}
+
diff --git a/api/system-current.txt b/api/system-current.txt
index 71d1a38..09684d8 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -791,6 +791,7 @@
package android.app.admin {
public class DevicePolicyManager {
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser();
@@ -1037,6 +1038,7 @@
field public static final int AGENT_ERROR = -1003; // 0xfffffc15
field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14
field public static final String EXTRA_TRANSPORT_REGISTRATION = "android.app.backup.extra.TRANSPORT_REGISTRATION";
+ field public static final int FLAG_DATA_NOT_CHANGED = 8; // 0x8
field public static final int FLAG_INCREMENTAL = 2; // 0x2
field public static final int FLAG_NON_INCREMENTAL = 4; // 0x4
field public static final int FLAG_USER_INITIATED = 1; // 0x1
@@ -1703,7 +1705,7 @@
method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
field public static final String APP_PREDICTION_SERVICE = "app_prediction";
field public static final String BACKUP_SERVICE = "backup";
@@ -4802,6 +4804,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+ method @Deprecated public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int, int, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
@@ -4819,6 +4822,7 @@
field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
field public static final int TYPE_NONE = -1; // 0xffffffff
+ field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
}
public abstract static class ConnectivityManager.OnStartTetheringCallback {
@@ -4967,6 +4971,27 @@
field public final int netId;
}
+ public abstract class NetworkAgent {
+ method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
+ method public void onAutomaticReconnectDisabled();
+ method public void onBandwidthUpdateRequested();
+ method public void onNetworkUnwanted();
+ method public void onRemoveKeepalivePacketFilter(int);
+ method public void onSaveAcceptUnvalidated(boolean);
+ method public void onSignalStrengthThresholdsUpdated(@NonNull int[]);
+ method public void onStartSocketKeepalive(int, int, @NonNull android.net.KeepalivePacketData);
+ method public void onStopSocketKeepalive(int);
+ method public void onValidationStatus(int, @Nullable String);
+ method public void sendLinkProperties(@NonNull android.net.LinkProperties);
+ method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+ method public void sendNetworkScore(int);
+ method public void sendSocketKeepaliveEvent(int, int);
+ field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
+ field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
+ field @NonNull public final android.net.Network network;
+ field public final int providerId;
+ }
+
public final class NetworkAgentConfig implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getSubscriberId();
@@ -5022,6 +5047,10 @@
method public abstract void onRequestScores(android.net.NetworkKey[]);
}
+ public class NetworkRequest implements android.os.Parcelable {
+ method public boolean satisfiedBy(@Nullable android.net.NetworkCapabilities);
+ }
+
public static class NetworkRequest.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
}
@@ -5128,6 +5157,10 @@
field public final android.net.RssiCurve rssiCurve;
}
+ public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ field public static final int SUCCESS = 0; // 0x0
+ }
+
public final class StaticIpConfiguration implements android.os.Parcelable {
ctor public StaticIpConfiguration();
ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
@@ -5161,6 +5194,10 @@
field @NonNull public final String specifier;
}
+ public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
+ }
+
public class TrafficStats {
method public static void setThreadStatsTagApp();
method public static void setThreadStatsTagBackup();
@@ -10311,6 +10348,7 @@
public class ServiceState implements android.os.Parcelable {
method @NonNull public android.telephony.ServiceState createLocationInfoSanitizedCopy(boolean);
method public void fillInNotifierBundle(@NonNull android.os.Bundle);
+ method public int getDataNetworkType();
method public int getDataRegistrationState();
method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoList();
@@ -10567,6 +10605,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(int);
method public String getCdmaPrlVersion();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaRoamingMode();
method public int getCurrentPhoneType();
method public int getCurrentPhoneType(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getDataActivationState();
@@ -10640,6 +10679,8 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAlwaysAllowMmsData(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaRoamingMode(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaSubscriptionMode(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
@@ -10683,6 +10724,9 @@
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1
+ field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0
+ field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff
field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto";
@@ -11596,6 +11640,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
@@ -11860,6 +11905,7 @@
method public String getConfigString(int);
method public final void notifyProvisionedValueChanged(int, int);
method public final void notifyProvisionedValueChanged(int, String);
+ method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method public int setConfig(int, int);
method public int setConfig(int, String);
field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
@@ -12237,6 +12283,12 @@
method public void onJsResultComplete(android.webkit.JsResult);
}
+ public interface PacProcessor {
+ method @NonNull public static android.webkit.PacProcessor getInstance();
+ method @Nullable public String makeProxyRequest(@NonNull String);
+ method public boolean setProxyScript(@NonNull String);
+ }
+
public class SslErrorHandler extends android.os.Handler {
ctor public SslErrorHandler();
}
@@ -12371,6 +12423,7 @@
method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess);
method public android.webkit.CookieManager getCookieManager();
method public android.webkit.GeolocationPermissions getGeolocationPermissions();
+ method @NonNull public default android.webkit.PacProcessor getPacProcessor();
method public android.webkit.ServiceWorkerController getServiceWorkerController();
method public android.webkit.WebViewFactoryProvider.Statics getStatics();
method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/api/test-current.txt b/api/test-current.txt
index 1db4c9b..32959c9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -471,7 +471,10 @@
}
public class WallpaperManager {
+ method @Nullable public android.graphics.Bitmap getBitmap();
method @RequiresPermission("android.permission.SET_WALLPAPER_COMPONENT") public boolean setWallpaperComponent(android.content.ComponentName);
+ method public boolean shouldEnableWideColorGamut();
+ method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
}
public class WindowConfiguration implements java.lang.Comparable<android.app.WindowConfiguration> android.os.Parcelable {
@@ -3264,6 +3267,7 @@
public class ServiceState implements android.os.Parcelable {
method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
+ method public int getDataNetworkType();
method public void setCdmaSystemAndNetworkId(int, int);
method public void setCellBandwidths(int[]);
method public void setChannelNumber(int);
@@ -3827,6 +3831,7 @@
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public String getProvisioningStringValue(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
@@ -4050,6 +4055,7 @@
method public String getConfigString(int);
method public final void notifyProvisionedValueChanged(int, int);
method public final void notifyProvisionedValueChanged(int, String);
+ method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method public int setConfig(int, int);
method public int setConfig(int, String);
field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
@@ -4379,6 +4385,7 @@
}
public final class Display {
+ method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
method public boolean hasAccess(int);
}
@@ -4496,6 +4503,8 @@
}
public class AccessibilityNodeInfo implements android.os.Parcelable {
+ method public void addChild(@NonNull android.os.IBinder);
+ method public void setLeashedParent(@Nullable android.os.IBinder, int);
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
method public void writeToParcelNoRecycle(android.os.Parcel, int);
}
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index cfd77c2..62312d1 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -123,14 +123,17 @@
// ================================================================================
ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
- const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
- const sp<Throttler>& throttler)
+ const sp<Broadcaster>& broadcaster,
+ const sp<Looper>& handlerLooper,
+ const sp<Throttler>& throttler,
+ const vector<BringYourOwnSection*>& registeredSections)
:mLock(),
mWorkDirectory(workDirectory),
mBroadcaster(broadcaster),
mHandlerLooper(handlerLooper),
mBacklogDelay(DEFAULT_DELAY_NS),
mThrottler(throttler),
+ mRegisteredSections(registeredSections),
mBatch(new ReportBatch()) {
}
@@ -185,7 +188,7 @@
return;
}
- sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
+ sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections);
// Take the report, which might take a while. More requests might queue
// up while we're doing this, and we'll handle them in their next batch.
@@ -237,7 +240,7 @@
mWorkDirectory = new WorkDirectory();
mBroadcaster = new Broadcaster(mWorkDirectory);
mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
- mThrottler);
+ mThrottler, mRegisteredSections);
mBroadcaster->setHandler(mHandler);
}
@@ -327,6 +330,11 @@
incidentArgs.addSection(id);
}
}
+ for (const Section* section : mRegisteredSections) {
+ if (!section_requires_specific_mention(section->id)) {
+ incidentArgs.addSection(section->id);
+ }
+ }
// The ReportRequest takes ownership of the fd, so we need to dup it.
int fd = dup(stream.get());
@@ -339,6 +347,45 @@
return Status::ok();
}
+Status IncidentService::registerSection(const int id, const String16& name16,
+ const sp<IIncidentDumpCallback>& callback) {
+ const char* name = String8(name16).c_str();
+ ALOGI("Register section %d: %s", id, name);
+ if (callback == nullptr) {
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+ }
+ const uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ for (int i = 0; i < mRegisteredSections.size(); i++) {
+ if (mRegisteredSections.at(i)->id == id) {
+ if (mRegisteredSections.at(i)->uid != callingUid) {
+ ALOGW("Error registering section %d: calling uid does not match", id);
+ return Status::fromExceptionCode(Status::EX_SECURITY);
+ }
+ mRegisteredSections.at(i) = new BringYourOwnSection(id, name, callingUid, callback);
+ return Status::ok();
+ }
+ }
+ mRegisteredSections.push_back(new BringYourOwnSection(id, name, callingUid, callback));
+ return Status::ok();
+}
+
+Status IncidentService::unregisterSection(const int id) {
+ ALOGI("Unregister section %d", id);
+ uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) {
+ if ((*it)->id == id) {
+ if ((*it)->uid != callingUid) {
+ ALOGW("Error unregistering section %d: calling uid does not match", id);
+ return Status::fromExceptionCode(Status::EX_SECURITY);
+ }
+ mRegisteredSections.erase(it);
+ return Status::ok();
+ }
+ }
+ ALOGW("Section %d not found", id);
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+}
+
Status IncidentService::systemRunning() {
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index b2c7f23..49fc566 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -40,12 +40,16 @@
using namespace android::binder;
using namespace android::os;
+class BringYourOwnSection;
+
// ================================================================================
class ReportHandler : public MessageHandler {
public:
ReportHandler(const sp<WorkDirectory>& workDirectory,
- const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
- const sp<Throttler>& throttler);
+ const sp<Broadcaster>& broadcaster,
+ const sp<Looper>& handlerLooper,
+ const sp<Throttler>& throttler,
+ const vector<BringYourOwnSection*>& registeredSections);
virtual ~ReportHandler();
virtual void handleMessage(const Message& message);
@@ -79,6 +83,8 @@
nsecs_t mBacklogDelay;
sp<Throttler> mThrottler;
+ const vector<BringYourOwnSection*>& mRegisteredSections;
+
sp<ReportBatch> mBatch;
/**
@@ -126,6 +132,11 @@
virtual Status reportIncidentToDumpstate(unique_fd stream,
const sp<IIncidentReportStatusListener>& listener);
+ virtual Status registerSection(int id, const String16& name,
+ const sp<IIncidentDumpCallback>& callback);
+
+ virtual Status unregisterSection(int id);
+
virtual Status systemRunning();
virtual Status getIncidentReportList(const String16& pkg, const String16& cls,
@@ -149,6 +160,7 @@
sp<Broadcaster> mBroadcaster;
sp<ReportHandler> mHandler;
sp<Throttler> mThrottler;
+ vector<BringYourOwnSection*> mRegisteredSections;
/**
* Commands print out help.
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 02b6bbe..aa40f85 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -364,7 +364,6 @@
mSectionBufferSuccess = false;
mHadError = false;
mSectionErrors.clear();
-
}
void ReportWriter::setSectionStats(const FdBuffer& buffer) {
@@ -470,10 +469,13 @@
// ================================================================================
-Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch)
+Reporter::Reporter(const sp<WorkDirectory>& workDirectory,
+ const sp<ReportBatch>& batch,
+ const vector<BringYourOwnSection*>& registeredSections)
:mWorkDirectory(workDirectory),
mWriter(batch),
- mBatch(batch) {
+ mBatch(batch),
+ mRegisteredSections(registeredSections) {
}
Reporter::~Reporter() {
@@ -580,50 +582,15 @@
// For each of the report fields, see if we need it, and if so, execute the command
// and report to those that care that we're doing it.
for (const Section** section = SECTION_LIST; *section; section++) {
- const int sectionId = (*section)->id;
-
- // If nobody wants this section, skip it.
- if (!mBatch->containsSection(sectionId)) {
- continue;
- }
-
- ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string());
- IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections();
-
- // Notify listener of starting
- mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
- listener->onReportSectionStatus(
- sectionId, IIncidentReportStatusListener::STATUS_STARTING);
- });
-
- // Go get the data and write it into the file descriptors.
- mWriter.startSection(sectionId);
- err = (*section)->Execute(&mWriter);
- mWriter.endSection(sectionMetadata);
-
- // Sections returning errors are fatal. Most errors should not be fatal.
- if (err != NO_ERROR) {
- mWriter.error((*section), err, "Section failed. Stopping report.");
+ if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) {
goto DONE;
}
+ }
- // The returned max data size is used for throttling too many incident reports.
- (*reportByteSize) += sectionMetadata->report_size_bytes();
-
- // For any requests that failed during this section, remove them now. We do this
- // before calling back about section finished, so listeners do not erroniously get the
- // impression that the section succeeded. But we do it here instead of inside
- // writeSection so that the callback is done from a known context and not from the
- // bowels of a section, where changing the batch could cause odd errors.
- cancel_and_remove_failed_requests();
-
- // Notify listener of finishing
- mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
- listener->onReportSectionStatus(
- sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
- });
-
- ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string());
+ for (const Section* section : mRegisteredSections) {
+ if (execute_section(section, &metadata, reportByteSize) != NO_ERROR) {
+ goto DONE;
+ }
}
DONE:
@@ -681,6 +648,55 @@
ALOGI("Done taking incident report err=%s", strerror(-err));
}
+status_t Reporter::execute_section(const Section* section, IncidentMetadata* metadata,
+ size_t* reportByteSize) {
+ const int sectionId = section->id;
+
+ // If nobody wants this section, skip it.
+ if (!mBatch->containsSection(sectionId)) {
+ return NO_ERROR;
+ }
+
+ ALOGD("Start incident report section %d '%s'", sectionId, section->name.string());
+ IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections();
+
+ // Notify listener of starting
+ mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+ listener->onReportSectionStatus(
+ sectionId, IIncidentReportStatusListener::STATUS_STARTING);
+ });
+
+ // Go get the data and write it into the file descriptors.
+ mWriter.startSection(sectionId);
+ status_t err = section->Execute(&mWriter);
+ mWriter.endSection(sectionMetadata);
+
+ // Sections returning errors are fatal. Most errors should not be fatal.
+ if (err != NO_ERROR) {
+ mWriter.error(section, err, "Section failed. Stopping report.");
+ return err;
+ }
+
+ // The returned max data size is used for throttling too many incident reports.
+ (*reportByteSize) += sectionMetadata->report_size_bytes();
+
+ // For any requests that failed during this section, remove them now. We do this
+ // before calling back about section finished, so listeners do not erroniously get the
+ // impression that the section succeeded. But we do it here instead of inside
+ // writeSection so that the callback is done from a known context and not from the
+ // bowels of a section, where changing the batch could cause odd errors.
+ cancel_and_remove_failed_requests();
+
+ // Notify listener of finishing
+ mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+ listener->onReportSectionStatus(
+ sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
+ });
+
+ ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string());
+ return NO_ERROR;
+}
+
void Reporter::cancel_and_remove_failed_requests() {
// Handle a failure in the persisted file
if (mPersistedFile != nullptr) {
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index fb3961a..cbc8b13 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -21,6 +21,7 @@
#include "frameworks/base/core/proto/android/os/metadata.pb.h"
#include <android/content/ComponentName.h>
#include <android/os/IIncidentReportStatusListener.h>
+#include <android/os/IIncidentDumpCallback.h>
#include <android/os/IncidentReportArgs.h>
#include <android/util/protobuf.h>
@@ -39,6 +40,7 @@
using namespace android::content;
using namespace android::os;
+class BringYourOwnSection;
class Section;
// ================================================================================
@@ -122,7 +124,7 @@
void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func);
/**
- * Call func(request) for each file descriptor that has
+ * Call func(request) for each file descriptor.
*/
void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func);
@@ -251,7 +253,9 @@
// ================================================================================
class Reporter : public virtual RefBase {
public:
- Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch);
+ Reporter(const sp<WorkDirectory>& workDirectory,
+ const sp<ReportBatch>& batch,
+ const vector<BringYourOwnSection*>& registeredSections);
virtual ~Reporter();
@@ -263,6 +267,10 @@
ReportWriter mWriter;
sp<ReportBatch> mBatch;
sp<ReportFile> mPersistedFile;
+ const vector<BringYourOwnSection*>& mRegisteredSections;
+
+ status_t execute_section(const Section* section, IncidentMetadata* metadata,
+ size_t* reportByteSize);
void cancel_and_remove_failed_requests();
};
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index c9277a5..2229e1c 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -267,7 +267,7 @@
signal(SIGPIPE, sigpipe_handler);
WorkerThreadData* data = (WorkerThreadData*)cookie;
- status_t err = data->section->BlockingCall(data->pipe.writeFd().get());
+ status_t err = data->section->BlockingCall(data->pipe.writeFd());
{
unique_lock<mutex> lock(data->lock);
@@ -458,7 +458,7 @@
DumpsysSection::~DumpsysSection() {}
-status_t DumpsysSection::BlockingCall(int pipeWriteFd) const {
+status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const {
// checkService won't wait for the service to show up like getService will.
sp<IBinder> service = defaultServiceManager()->checkService(mService);
@@ -467,7 +467,7 @@
return NAME_NOT_FOUND;
}
- service->dump(pipeWriteFd, mArgs);
+ service->dump(pipeWriteFd.get(), mArgs);
return NO_ERROR;
}
@@ -526,7 +526,7 @@
return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
}
-status_t LogSection::BlockingCall(int pipeWriteFd) const {
+status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const {
// Open log buffer and getting logs since last retrieved time if any.
unique_ptr<logger_list, void (*)(logger_list*)> loggers(
gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end()
@@ -643,7 +643,7 @@
}
}
gLastLogsRetrieved[mLogID] = lastTimestamp;
- if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+ if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
return EPIPE;
}
@@ -660,7 +660,7 @@
TombstoneSection::~TombstoneSection() {}
-status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
+status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const {
std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
if (proc.get() == nullptr) {
ALOGE("opendir /proc failed: %s\n", strerror(errno));
@@ -768,7 +768,7 @@
dumpPipe.readFd().reset();
}
- if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+ if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
if (err != NO_ERROR) {
return EPIPE;
@@ -778,6 +778,22 @@
return err;
}
+// ================================================================================
+BringYourOwnSection::BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+ const sp<IIncidentDumpCallback>& callback)
+ : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), uid(callingUid), mCallback(callback) {
+ name = "registered ";
+ name += customName;
+}
+
+BringYourOwnSection::~BringYourOwnSection() {}
+
+status_t BringYourOwnSection::BlockingCall(unique_fd& pipeWriteFd) const {
+ android::os::ParcelFileDescriptor pfd(std::move(pipeWriteFd));
+ mCallback->onDumpSection(pfd);
+ return NO_ERROR;
+}
+
} // namespace incidentd
} // namespace os
} // namespace android
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index fcf12f7..0bb9da9 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -23,6 +23,8 @@
#include <stdarg.h>
#include <map>
+#include <android/os/IIncidentDumpCallback.h>
+
#include <utils/String16.h>
#include <utils/String8.h>
#include <utils/Vector.h>
@@ -89,7 +91,7 @@
virtual status_t Execute(ReportWriter* writer) const;
- virtual status_t BlockingCall(int pipeWriteFd) const = 0;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const = 0;
};
/**
@@ -117,7 +119,7 @@
DumpsysSection(int id, const char* service, ...);
virtual ~DumpsysSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
String16 mService;
@@ -132,7 +134,7 @@
SystemPropertyDumpsysSection(int id, const char* service, ...);
virtual ~SystemPropertyDumpsysSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
String16 mService;
@@ -153,7 +155,7 @@
LogSection(int id, const char* logID, ...);
virtual ~LogSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
log_id_t mLogID;
@@ -169,12 +171,29 @@
TombstoneSection(int id, const char* type, int64_t timeoutMs = 120000 /* 2 minutes */);
virtual ~TombstoneSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
std::string mType;
};
+/**
+ * Section that gets data from a registered dump callback.
+ */
+class BringYourOwnSection : public WorkerThreadSection {
+public:
+ const uid_t uid;
+
+ BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+ const sp<IIncidentDumpCallback>& callback);
+ virtual ~BringYourOwnSection();
+
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
+
+private:
+ const sp<IIncidentDumpCallback>& mCallback;
+};
+
/**
* These sections will not be generated when doing an 'all' report, either
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 6033655..c2ee6dc 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -103,6 +103,10 @@
runSetVirtualDisk();
} else if ("set-isolated-storage".equals(op)) {
runIsolatedStorage();
+ } else if ("start-checkpoint".equals(op)) {
+ runStartCheckpoint();
+ } else if ("supports-checkpoint".equals(op)) {
+ runSupportsCheckpoint();
} else {
throw new IllegalArgumentException();
}
@@ -313,6 +317,27 @@
}
}
+ private void runStartCheckpoint() throws RemoteException {
+ final String numRetriesString = nextArg();
+ if (numRetriesString == null) {
+ throw new IllegalArgumentException("Expected <num-retries>");
+ }
+ int numRetries;
+ try {
+ numRetries = Integer.parseInt(numRetriesString);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("<num-retries> must be a positive integer");
+ }
+ if (numRetries <= 0) {
+ throw new IllegalArgumentException("<num-retries> must be a positive integer");
+ }
+ mSm.startCheckpoint(numRetries);
+ }
+
+ private void runSupportsCheckpoint() throws RemoteException {
+ System.out.println(mSm.supportsCheckpoint());
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -344,6 +369,10 @@
System.err.println("");
System.err.println(" sm set-isolated-storage [on|off|default]");
System.err.println("");
+ System.err.println(" sm start-checkpoint <num-retries>");
+ System.err.println("");
+ System.err.println(" sm supports-checkpoint");
+ System.err.println("");
return 1;
}
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1441d48..cbece78 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -338,6 +338,7 @@
BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240;
BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
BootTimeEventErrorCode boot_time_event_error_code_reported = 242;
+ UserspaceRebootReported userspace_reboot_reported = 243;
}
// Pulled events will start at field 10000.
@@ -7904,4 +7905,42 @@
optional SamplingStrategy sampling_strategy = 6;
}
-
+/*
+ * Logs userspace reboot outcome and duration.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/server/BootReceiver.java
+ */
+message UserspaceRebootReported {
+ // Possible outcomes of userspace reboot.
+ enum Outcome {
+ // Default value in case platform failed to determine the outcome.
+ OUTCOME_UNKNOWN = 0;
+ // Userspace reboot succeeded (i.e. boot completed without a fall back to hard reboot).
+ SUCCESS = 1;
+ // Userspace reboot shutdown sequence was aborted.
+ FAILED_SHUTDOWN_SEQUENCE_ABORTED = 2;
+ // Remounting userdata into checkpointing mode failed.
+ FAILED_USERDATA_REMOUNT = 3;
+ // Device didn't finish booting before timeout and userspace reboot watchdog issued a hard
+ // reboot.
+ FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED = 4;
+ }
+ // Outcome of userspace reboot. Always set.
+ optional Outcome outcome = 1;
+ // Duration of userspace reboot in case it has a successful outcome.
+ // Duration is measured as time between userspace reboot was initiated and until boot completed
+ // (e.g. sys.boot_completed=1).
+ optional int64 duration_millis = 2;
+ // State of primary user's (user0) credential encryption storage.
+ enum UserEncryptionState {
+ // Default value.
+ USER_ENCRYPTION_STATE_UNKNOWN = 0;
+ // Credential encrypted storage is unlocked.
+ UNLOCKED = 1;
+ // Credential encrypted storage is locked.
+ LOCKED = 2;
+ }
+ // State of primary user's encryption storage at the moment boot completed. Always set.
+ optional UserEncryptionState user_encryption_state = 3;
+}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 731afe8..b5920cb 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -60,10 +60,6 @@
std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
- // kernel_wakelock
- {{.atomTag = android::util::KERNEL_WAKELOCK},
- {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
-
// subsystem_sleep_state
{{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE},
{.puller = new SubsystemSleepStatePuller()}},
@@ -72,37 +68,6 @@
{{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT},
{.puller = new PowerStatsPuller()}},
- // cpu_time_per_freq
- {{.atomTag = android::util::CPU_TIME_PER_FREQ},
- {.additiveFields = {3},
- .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
-
- // cpu_time_per_uid
- {{.atomTag = android::util::CPU_TIME_PER_UID},
- {.additiveFields = {2, 3},
- .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}},
-
- // cpu_time_per_uid_freq
- // the throttling is 3sec, handled in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- {{.atomTag = android::util::CPU_TIME_PER_UID_FREQ},
- {.additiveFields = {4},
- .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}},
-
- // cpu_active_time
- // the throttling is 3sec, handled in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- {{.atomTag = android::util::CPU_ACTIVE_TIME},
- {.additiveFields = {2},
- .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}},
-
- // cpu_cluster_time
- // the throttling is 3sec, handled in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- {{.atomTag = android::util::CPU_CLUSTER_TIME},
- {.additiveFields = {3},
- .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}},
-
// wifi_activity_energy_info
{{.atomTag = android::util::WIFI_ACTIVITY_INFO},
{.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}},
@@ -118,10 +83,6 @@
.pullTimeoutNs = NS_PER_SEC / 2,
}},
- // system_uptime
- {{.atomTag = android::util::SYSTEM_UPTIME},
- {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
-
// remaining_battery_capacity
{{.atomTag = android::util::REMAINING_BATTERY_CAPACITY},
{.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}},
@@ -256,10 +217,6 @@
{.additiveFields = {1, 2, 3, 4},
.puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
- // BuildInformation.
- {{.atomTag = android::util::BUILD_INFORMATION},
- {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
-
// RoleHolder.
{{.atomTag = android::util::ROLE_HOLDER},
{.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}},
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 7b651df..e0aecce 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -617,19 +617,6 @@
void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
EXPECT_EQ(value.field(), atomId);
- // Attribution field.
- EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
- // Uid only.
- EXPECT_EQ(value.value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value_size(), 1);
- EXPECT_EQ(value.value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value(0).field(), 1);
- EXPECT_EQ(value.value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value(0).value_int(), uid);
-}
-
-void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) {
- EXPECT_EQ(value.field(), atomId);
EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
// Attribution field.
EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index e46840c..0bd8ce6 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,6 +17,7 @@
package android.accessibilityservice;
import android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,12 +27,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.graphics.Region;
+import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -48,10 +52,13 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Accessibility services should only be used to assist users with disabilities in using
@@ -483,6 +490,9 @@
private FingerprintGestureController mFingerprintGestureController;
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT = "screenshot";
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -1761,6 +1771,51 @@
}
/**
+ * Takes a screenshot of the specified display and returns it by {@link Bitmap.Config#HARDWARE}
+ * format.
+ * <p>
+ * <strong>Note:</strong> In order to take screenshot your service has
+ * to declare the capability to take screenshot by setting the
+ * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Besides, This API is only supported for default display now
+ * {@link Display#DEFAULT_DISPLAY}.
+ * </p>
+ *
+ * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when the taking screenshot is done.
+ *
+ * @return {@code true} if the taking screenshot accepted, {@code false} if not.
+ */
+ public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Bitmap> callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mConnectionId);
+ if (connection == null) {
+ return false;
+ }
+ try {
+ connection.takeScreenshotWithCallback(displayId, new RemoteCallback((result) -> {
+ final Bitmap screenshot = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.accept(screenshot));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }));
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ return true;
+ }
+
+ /**
* Implement to return the implementation of the internal accessibility
* service interface.
*/
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 5e2c1fa..12f2c3b 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -86,6 +86,7 @@
* @attr ref android.R.styleable#AccessibilityService_settingsActivity
* @attr ref android.R.styleable#AccessibilityService_nonInteractiveUiTimeout
* @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout
+ * @attr ref android.R.styleable#AccessibilityService_canTakeScreenshot
* @see AccessibilityService
* @see android.view.accessibility.AccessibilityEvent
* @see android.view.accessibility.AccessibilityManager
@@ -136,6 +137,12 @@
*/
public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 0x00000040;
+ /**
+ * Capability: This accessibility service can take screenshot.
+ * @see android.R.styleable#AccessibilityService_canTakeScreenshot
+ */
+ public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 0x00000080;
+
private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos;
/**
@@ -625,6 +632,10 @@
.AccessibilityService_canRequestFingerprintGestures, false)) {
mCapabilities |= CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES;
}
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canTakeScreenshot, false)) {
+ mCapabilities |= CAPABILITY_CAN_TAKE_SCREENSHOT;
+ }
TypedValue peekedValue = asAttributes.peekValue(
com.android.internal.R.styleable.AccessibilityService_description);
if (peekedValue != null) {
@@ -794,6 +805,7 @@
* @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
* @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
* @see #CAPABILITY_CAN_PERFORM_GESTURES
+ * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
*/
public int getCapabilities() {
return mCapabilities;
@@ -810,6 +822,7 @@
* @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
* @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
* @see #CAPABILITY_CAN_PERFORM_GESTURES
+ * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
*
* @hide
*/
@@ -1253,6 +1266,8 @@
return "CAPABILITY_CAN_PERFORM_GESTURES";
case CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES:
return "CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES";
+ case CAPABILITY_CAN_TAKE_SCREENSHOT:
+ return "CAPABILITY_CAN_TAKE_SCREENSHOT";
default:
return "UNKNOWN";
}
@@ -1314,6 +1329,10 @@
new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
R.string.capability_title_canPerformGestures,
R.string.capability_desc_canPerformGestures));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_TAKE_SCREENSHOT,
+ new CapabilityInfo(CAPABILITY_CAN_TAKE_SCREENSHOT,
+ R.string.capability_title_canTakeScreenshot,
+ R.string.capability_desc_canTakeScreenshot));
if ((context == null) || fingerprintAvailable(context)) {
sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
new CapabilityInfo(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 656f87f..4ea5c62 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,8 +18,10 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.graphics.Region;
import android.os.Bundle;
+import android.os.RemoteCallback;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -102,4 +104,10 @@
boolean isFingerprintGestureDetectionAvailable();
IBinder getOverlayWindowToken(int displayid);
+
+ int getWindowIdForLeashToken(IBinder token);
+
+ Bitmap takeScreenshot(int displayId);
+
+ void takeScreenshotWithCallback(int displayId, in RemoteCallback callback);
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 1c6a561..d39a8c4 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -133,6 +133,10 @@
// Default flags to use with PackageManager when no flags are given.
private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
+ // Name of the resource which provides background permission button string
+ public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS =
+ "app_permission_button_allow_always";
+
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -807,6 +811,26 @@
}
@Override
+ public CharSequence getBackgroundPermissionButtonLabel() {
+ try {
+
+ String permissionController = getPermissionControllerPackageName();
+ Context context =
+ mContext.createPackageContext(permissionController, 0);
+
+ int textId = context.getResources().getIdentifier(APP_PERMISSION_BUTTON_ALLOW_ALWAYS,
+ "string", "com.android.permissioncontroller");
+// permissionController); STOPSHIP b/147434671
+ if (textId != 0) {
+ return context.getText(textId);
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Permission controller not found.", e);
+ }
+ return "";
+ }
+
+ @Override
public int checkSignatures(String pkg1, String pkg2) {
try {
return mPM.checkSignatures(pkg1, pkg2);
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 5185941..1f323c3 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -41,8 +41,7 @@
// Don't need to worry about synchronization
private static DisabledWallpaperManager sInstance;
- // TODO(b/138939803): STOPSHIP changed to false and/or remove it
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
@NonNull
static DisabledWallpaperManager getInstance() {
@@ -53,7 +52,6 @@
}
private DisabledWallpaperManager() {
- super(null, null, null);
}
@Override
@@ -66,10 +64,6 @@
return false;
}
- // TODO(b/138939803): STOPSHIP methods below should not be necessary,
- // callers should check if isWallpaperSupported(), consider removing them to keep this class
- // simpler
-
private static <T> T unsupported() {
if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
return null;
@@ -343,4 +337,9 @@
public boolean isWallpaperBackupEligible(int which) {
return unsupportedBoolean();
}
+
+ @Override
+ public boolean wallpaperSupportsWcg(int which) {
+ return unsupportedBoolean();
+ }
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index bd6baec..580382e 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -355,6 +355,12 @@
in Bundle options, int userId);
@UnsupportedAppUsage
int stopUser(int userid, boolean force, in IStopUserCallback callback);
+ /**
+ * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)}
+ * for details.
+ */
+ int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback);
+
@UnsupportedAppUsage
void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name);
void unregisterUserSwitchObserver(in IUserSwitchObserver observer);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index fcdb7cc..7af7a4a 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -94,7 +94,7 @@
void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group);
void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId);
- NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, String conversationId);
+ NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId);
void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId);
NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, String channelId);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 8c3180b..80ba464 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -39,7 +39,6 @@
boolean injectInputEvent(in InputEvent event, boolean sync);
void syncInputTransactions();
boolean setRotation(int rotation);
- Bitmap takeScreenshot(in Rect crop, int rotation);
boolean clearWindowContentFrameStats(int windowId);
WindowContentFrameStats getWindowContentFrameStats(int windowId);
void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 775b1d1..70262b0 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -251,7 +251,7 @@
}
private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
- if (appInfo.appComponentFactory != null && cl != null) {
+ if (mIncludeCode && appInfo.appComponentFactory != null && cl != null) {
try {
return (AppComponentFactory)
cl.loadClass(appInfo.appComponentFactory).newInstance();
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index a33c2c1..bdc7b99 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -31,6 +31,7 @@
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
@@ -71,7 +72,7 @@
* Conversation id to use for apps that aren't providing them yet.
* @hide
*/
- public static final String PLACEHOLDER_CONVERSATION_ID = "placeholder_id";
+ public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
/**
* The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
@@ -826,8 +827,8 @@
setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
- setConversationId(parser.getAttributeValue(ATT_PARENT_CHANNEL, null),
- parser.getAttributeValue(ATT_CONVERSATION_ID, null));
+ setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
+ parser.getAttributeValue(null, ATT_CONVERSATION_ID));
}
@Nullable
@@ -1175,7 +1176,7 @@
+ ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
+ ", mOriginalImp=" + mOriginalImportance
+ ", mParent=" + mParentId
- + ", mConversationId" + mConversationId;
+ + ", mConversationId=" + mConversationId;
}
/** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 61c1098..1a8e15c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -855,7 +855,8 @@
INotificationManager service = getService();
try {
return service.getConversationNotificationChannel(mContext.getOpPackageName(),
- mContext.getUserId(), mContext.getPackageName(), channelId, conversationId);
+ mContext.getUserId(), mContext.getPackageName(), channelId, true,
+ conversationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a9be9ea..7b21f12 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -54,6 +54,7 @@
import android.content.integrity.IAppIntegrityManager;
import android.content.om.IOverlayManager;
import android.content.om.OverlayManager;
+import android.content.pm.ApplicationInfo;
import android.content.pm.CrossProfileApps;
import android.content.pm.DataLoaderManager;
import android.content.pm.ICrossProfileApps;
@@ -693,20 +694,20 @@
throws ServiceNotFoundException {
final IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
if (b == null) {
- // There are 2 reason service can be null:
- // 1.Device doesn't support it - that's fine
- // 2.App is running on instant mode - should fail
- final boolean enabled = Resources.getSystem()
- .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
- if (!enabled) {
- // Life moves on...
- return DisabledWallpaperManager.getInstance();
- }
- if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+ ApplicationInfo appInfo = ctx.getApplicationInfo();
+ if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P
+ && appInfo.isInstantApp()) {
// Instant app
throw new ServiceNotFoundException(Context.WALLPAPER_SERVICE);
}
+ final boolean enabled = Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
+ if (!enabled) {
+ // Device doesn't support wallpaper, return a limited manager
+ return DisabledWallpaperManager.getInstance();
+ }
// Bad state - WallpaperManager methods will throw exception
+ Log.e(TAG, "No wallpaper service");
}
IWallpaperManager service = IWallpaperManager.Stub.asInterface(b);
return new WallpaperManager(service, ctx.getOuterContext(),
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 18a3e6e..2579bd1 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -27,10 +27,7 @@
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.graphics.Region;
-import android.hardware.display.DisplayManagerGlobal;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
@@ -831,39 +828,20 @@
}
/**
- * Takes a screenshot.
+ * Takes a screenshot of the default display and returns it by {@link Bitmap.Config#HARDWARE}
+ * format.
*
* @return The screenshot bitmap on success, null otherwise.
*/
public Bitmap takeScreenshot() {
+ final int connectionId;
synchronized (mLock) {
throwIfNotConnectedLocked();
+ connectionId = mConnectionId;
}
- Display display = DisplayManagerGlobal.getInstance()
- .getRealDisplay(Display.DEFAULT_DISPLAY);
- Point displaySize = new Point();
- display.getRealSize(displaySize);
-
- int rotation = display.getRotation();
-
- // Take the screenshot
- Bitmap screenShot = null;
- try {
- // Calling out without a lock held.
- screenShot = mUiAutomationConnection.takeScreenshot(
- new Rect(0, 0, displaySize.x, displaySize.y), rotation);
- if (screenShot == null) {
- return null;
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while taking screnshot!", re);
- return null;
- }
-
- // Optimization
- screenShot.setHasAlpha(false);
-
- return screenShot;
+ // Calling out without a lock held.
+ return AccessibilityInteractionClient.getInstance()
+ .takeScreenshot(connectionId, Display.DEFAULT_DISPLAY);
}
/**
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 82e9881..4fb9743 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -21,8 +21,6 @@
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.IBinder;
@@ -180,23 +178,6 @@
}
@Override
- public Bitmap takeScreenshot(Rect crop, int rotation) {
- synchronized (mLock) {
- throwIfCalledByNotTrustedUidLocked();
- throwIfShutdownLocked();
- throwIfNotConnectedLocked();
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- int width = crop.width();
- int height = crop.height();
- return SurfaceControl.screenshot(crop, width, height, rotation);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
@@ -449,7 +430,8 @@
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
- | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
+ | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+ | AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
try {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 2507991..9ad3d38 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -543,6 +543,13 @@
mCmProxy = new ColorManagementProxy(context);
}
+ // no-op constructor called just by DisabledWallpaperManager
+ /*package*/ WallpaperManager() {
+ mContext = null;
+ mCmProxy = null;
+ mWcgEnabled = false;
+ }
+
/**
* Retrieve a WallpaperManager associated with the given Context.
*/
@@ -568,8 +575,10 @@
*
* @see Configuration#isScreenWideColorGamut()
* @return True if wcg should be enabled for this device.
+ * @hide
*/
- private boolean shouldEnableWideColorGamut() {
+ @TestApi
+ public boolean shouldEnableWideColorGamut() {
return mWcgEnabled;
}
@@ -877,6 +886,7 @@
* @see #FLAG_SYSTEM
* @hide
*/
+ @TestApi
@RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
public boolean wallpaperSupportsWcg(int which) {
if (!shouldEnableWideColorGamut()) {
@@ -893,6 +903,8 @@
*
* @hide
*/
+ @TestApi
+ @Nullable
@UnsupportedAppUsage
public Bitmap getBitmap() {
return getBitmap(false);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index caaa686..c584575 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7418,7 +7418,9 @@
* @param userHandle The user for whom to check the caller-id permission
* @hide
*/
- public boolean getBluetoothContactSharingDisabled(UserHandle userHandle) {
+ @SystemApi
+ @RequiresPermission(permission.INTERACT_ACROSS_USERS)
+ public boolean getBluetoothContactSharingDisabled(@NonNull UserHandle userHandle) {
if (mService != null) {
try {
return mService.getBluetoothContactSharingDisabledForUser(userHandle
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index c8f2ff3..567eb4a 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -85,6 +85,15 @@
public static final int FLAG_NON_INCREMENTAL = 1 << 2;
/**
+ * For key value backup, indicates that the backup contains no new data since the last backup
+ * attempt completed without any errors. The transport should use this to record that
+ * a successful backup attempt has been completed but no backup data has been changed.
+ *
+ * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
+ */
+ public static final int FLAG_DATA_NOT_CHANGED = 1 << 3;
+
+ /**
* Used as a boolean extra in the binding intent of transports. We pass {@code true} to
* notify transports that the current connection is used for registering the transport.
*/
@@ -302,7 +311,8 @@
* BackupService.doBackup() method. This may be a pipe rather than a file on
* persistent media, so it may not be seekable.
* @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link
- * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0.
+ * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL},
+ * {@link BackupTransport#FLAG_DATA_NOT_CHANGED},or 0.
* @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
* {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
* specific package, but allow others to proceed),
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index d840c1c..6ab880d 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -451,21 +451,7 @@
/** @hide */
public Event(Event orig) {
- mPackage = orig.mPackage;
- mClass = orig.mClass;
- mInstanceId = orig.mInstanceId;
- mTaskRootPackage = orig.mTaskRootPackage;
- mTaskRootClass = orig.mTaskRootClass;
- mTimeStamp = orig.mTimeStamp;
- mEventType = orig.mEventType;
- mConfiguration = orig.mConfiguration;
- mShortcutId = orig.mShortcutId;
- mAction = orig.mAction;
- mContentType = orig.mContentType;
- mContentAnnotations = orig.mContentAnnotations;
- mFlags = orig.mFlags;
- mBucketAndReason = orig.mBucketAndReason;
- mNotificationChannelId = orig.mNotificationChannelId;
+ copyFrom(orig);
}
/**
@@ -622,6 +608,24 @@
// which instant apps can't use anyway, so there's no need to hide them.
return ret;
}
+
+ private void copyFrom(Event orig) {
+ mPackage = orig.mPackage;
+ mClass = orig.mClass;
+ mInstanceId = orig.mInstanceId;
+ mTaskRootPackage = orig.mTaskRootPackage;
+ mTaskRootClass = orig.mTaskRootClass;
+ mTimeStamp = orig.mTimeStamp;
+ mEventType = orig.mEventType;
+ mConfiguration = orig.mConfiguration;
+ mShortcutId = orig.mShortcutId;
+ mAction = orig.mAction;
+ mContentType = orig.mContentType;
+ mContentAnnotations = orig.mContentAnnotations;
+ mFlags = orig.mFlags;
+ mBucketAndReason = orig.mBucketAndReason;
+ mNotificationChannelId = orig.mNotificationChannelId;
+ }
}
// Only used when creating the resulting events. Not used for reading/unparceling.
@@ -725,10 +729,14 @@
return false;
}
- readEventFromParcel(mParcel, eventOut);
+ if (mParcel != null) {
+ readEventFromParcel(mParcel, eventOut);
+ } else {
+ eventOut.copyFrom(mEventsToWrite.get(mIndex));
+ }
mIndex++;
- if (mIndex >= mEventCount) {
+ if (mIndex >= mEventCount && mParcel != null) {
mParcel.recycle();
mParcel = null;
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9ef9574..3860508 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1825,7 +1825,7 @@
* @throws ActivityNotFoundException
* @hide
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
@SystemApi
public void startActivityAsUser(@RequiresPermission @NonNull Intent intent,
@NonNull UserHandle user) {
@@ -1873,7 +1873,7 @@
* @throws ActivityNotFoundException
* @hide
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
@UnsupportedAppUsage
public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
UserHandle userId) {
@@ -1979,7 +1979,7 @@
* @see #startActivities(Intent[])
* @see PackageManager#resolveActivity
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java
index 4d235f1..c0fdcc9 100644
--- a/core/java/android/content/pm/InstallSourceInfo.java
+++ b/core/java/android/content/pm/InstallSourceInfo.java
@@ -29,32 +29,39 @@
@Nullable private final String mInitiatingPackageName;
+ @Nullable private final SigningInfo mInitiatingPackageSigningInfo;
+
@Nullable private final String mOriginatingPackageName;
@Nullable private final String mInstallingPackageName;
/** @hide */
public InstallSourceInfo(@Nullable String initiatingPackageName,
+ @Nullable SigningInfo initiatingPackageSigningInfo,
@Nullable String originatingPackageName, @Nullable String installingPackageName) {
- this.mInitiatingPackageName = initiatingPackageName;
- this.mOriginatingPackageName = originatingPackageName;
- this.mInstallingPackageName = installingPackageName;
+ mInitiatingPackageName = initiatingPackageName;
+ mInitiatingPackageSigningInfo = initiatingPackageSigningInfo;
+ mOriginatingPackageName = originatingPackageName;
+ mInstallingPackageName = installingPackageName;
}
@Override
public int describeContents() {
- return 0;
+ return mInitiatingPackageSigningInfo == null
+ ? 0 : mInitiatingPackageSigningInfo.describeContents();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mInitiatingPackageName);
+ dest.writeParcelable(mInitiatingPackageSigningInfo, flags);
dest.writeString(mOriginatingPackageName);
dest.writeString(mInstallingPackageName);
}
private InstallSourceInfo(Parcel source) {
mInitiatingPackageName = source.readString();
+ mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader());
mOriginatingPackageName = source.readString();
mInstallingPackageName = source.readString();
}
@@ -66,6 +73,14 @@
}
/**
+ * Information about the signing certificates used to sign the initiating package, if available.
+ */
+ @Nullable
+ public SigningInfo getInitiatingPackageSigningInfo() {
+ return mInitiatingPackageSigningInfo;
+ }
+
+ /**
* The name of the package on behalf of which the initiating package requested the installation,
* or null if not available.
* <p>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4bfc40e..5ca50c2 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4389,6 +4389,18 @@
public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permName);
/**
+ * Gets the string that is displayed on the button which corresponds to granting background
+ * location in settings. The intended use for this is to help apps instruct users how to
+ * grant a background permission by providing the string that users will see.
+ *
+ * @return the string shown on the button for granting background location
+ */
+ @NonNull
+ public CharSequence getBackgroundPermissionButtonLabel() {
+ return "";
+ }
+
+ /**
* Returns an {@link android.content.Intent} suitable for passing to
* {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
* which prompts the user to grant permissions to this application.
@@ -6053,6 +6065,11 @@
* If the calling application does not hold the INSTALL_PACKAGES permission then
* the result will always return {@code null} from
* {@link InstallSourceInfo#getOriginatingPackageName()}.
+ * <p>
+ * If the package that requested the install has been uninstalled, then information about it
+ * will only be returned from {@link InstallSourceInfo#getInitiatingPackageName()} and
+ * {@link InstallSourceInfo#getInitiatingPackageSigningInfo()} if the calling package is
+ * requesting its own install information and is not an instant app.
*
* @param packageName The name of the package to query
* @throws NameNotFoundException if the given package name is not installed
diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
index 698876b..11cf2d6 100644
--- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java
+++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
@@ -18,6 +18,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
import android.os.CancellationSignal;
import android.os.Parcelable;
@@ -119,6 +120,7 @@
class AuthenticationResult {
private Identifier mIdentifier;
private CryptoObject mCryptoObject;
+ private @AuthenticationResultType int mAuthenticationType;
private int mUserId;
/**
@@ -129,27 +131,41 @@
/**
* Authentication result
* @param crypto
+ * @param authenticationType
* @param identifier
* @param userId
* @hide
*/
- public AuthenticationResult(CryptoObject crypto, Identifier identifier,
+ public AuthenticationResult(CryptoObject crypto,
+ @AuthenticationResultType int authenticationType, Identifier identifier,
int userId) {
mCryptoObject = crypto;
+ mAuthenticationType = authenticationType;
mIdentifier = identifier;
mUserId = userId;
}
/**
- * Obtain the crypto object associated with this transaction
- * @return crypto object provided to {@link BiometricAuthenticator#authenticate(
- * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)}
+ * Provides the crypto object associated with this transaction.
+ * @return The crypto object provided to {@link BiometricPrompt#authenticate(
+ * BiometricPrompt.CryptoObject, CancellationSignal, Executor,
+ * BiometricPrompt.AuthenticationCallback)}
*/
public CryptoObject getCryptoObject() {
return mCryptoObject;
}
/**
+ * Provides the type of authentication (e.g. device credential or biometric) that was
+ * requested from and successfully provided by the user.
+ *
+ * @return An integer value representing the authentication method used.
+ */
+ public @AuthenticationResultType int getAuthenticationType() {
+ return mAuthenticationType;
+ }
+
+ /**
* Obtain the biometric identifier associated with this operation. Applications are strongly
* discouraged from associating specific identifiers with specific applications or
* operations.
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index cb8fc8b..a695ce8 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -21,6 +21,7 @@
import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -40,6 +41,8 @@
import com.android.internal.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.Signature;
import java.util.concurrent.Executor;
@@ -397,9 +400,11 @@
new IBiometricServiceReceiver.Stub() {
@Override
- public void onAuthenticationSucceeded() throws RemoteException {
+ public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType)
+ throws RemoteException {
mExecutor.execute(() -> {
- final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
+ final AuthenticationResult result =
+ new AuthenticationResult(mCryptoObject, authenticationType);
mAuthenticationCallback.onAuthenticationSucceeded(result);
});
}
@@ -576,28 +581,62 @@
}
/**
- * Container for callback data from {@link #authenticate( CancellationSignal, Executor,
+ * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
+ * entering their device PIN, pattern, or password.
+ */
+ public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1;
+
+ /**
+ * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
+ * presenting some form of biometric (e.g. fingerprint or face).
+ */
+ public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2;
+
+ /**
+ * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC})
+ public @interface AuthenticationResultType {
+ }
+
+ /**
+ * Container for callback data from {@link #authenticate(CancellationSignal, Executor,
* AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor,
- * AuthenticationCallback)}
+ * AuthenticationCallback)}.
*/
public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult {
/**
* Authentication result
* @param crypto
+ * @param authenticationType
* @hide
*/
- public AuthenticationResult(CryptoObject crypto) {
+ public AuthenticationResult(CryptoObject crypto,
+ @AuthenticationResultType int authenticationType) {
// Identifier and userId is not used for BiometricPrompt.
- super(crypto, null /* identifier */, 0 /* userId */);
+ super(crypto, authenticationType, null /* identifier */, 0 /* userId */);
}
+
/**
- * Obtain the crypto object associated with this transaction
- * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal,
- * Executor, AuthenticationCallback)}
+ * Provides the crypto object associated with this transaction.
+ * @return The crypto object provided to {@link #authenticate(CryptoObject,
+ * CancellationSignal, Executor, AuthenticationCallback)}
*/
public CryptoObject getCryptoObject() {
return (CryptoObject) super.getCryptoObject();
}
+
+ /**
+ * Provides the type of authentication (e.g. device credential or biometric) that was
+ * requested from and successfully provided by the user.
+ *
+ * @return An integer value representing the authentication method used.
+ */
+ public @AuthenticationResultType int getAuthenticationType() {
+ return super.getAuthenticationType();
+ }
}
/**
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
index c960049..1d43aa6 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
@@ -20,8 +20,8 @@
* @hide
*/
oneway interface IBiometricServiceReceiver {
- // Notify BiometricPrompt that authentication was successful
- void onAuthenticationSucceeded();
+ // Notify BiometricPrompt that authentication was successful.
+ void onAuthenticationSucceeded(int authenticationType);
// Noties that authentication failed.
void onAuthenticationFailed();
// Notify BiometricPrompt that an error has occurred.
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index a5e0f04..456ebf2 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -557,9 +557,9 @@
dest.writeInt(vendorUuid.toString().length());
dest.writeString(vendorUuid.toString());
}
+ dest.writeInt(version);
dest.writeBlob(data);
dest.writeTypedArray(keyphrases, flags);
- dest.writeInt(version);
}
@Override
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 7291419..03d29a3 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -370,6 +370,12 @@
}
@Override
+ public synchronized void onModuleDied() {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
+ mHandler.sendMessage(m);
+ }
+
+ @Override
public synchronized void binderDied() {
Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
mHandler.sendMessage(m);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c523f65..8ba3131 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -662,7 +662,7 @@
* {@hide}
*/
@Deprecated
- @UnsupportedAppUsage
+ @SystemApi
public static final int TYPE_WIFI_P2P = 13;
/**
@@ -3622,14 +3622,26 @@
/**
* Helper function to request a network with a particular legacy type.
*
- * This is temporarily public @hide so it can be called by system code that uses the
- * NetworkRequest API to request networks but relies on CONNECTIVITY_ACTION broadcasts for
- * instead network notifications.
+ * @deprecated This is temporarily public for tethering to backwards compatibility that uses
+ * the NetworkRequest API to request networks with legacy type and relies on
+ * CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} instead.
*
* TODO: update said system code to rely on NetworkCallbacks and make this method private.
+
+ * @param request {@link NetworkRequest} describing this request.
+ * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+ * the callback must not be shared - it uniquely specifies this request.
+ * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
+ * before {@link NetworkCallback#onUnavailable()} is called. The timeout must
+ * be a positive value (i.e. >0).
+ * @param legacyType to specify the network type(#TYPE_*).
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
*
* @hide
*/
+ @SystemApi
+ @Deprecated
public void requestNetwork(@NonNull NetworkRequest request,
@NonNull NetworkCallback networkCallback, int timeoutMs, int legacyType,
@NonNull Handler handler) {
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 385cb1d..72a6b39 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -57,9 +57,6 @@
@UnsupportedAppUsage
boolean getRestrictBackground();
- /** Callback used to change internal state on tethering */
- void onTetheringChanged(String iface, boolean tethering);
-
/** Gets the restrict background status based on the caller's UID:
1 - disabled
2 - whitelisted
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 2792c56..be8e561 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -21,6 +21,8 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.LinkPropertiesUtils;
+import android.net.util.LinkPropertiesUtils.CompareResult;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -84,36 +86,6 @@
/**
* @hide
*/
- public static class CompareResult<T> {
- public final List<T> removed = new ArrayList<>();
- public final List<T> added = new ArrayList<>();
-
- public CompareResult() {}
-
- public CompareResult(Collection<T> oldItems, Collection<T> newItems) {
- if (oldItems != null) {
- removed.addAll(oldItems);
- }
- if (newItems != null) {
- for (T newItem : newItems) {
- if (!removed.remove(newItem)) {
- added.add(newItem);
- }
- }
- }
- }
-
- @Override
- public String toString() {
- return "removed=[" + TextUtils.join(",", removed)
- + "] added=[" + TextUtils.join(",", added)
- + "]";
- }
- }
-
- /**
- * @hide
- */
@UnsupportedAppUsage(implicitMember =
"values()[Landroid/net/LinkProperties$ProvisioningChange;")
public enum ProvisioningChange {
@@ -1295,7 +1267,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalInterfaceName(@NonNull LinkProperties target) {
- return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
+ return LinkPropertiesUtils.isIdenticalInterfaceName(target, this);
}
/**
@@ -1318,10 +1290,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalAddresses(@NonNull LinkProperties target) {
- Collection<InetAddress> targetAddresses = target.getAddresses();
- Collection<InetAddress> sourceAddresses = getAddresses();
- return (sourceAddresses.size() == targetAddresses.size()) ?
- sourceAddresses.containsAll(targetAddresses) : false;
+ return LinkPropertiesUtils.isIdenticalAddresses(target, this);
}
/**
@@ -1333,15 +1302,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalDnses(@NonNull LinkProperties target) {
- Collection<InetAddress> targetDnses = target.getDnsServers();
- String targetDomains = target.getDomains();
- if (mDomains == null) {
- if (targetDomains != null) return false;
- } else {
- if (!mDomains.equals(targetDomains)) return false;
- }
- return (mDnses.size() == targetDnses.size()) ?
- mDnses.containsAll(targetDnses) : false;
+ return LinkPropertiesUtils.isIdenticalDnses(target, this);
}
/**
@@ -1394,9 +1355,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalRoutes(@NonNull LinkProperties target) {
- Collection<RouteInfo> targetRoutes = target.getRoutes();
- return (mRoutes.size() == targetRoutes.size()) ?
- mRoutes.containsAll(targetRoutes) : false;
+ return LinkPropertiesUtils.isIdenticalRoutes(target, this);
}
/**
@@ -1408,8 +1367,7 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public boolean isIdenticalHttpProxy(@NonNull LinkProperties target) {
- return getHttpProxy() == null ? target.getHttpProxy() == null :
- getHttpProxy().equals(target.getHttpProxy());
+ return LinkPropertiesUtils.isIdenticalHttpProxy(target, this);
}
/**
@@ -1541,26 +1499,6 @@
}
/**
- * Compares the addresses in this LinkProperties with another
- * LinkProperties, examining only addresses on the base link.
- *
- * @param target a LinkProperties with the new list of addresses
- * @return the differences between the addresses.
- * @hide
- */
- public @NonNull CompareResult<LinkAddress> compareAddresses(@Nullable LinkProperties target) {
- /*
- * Duplicate the LinkAddresses into removed, we will be removing
- * address which are common between mLinkAddresses and target
- * leaving the addresses that are different. And address which
- * are in target but not in mLinkAddresses are placed in the
- * addedAddresses.
- */
- return new CompareResult<>(mLinkAddresses,
- target != null ? target.getLinkAddresses() : null);
- }
-
- /**
* Compares the DNS addresses in this LinkProperties with another
* LinkProperties, examining only DNS addresses on the base link.
*
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 74c9aac..0e10c42 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -20,11 +20,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.MacAddressUtils;
import android.net.wifi.WifiInfo;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.BitUtils;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -33,7 +33,6 @@
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.Arrays;
-import java.util.Random;
/**
* Representation of a MAC address.
@@ -109,21 +108,13 @@
if (equals(BROADCAST_ADDRESS)) {
return TYPE_BROADCAST;
}
- if (isMulticastAddress()) {
+ if ((mAddr & MULTICAST_MASK) != 0) {
return TYPE_MULTICAST;
}
return TYPE_UNICAST;
}
/**
- * @return true if this MacAddress is a multicast address.
- * @hide
- */
- public boolean isMulticastAddress() {
- return (mAddr & MULTICAST_MASK) != 0;
- }
-
- /**
* @return true if this MacAddress is a locally assigned address.
*/
public boolean isLocallyAssigned() {
@@ -192,7 +183,7 @@
* @hide
*/
public static boolean isMacAddress(byte[] addr) {
- return addr != null && addr.length == ETHER_ADDR_LEN;
+ return MacAddressUtils.isMacAddress(addr);
}
/**
@@ -261,26 +252,11 @@
}
private static byte[] byteAddrFromLongAddr(long addr) {
- byte[] bytes = new byte[ETHER_ADDR_LEN];
- int index = ETHER_ADDR_LEN;
- while (index-- > 0) {
- bytes[index] = (byte) addr;
- addr = addr >> 8;
- }
- return bytes;
+ return MacAddressUtils.byteAddrFromLongAddr(addr);
}
private static long longAddrFromByteAddr(byte[] addr) {
- Preconditions.checkNotNull(addr);
- if (!isMacAddress(addr)) {
- throw new IllegalArgumentException(
- Arrays.toString(addr) + " was not a valid MAC address");
- }
- long longAddr = 0;
- for (byte b : addr) {
- longAddr = (longAddr << 8) + BitUtils.uint8(b);
- }
- return longAddr;
+ return MacAddressUtils.longAddrFromByteAddr(addr);
}
// Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr))
@@ -350,50 +326,7 @@
* @hide
*/
public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() {
- return createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
- }
-
- /**
- * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the
- * unicast bit, are randomly selected.
- *
- * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
- *
- * @return a random locally assigned, unicast MacAddress.
- *
- * @hide
- */
- public static @NonNull MacAddress createRandomUnicastAddress() {
- return createRandomUnicastAddress(null, new SecureRandom());
- }
-
- /**
- * Returns a randomly generated MAC address using the given Random object and the same
- * OUI values as the given MacAddress.
- *
- * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
- *
- * @param base a base MacAddress whose OUI is used for generating the random address.
- * If base == null then the OUI will also be randomized.
- * @param r a standard Java Random object used for generating the random address.
- * @return a random locally assigned MacAddress.
- *
- * @hide
- */
- public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
- long addr;
- if (base == null) {
- addr = r.nextLong() & VALID_LONG_MASK;
- } else {
- addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
- }
- addr |= LOCALLY_ASSIGNED_MASK;
- addr &= ~MULTICAST_MASK;
- MacAddress mac = new MacAddress(addr);
- if (mac.equals(DEFAULT_MAC_ADDRESS)) {
- return createRandomUnicastAddress(base, r);
- }
- return mac;
+ return MacAddressUtils.createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
}
// Convenience function for working around the lack of byte literals.
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index d286204..aae9fd4 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -17,6 +17,8 @@
package android.net;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
@@ -43,9 +45,11 @@
*
* @hide
*/
+@SystemApi
public abstract class NetworkAgent {
- // Guaranteed to be non-null, otherwise registerNetworkAgent() would have thrown
- // an exception. Be careful in tests when mocking though.
+ /**
+ * The {@link Network} corresponding to this object.
+ */
@NonNull
public final Network network;
@@ -58,9 +62,14 @@
private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
private volatile long mLastBwRefreshTime = 0;
private static final long BW_REFRESH_MIN_WIN_MS = 500;
- private boolean mPollLceScheduled = false;
- private AtomicBoolean mPollLcePending = new AtomicBoolean(false);
- public final int mProviderId;
+ private boolean mBandwidthUpdateScheduled = false;
+ private AtomicBoolean mBandwidthUpdatePending = new AtomicBoolean(false);
+
+ /**
+ * The ID of the {@link NetworkProvider} that created this object, or
+ * {@link NetworkProvider#ID_NONE} if unknown.
+ */
+ public final int providerId;
private static final int BASE = Protocol.BASE_NETWORK_AGENT;
@@ -68,6 +77,7 @@
* Sent by ConnectivityService to the NetworkAgent to inform it of
* suspected connectivity problems on its network. The NetworkAgent
* should take steps to verify and correct connectivity.
+ * @hide
*/
public static final int CMD_SUSPECT_BAD = BASE;
@@ -76,6 +86,7 @@
* ConnectivityService to pass the current NetworkInfo (connection state).
* Sent when the NetworkInfo changes, mainly due to change of state.
* obj = NetworkInfo
+ * @hide
*/
public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
@@ -83,6 +94,7 @@
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkCapabilties.
* obj = NetworkCapabilities
+ * @hide
*/
public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
@@ -90,11 +102,14 @@
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkProperties.
* obj = NetworkProperties
+ * @hide
*/
public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
- /* centralize place where base network score, and network score scaling, will be
+ /**
+ * Centralize the place where base network score, and network score scaling, will be
* stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE
+ * @hide
*/
public static final int WIFI_BASE_SCORE = 60;
@@ -102,6 +117,7 @@
* Sent by the NetworkAgent to ConnectivityService to pass the current
* network score.
* obj = network score Integer
+ * @hide
*/
public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
@@ -114,12 +130,33 @@
* obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String}
* representing URL that Internet probe was redirect to, if it was redirected,
* or mapping to {@code null} otherwise.
+ * @hide
*/
public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;
+
+ /**
+ * Network validation suceeded.
+ * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}.
+ */
+ public static final int VALIDATION_STATUS_VALID = 1;
+
+ /**
+ * Network validation was attempted and failed. This may be received more than once as
+ * subsequent validation attempts are made.
+ */
+ public static final int VALIDATION_STATUS_NOT_VALID = 2;
+
+ // TODO: remove.
+ /** @hide */
public static final int VALID_NETWORK = 1;
+ /** @hide */
public static final int INVALID_NETWORK = 2;
+ /**
+ * The key for the redirect URL in the Bundle argument of {@code CMD_REPORT_NETWORK_STATUS}.
+ * @hide
+ */
public static String REDIRECT_URL_KEY = "redirect URL";
/**
@@ -128,6 +165,7 @@
* CONNECTED so it can be given special treatment at that time.
*
* obj = boolean indicating whether to use this network even if unvalidated
+ * @hide
*/
public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8;
@@ -138,12 +176,14 @@
* responsibility to remember it.
*
* arg1 = 1 if true, 0 if false
+ * @hide
*/
public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
/**
* Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
* the underlying network connection for updated bandwidth information.
+ * @hide
*/
public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
@@ -156,6 +196,7 @@
* obj = KeepalivePacketData object describing the data to be sent
*
* Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+ * @hide
*/
public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;
@@ -165,6 +206,7 @@
* arg1 = slot number of the keepalive to stop.
*
* Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+ * @hide
*/
public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12;
@@ -178,6 +220,7 @@
*
* arg1 = slot number of the keepalive
* arg2 = error code
+ * @hide
*/
public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13;
@@ -186,6 +229,7 @@
* that when crossed should trigger a system wakeup and a NetworkCapabilities update.
*
* obj = int[] describing signal strength thresholds.
+ * @hide
*/
public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14;
@@ -193,6 +237,7 @@
* Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid
* automatically reconnecting to this network (e.g. via autojoin). Happens
* when user selects "No" option on the "Stay connected?" dialog box.
+ * @hide
*/
public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;
@@ -205,6 +250,7 @@
* This does not happen with UDP, so this message is TCP-specific.
* arg1 = slot number of the keepalive to filter for.
* obj = the keepalive packet to send repeatedly.
+ * @hide
*/
public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16;
@@ -212,6 +258,7 @@
* Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See
* {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}.
* arg1 = slot number of the keepalive packet filter to remove.
+ * @hide
*/
public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;
@@ -219,27 +266,32 @@
// of dependent changes that would conflict throughout the automerger graph. Having these
// temporarily helps with the process of going through with all these dependent changes across
// the entire tree.
+ /** @hide TODO: decide which of these to expose. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE);
}
+
+ /** @hide TODO: decide which of these to expose. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) {
this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE);
}
+ /** @hide TODO: decide which of these to expose. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score, int providerId) {
this(looper, context, logTag, ni, nc, lp, score, null, providerId);
}
+ /** @hide TODO: decide which of these to expose. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config,
int providerId) {
mHandler = new NetworkAgentHandler(looper);
LOG_TAG = logTag;
mContext = context;
- mProviderId = providerId;
+ this.providerId = providerId;
if (ni == null || nc == null || lp == null) {
throw new IllegalArgumentException();
}
@@ -287,7 +339,7 @@
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
if (DBG) log("NetworkAgent channel lost");
// let the client know CS is done with us.
- unwanted();
+ onNetworkUnwanted();
synchronized (mPreConnectedQueue) {
mAsyncChannel = null;
}
@@ -303,16 +355,16 @@
log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
}
if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
- mPollLceScheduled = false;
- if (!mPollLcePending.getAndSet(true)) {
- pollLceData();
+ mBandwidthUpdateScheduled = false;
+ if (!mBandwidthUpdatePending.getAndSet(true)) {
+ onBandwidthUpdateRequested();
}
} else {
// deliver the request at a later time rather than discard it completely.
- if (!mPollLceScheduled) {
+ if (!mBandwidthUpdateScheduled) {
long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS
- currentTimeMs + 1;
- mPollLceScheduled = sendEmptyMessageDelayed(
+ mBandwidthUpdateScheduled = sendEmptyMessageDelayed(
CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
}
}
@@ -325,19 +377,20 @@
+ (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ")
+ redirectUrl);
}
- networkStatus(msg.arg1, redirectUrl);
+ onValidationStatus(msg.arg1 /* status */, redirectUrl);
break;
}
case CMD_SAVE_ACCEPT_UNVALIDATED: {
- saveAcceptUnvalidated(msg.arg1 != 0);
+ onSaveAcceptUnvalidated(msg.arg1 != 0);
break;
}
case CMD_START_SOCKET_KEEPALIVE: {
- startSocketKeepalive(msg);
+ onStartSocketKeepalive(msg.arg1 /* slot */, msg.arg2 /* interval */,
+ (KeepalivePacketData) msg.obj /* packet */);
break;
}
case CMD_STOP_SOCKET_KEEPALIVE: {
- stopSocketKeepalive(msg);
+ onStopSocketKeepalive(msg.arg1 /* slot */);
break;
}
@@ -350,19 +403,20 @@
for (int i = 0; i < intThresholds.length; i++) {
intThresholds[i] = thresholds.get(i);
}
- setSignalStrengthThresholds(intThresholds);
+ onSignalStrengthThresholdsUpdated(intThresholds);
break;
}
case CMD_PREVENT_AUTOMATIC_RECONNECT: {
- preventAutomaticReconnect();
+ onAutomaticReconnectDisabled();
break;
}
case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
- addKeepalivePacketFilter(msg);
+ onAddKeepalivePacketFilter(msg.arg1 /* slot */,
+ (KeepalivePacketData) msg.obj /* packet */);
break;
}
case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
- removeKeepalivePacketFilter(msg);
+ onRemoveKeepalivePacketFilter(msg.arg1 /* slot */);
break;
}
}
@@ -397,14 +451,16 @@
}
/**
- * Called by the bearer code when it has new LinkProperties data.
+ * Must be called by the agent when the network's {@link LinkProperties} change.
+ * @param linkProperties the new LinkProperties.
*/
- public void sendLinkProperties(LinkProperties linkProperties) {
+ public void sendLinkProperties(@NonNull LinkProperties linkProperties) {
queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
}
/**
- * Called by the bearer code when it has new NetworkInfo data.
+ * Must be called by the agent when it has a new NetworkInfo object.
+ * @hide TODO: expose something better.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public void sendNetworkInfo(NetworkInfo networkInfo) {
@@ -412,17 +468,19 @@
}
/**
- * Called by the bearer code when it has new NetworkCapabilities data.
+ * Must be called by the agent when the network's {@link NetworkCapabilities} change.
+ * @param networkCapabilities the new NetworkCapabilities.
*/
- public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
- mPollLcePending.set(false);
+ public void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+ mBandwidthUpdatePending.set(false);
mLastBwRefreshTime = System.currentTimeMillis();
queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
new NetworkCapabilities(networkCapabilities));
}
/**
- * Called by the bearer code when it has a new score for this network.
+ * Must be called by the agent to update the score of this network.
+ * @param score the new score.
*/
public void sendNetworkScore(int score) {
if (score < 0) {
@@ -434,14 +492,16 @@
}
/**
- * Called by the bearer code when it has a new NetworkScore for this network.
+ * Must be called by the agent when it has a new {@link NetworkScore} for this network.
+ * @param ns the new score.
+ * @hide TODO: unhide the NetworkScore class, and rename to sendNetworkScore.
*/
public void updateScore(@NonNull NetworkScore ns) {
queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new NetworkScore(ns));
}
/**
- * Called by the bearer to indicate this network was manually selected by the user.
+ * Must be called by the agent to indicate this network was manually selected by the user.
* This should be called before the NetworkInfo is marked CONNECTED so that this
* Network can be given special treatment at that time. If {@code acceptUnvalidated} is
* {@code true}, then the system will switch to this network. If it is {@code false} and the
@@ -450,15 +510,16 @@
* {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
* calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
* {@link #saveAcceptUnvalidated} to respect the user's choice.
+ * @hide should move to NetworkAgentConfig.
*/
public void explicitlySelected(boolean acceptUnvalidated) {
explicitlySelected(true /* explicitlySelected */, acceptUnvalidated);
}
/**
- * Called by the bearer to indicate whether the network was manually selected by the user.
- * This should be called before the NetworkInfo is marked CONNECTED so that this
- * Network can be given special treatment at that time.
+ * Must be called by the agent to indicate whether the network was manually selected by the
+ * user. This should be called before the network becomes connected, so it can be given
+ * special treatment when it does.
*
* If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true},
* then the system will switch to this network. If {@code explicitlySelected} is {@code true}
@@ -473,6 +534,7 @@
* {@code true}, the system will interpret this as the user having accepted partial connectivity
* on this network. Thus, the system will switch to the network and consider it validated even
* if it only provides partial connectivity, but the network is not otherwise treated specially.
+ * @hide should move to NetworkAgentConfig.
*/
public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED,
@@ -486,73 +548,126 @@
* as well, either canceling NetworkRequests or altering their score such that this
* network won't be immediately requested again.
*/
- abstract protected void unwanted();
+ public void onNetworkUnwanted() {
+ unwanted();
+ }
+ /** @hide TODO delete once subclasses have moved to onNetworkUnwanted. */
+ protected void unwanted() {
+ }
/**
* Called when ConnectivityService request a bandwidth update. The parent factory
* shall try to overwrite this method and produce a bandwidth update if capable.
*/
+ public void onBandwidthUpdateRequested() {
+ pollLceData();
+ }
+ /** @hide TODO delete once subclasses have moved to onBandwidthUpdateRequested. */
protected void pollLceData() {
}
/**
* Called when the system determines the usefulness of this network.
*
- * Networks claiming internet connectivity will have their internet
- * connectivity verified.
+ * The system attempts to validate Internet connectivity on networks that provide the
+ * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability.
*
* Currently there are two possible values:
- * {@code VALID_NETWORK} if the system is happy with the connection,
- * {@code INVALID_NETWORK} if the system is not happy.
- * TODO - add indications of captive portal-ness and related success/failure,
- * ie, CAPTIVE_SUCCESS_NETWORK, CAPTIVE_NETWORK for successful login and detection
+ * {@code VALIDATION_STATUS_VALID} if Internet connectivity was validated,
+ * {@code VALIDATION_STATUS_NOT_VALID} if Internet connectivity was not validated.
*
- * This may be called multiple times as the network status changes and may
- * generate false negatives if we lose ip connectivity before the link is torn down.
+ * This may be called multiple times as network status changes, or if there are multiple
+ * subsequent attempts to validate connectivity that fail.
*
- * @param status one of {@code VALID_NETWORK} or {@code INVALID_NETWORK}.
- * @param redirectUrl If the Internet probe was redirected, this is the destination it was
- * redirected to, otherwise {@code null}.
+ * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}.
+ * @param redirectUrl If Internet connectivity is being redirected (e.g., on a captive portal),
+ * this is the destination the probes are being redirected to, otherwise {@code null}.
*/
+ public void onValidationStatus(int status, @Nullable String redirectUrl) {
+ networkStatus(status, redirectUrl);
+ }
+ /** @hide TODO delete once subclasses have moved to onValidationStatus */
protected void networkStatus(int status, String redirectUrl) {
}
+
/**
* Called when the user asks to remember the choice to use this network even if unvalidated.
* The transport is responsible for remembering the choice, and the next time the user connects
* to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}.
* This method will only be called if {@link #explicitlySelected} was called with
* {@code acceptUnvalidated} set to {@code false}.
+ * @param accept whether the user wants to use the network even if unvalidated.
*/
+ public void onSaveAcceptUnvalidated(boolean accept) {
+ saveAcceptUnvalidated(accept);
+ }
+ /** @hide TODO delete once subclasses have moved to onSaveAcceptUnvalidated */
protected void saveAcceptUnvalidated(boolean accept) {
}
/**
* Requests that the network hardware send the specified packet at the specified interval.
+ *
+ * @param slot the hardware slot on which to start the keepalive.
+ * @param intervalSeconds the interval between packets
+ * @param packet the packet to send.
*/
+ public void onStartSocketKeepalive(int slot, int intervalSeconds,
+ @NonNull KeepalivePacketData packet) {
+ Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot, intervalSeconds,
+ packet);
+ startSocketKeepalive(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onStartSocketKeepalive */
protected void startSocketKeepalive(Message msg) {
onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
- * Requests that the network hardware send the specified packet at the specified interval.
+ * Requests that the network hardware stop a previously-started keepalive.
+ *
+ * @param slot the hardware slot on which to stop the keepalive.
*/
+ public void onStopSocketKeepalive(int slot) {
+ Message msg = mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0, null);
+ stopSocketKeepalive(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onStopSocketKeepalive */
protected void stopSocketKeepalive(Message msg) {
onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
- * Called by the network when a socket keepalive event occurs.
+ * Must be called by the agent when a socket keepalive event occurs.
+ *
+ * @param slot the hardware slot on which the event occurred.
+ * @param event the event that occurred.
*/
+ public void sendSocketKeepaliveEvent(int slot, int event) {
+ queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, event);
+ }
+ /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */
public void onSocketKeepaliveEvent(int slot, int reason) {
- queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, reason);
+ sendSocketKeepaliveEvent(slot, reason);
}
/**
* Called by ConnectivityService to add specific packet filter to network hardware to block
- * ACKs matching the sent keepalive packets. Implementations that support this feature must
- * override this method.
+ * replies (e.g., TCP ACKs) matching the sent keepalive packets. Implementations that support
+ * this feature must override this method.
+ *
+ * @param slot the hardware slot on which the keepalive should be sent.
+ * @param packet the packet that is being sent.
*/
+ public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) {
+ Message msg = mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0, packet);
+ addKeepalivePacketFilter(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onAddKeepalivePacketFilter */
protected void addKeepalivePacketFilter(Message msg) {
}
@@ -560,14 +675,28 @@
* Called by ConnectivityService to remove a packet filter installed with
* {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature
* must override this method.
+ *
+ * @param slot the hardware slot on which the keepalive is being sent.
*/
+ public void onRemoveKeepalivePacketFilter(int slot) {
+ Message msg = mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0, null);
+ removeKeepalivePacketFilter(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onRemoveKeepalivePacketFilter */
protected void removeKeepalivePacketFilter(Message msg) {
}
/**
* Called by ConnectivityService to inform this network transport of signal strength thresholds
* that when crossed should trigger a system wakeup and a NetworkCapabilities update.
+ *
+ * @param thresholds the array of thresholds that should trigger wakeups.
*/
+ public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) {
+ setSignalStrengthThresholds(thresholds);
+ }
+ /** @hide TODO delete once subclasses have moved to onSetSignalStrengthThresholds */
protected void setSignalStrengthThresholds(int[] thresholds) {
}
@@ -577,9 +706,14 @@
* responsible for making sure the device does not automatically reconnect to the same network
* after the {@code unwanted} call.
*/
+ public void onAutomaticReconnectDisabled() {
+ preventAutomaticReconnect();
+ }
+ /** @hide TODO delete once subclasses have moved to onAutomaticReconnectDisabled */
protected void preventAutomaticReconnect() {
}
+ /** @hide */
protected void log(String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
}
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
index 824ddb8..e271037 100644
--- a/core/java/android/net/NetworkFactory.java
+++ b/core/java/android/net/NetworkFactory.java
@@ -115,13 +115,6 @@
*/
private static final int CMD_SET_FILTER = BASE + 3;
- /**
- * Sent by NetworkFactory to ConnectivityService to indicate that a request is
- * unfulfillable.
- * @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest).
- */
- public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4;
-
private final Context mContext;
private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
private final String LOG_TAG;
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 9731f3c..301d203 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -300,22 +300,34 @@
* this without a single transport set will generate an exception, as will
* subsequently adding or removing transports after this is set.
* </p>
- * The interpretation of this {@code String} is bearer specific and bearers that use
- * it should document their particulars. For example, Bluetooth may use some sort of
- * device id while WiFi could used ssid and/or bssid. Cellular may use carrier spn.
+ * If the {@code networkSpecifier} is provided, it shall be interpreted as follows:
+ * <ul>
+ * <li>If the specifier can be parsed as an integer, it will be treated as a
+ * {@link android.net TelephonyNetworkSpecifier}, and the provided integer will be
+ * interpreted as a SubscriptionId.
+ * <li>If the value is an ethernet interface name, it will be treated as such.
+ * <li>For all other cases, the behavior is undefined.
+ * </ul>
*
- * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
- * specific network specifier where the bearer has a choice of
- * networks.
+ * @param networkSpecifier A {@code String} of either a SubscriptionId in cellular
+ * network request or an ethernet interface name in ethernet
+ * network request.
+ *
+ * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead.
*/
+ @Deprecated
public Builder setNetworkSpecifier(String networkSpecifier) {
- /*
- * A StringNetworkSpecifier does not accept null or empty ("") strings. When network
- * specifiers were strings a null string and an empty string were considered equivalent.
- * Hence no meaning is attached to a null or empty ("") string.
- */
- return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
- : new StringNetworkSpecifier(networkSpecifier));
+ try {
+ int subId = Integer.parseInt(networkSpecifier);
+ return setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(subId).build());
+ } catch (NumberFormatException nfe) {
+ // A StringNetworkSpecifier does not accept null or empty ("") strings. When network
+ // specifiers were strings a null string and an empty string were considered
+ // equivalent. Hence no meaning is attached to a null or empty ("") string.
+ return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
+ : new StringNetworkSpecifier(networkSpecifier));
+ }
}
/**
@@ -455,6 +467,19 @@
}
/**
+ * Returns true iff. the capabilities requested in this NetworkRequest are satisfied by the
+ * provided {@link NetworkCapabilities}.
+ *
+ * @param nc Capabilities that should satisfy this NetworkRequest. null capabilities do not
+ * satisfy any request.
+ * @hide
+ */
+ @SystemApi
+ public boolean satisfiedBy(@Nullable NetworkCapabilities nc) {
+ return networkCapabilities.satisfiedByNetworkCapabilities(nc);
+ }
+
+ /**
* @see Builder#addTransportType(int)
*/
public boolean hasTransport(@Transport int transportType) {
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 428a4ea..c233ec0 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -403,7 +403,7 @@
* @throws SecurityException if the caller does not hold the
* {@link permission#REQUEST_NETWORK_SCORES} permission.
* @throws IllegalArgumentException if a score cache is already registered for this type.
- * @deprecated equivalent to registering for cache updates with CACHE_FILTER_NONE.
+ * @deprecated equivalent to registering for cache updates with {@link #SCORE_FILTER_NONE}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 08cc4e2..779f7bc 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -31,7 +31,6 @@
import java.io.FileDescriptor;
import java.math.BigInteger;
import java.net.Inet4Address;
-import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
@@ -313,15 +312,6 @@
}
/**
- * Check if IP address type is consistent between two InetAddress.
- * @return true if both are the same type. False otherwise.
- */
- public static boolean addressTypeMatches(InetAddress left, InetAddress right) {
- return (((left instanceof Inet4Address) && (right instanceof Inet4Address)) ||
- ((left instanceof Inet6Address) && (right instanceof Inet6Address)));
- }
-
- /**
* Convert a 32 char hex string into a Inet6Address.
* throws a runtime exception if the string isn't 32 chars, isn't hex or can't be
* made into an Inet6Address
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index ea6002c..e088094 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -22,6 +22,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.NetUtils;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -441,21 +442,7 @@
@UnsupportedAppUsage
@Nullable
public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
- if ((routes == null) || (dest == null)) return null;
-
- RouteInfo bestRoute = null;
- // pick a longest prefix match under same address type
- for (RouteInfo route : routes) {
- if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) {
- if ((bestRoute != null) &&
- (bestRoute.mDestination.getPrefixLength() >=
- route.mDestination.getPrefixLength())) {
- continue;
- }
- if (route.matches(dest)) bestRoute = route;
- }
- }
- return bestRoute;
+ return NetUtils.selectBestRoute(routes, dest);
}
/**
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index fb224fb..fc9a8f6 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -20,6 +20,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -53,7 +54,11 @@
public abstract class SocketKeepalive implements AutoCloseable {
static final String TAG = "SocketKeepalive";
- /** @hide */
+ /**
+ * No errors.
+ * @hide
+ */
+ @SystemApi
public static final int SUCCESS = 0;
/** @hide */
diff --git a/core/java/android/net/TelephonyNetworkSpecifier.java b/core/java/android/net/TelephonyNetworkSpecifier.java
new file mode 100644
index 0000000..726f770
--- /dev/null
+++ b/core/java/android/net/TelephonyNetworkSpecifier.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * NetworkSpecifier object for cellular network request. Apps should use the
+ * {@link TelephonyNetworkSpecifier.Builder} class to create an instance.
+ */
+public final class TelephonyNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+
+ private final int mSubId;
+
+ /**
+ * Return the subscription Id of current TelephonyNetworkSpecifier object.
+ *
+ * @return The subscription id.
+ */
+ public int getSubscriptionId() {
+ return mSubId;
+ }
+
+ /**
+ * @hide
+ */
+ public TelephonyNetworkSpecifier(int subId) {
+ this.mSubId = subId;
+ }
+
+ public static final @NonNull Creator<TelephonyNetworkSpecifier> CREATOR =
+ new Creator<TelephonyNetworkSpecifier>() {
+ @Override
+ public TelephonyNetworkSpecifier createFromParcel(Parcel in) {
+ int subId = in.readInt();
+ return new TelephonyNetworkSpecifier(subId);
+ }
+
+ @Override
+ public TelephonyNetworkSpecifier[] newArray(int size) {
+ return new TelephonyNetworkSpecifier[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSubId);
+ }
+
+ @Override
+ public int hashCode() {
+ return mSubId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof TelephonyNetworkSpecifier)) {
+ return false;
+ }
+
+ TelephonyNetworkSpecifier lhs = (TelephonyNetworkSpecifier) obj;
+ return mSubId == lhs.mSubId;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("TelephonyNetworkSpecifier [")
+ .append("mSubId = ").append(mSubId)
+ .append("]")
+ .toString();
+ }
+
+ /** @hide */
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ // Any generic requests should be satisfied by a specific telephony network.
+ // For simplicity, we treat null same as MatchAllNetworkSpecifier
+ return equals(other) || other == null || other instanceof MatchAllNetworkSpecifier;
+ }
+
+
+ /**
+ * Builder to create {@link TelephonyNetworkSpecifier} object.
+ */
+ public static final class Builder {
+ // Integer.MIN_VALUE which is not a valid subId, services as the sentinel to check if
+ // subId was set
+ private static final int SENTINEL_SUB_ID = Integer.MIN_VALUE;
+
+ private int mSubId;
+
+ public Builder() {
+ mSubId = SENTINEL_SUB_ID;
+ }
+
+ /**
+ * Set the subscription id.
+ *
+ * @param subId The subscription Id.
+ * @return Instance of {@link Builder} to enable the chaining of the builder method.
+ */
+ public @NonNull Builder setSubscriptionId(int subId) {
+ mSubId = subId;
+ return this;
+ }
+
+ /**
+ * Create a NetworkSpecifier for the cellular network request.
+ *
+ * @return TelephonyNetworkSpecifier object.
+ * @throws IllegalArgumentException when subscription Id is not provided through
+ * {@link #setSubscriptionId(int)}.
+ */
+ public @NonNull TelephonyNetworkSpecifier build() {
+ if (mSubId == SENTINEL_SUB_ID) {
+ throw new IllegalArgumentException("Subscription Id is not provided.");
+ }
+ return new TelephonyNetworkSpecifier(mSubId);
+ }
+ }
+}
diff --git a/core/java/android/os/IIncidentDumpCallback.aidl b/core/java/android/os/IIncidentDumpCallback.aidl
new file mode 100644
index 0000000..09b5b01
--- /dev/null
+++ b/core/java/android/os/IIncidentDumpCallback.aidl
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Callback from IIncidentManager to dump an extended section.
+ *
+ * @hide
+ */
+oneway interface IIncidentDumpCallback {
+ /**
+ * Dumps section data to the given ParcelFileDescriptor.
+ */
+ void onDumpSection(in ParcelFileDescriptor fd);
+}
diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl
index 7e1b1e0..923234e 100644
--- a/core/java/android/os/IIncidentManager.aidl
+++ b/core/java/android/os/IIncidentManager.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.IIncidentReportStatusListener;
+import android.os.IIncidentDumpCallback;
import android.os.IncidentManager;
import android.os.IncidentReportArgs;
@@ -52,6 +53,19 @@
@nullable IIncidentReportStatusListener listener);
/**
+ * Register a section callback with the given id and name. The callback function
+ * will be invoked when an incident report with all sections or sections matching
+ * the given id is being taken.
+ */
+ oneway void registerSection(int id, String name, IIncidentDumpCallback callback);
+
+ /**
+ * Unregister a section callback associated with the given id. The section must be
+ * previously registered with registerSection(int, String, IIncidentDumpCallback).
+ */
+ oneway void unregisterSection(int id);
+
+ /**
* Tell the incident daemon that the android system server is up and running.
*/
oneway void systemRunning();
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 7b2d148..416d692 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -24,7 +24,7 @@
{
boolean hasVibrator();
boolean hasAmplitudeControl();
- boolean setAlwaysOnEffect(int id, in VibrationEffect effect,
+ boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in VibrationEffect effect,
in VibrationAttributes attributes);
void vibrate(int uid, String opPkg, in VibrationEffect effect,
in VibrationAttributes attributes, String reason, IBinder token);
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index 09e1c0f..f6563eb 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -31,6 +31,7 @@
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -421,6 +422,39 @@
}
/**
+ * Callback for dumping an extended (usually vendor-supplied) incident report section
+ *
+ * @see #registerSection
+ * @see #unregisterSection
+ *
+ * @hide
+ */
+ public static class DumpCallback {
+ private Executor mExecutor;
+
+ IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() {
+ @Override
+ public void onDumpSection(ParcelFileDescriptor pfd) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ DumpCallback.this.onDumpSection(
+ new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+ });
+ } else {
+ DumpCallback.this.onDumpSection(
+ new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+ }
+ }
+ };
+
+ /**
+ * Called when incidentd requests to dump this section.
+ */
+ public void onDumpSection(OutputStream out) {
+ }
+ }
+
+ /**
* @hide
*/
public IncidentManager(Context context) {
@@ -528,6 +562,61 @@
}
/**
+ * Register a callback to dump an extended incident report section with the given id and name.
+ * The callback function will be invoked when an incident report with all sections or sections
+ * matching the given id is being taken.
+ *
+ * @hide
+ */
+ public void registerSection(int id, String name, @NonNull DumpCallback callback) {
+ registerSection(id, name, mContext.getMainExecutor(), callback);
+ }
+
+ /**
+ * Register a callback to dump an extended incident report section with the given id and name,
+ * running on the supplied executor.
+ *
+ * @hide
+ */
+ public void registerSection(int id, String name, @NonNull @CallbackExecutor Executor executor,
+ @NonNull DumpCallback callback) {
+ try {
+ if (callback.mExecutor != null) {
+ throw new RuntimeException("Do not reuse DumpCallback objects when calling"
+ + " registerSection");
+ }
+ callback.mExecutor = executor;
+ final IIncidentManager service = getIIncidentManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "registerSection can't find incident binder service");
+ return;
+ }
+ service.registerSection(id, name, callback.mBinder);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "registerSection failed", ex);
+ }
+ }
+
+ /**
+ * Unregister an extended section dump function. The section must be previously registered with
+ * {@link #registerSection(int, String, DumpCallback)}
+ *
+ * @hide
+ */
+ public void unregisterSection(int id) {
+ try {
+ final IIncidentManager service = getIIncidentManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "unregisterSection can't find incident binder service");
+ return;
+ }
+ service.unregisterSection(id);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unregisterSection failed", ex);
+ }
+ }
+
+ /**
* Get the incident reports that are available for upload for the supplied
* broadcast recevier.
*
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index c1542c7..8050454 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -70,14 +70,15 @@
}
@Override
- public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attributes) {
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+ AudioAttributes attributes) {
if (mService == null) {
Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
return false;
}
try {
VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
- return mService.setAlwaysOnEffect(id, effect, atr);
+ return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, atr);
} catch (RemoteException e) {
Log.w(TAG, "Failed to set always-on effect.", e);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d8fadfb..2eaefca 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -192,7 +192,11 @@
/**
* Specifies if a user is disallowed from changing Wi-Fi
* access points. The default value is <code>false</code>.
- * <p>This restriction has no effect in a managed profile.
+ * <p>
+ * Device owner and profile owner can set this restriction, although the restriction has no
+ * effect in a managed profile. When it is set by the profile owner of an organization-owned
+ * managed profile on the parent profile, it will disallow the personal user from changing
+ * Wi-Fi access points.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -242,8 +246,13 @@
/**
* Specifies if a user is disallowed from turning on location sharing.
* The default value is <code>false</code>.
- * <p>In a managed profile, location sharing always reflects the primary user's setting, but
+ * <p>
+ * In a managed profile, location sharing always reflects the primary user's setting, but
* can be overridden and forced off by setting this restriction to true in the managed profile.
+ * <p>
+ * Device owner and profile owner can set this restriction. When it is set by the profile
+ * owner of an organization-owned managed profile on the parent profile, it will prevent the
+ * user from turning on location sharing in the personal profile.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -349,9 +358,14 @@
* Specifies if a user is disallowed from configuring bluetooth.
* This does <em>not</em> restrict the user from turning bluetooth on or off.
* The default value is <code>false</code>.
- * <p>This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
+ * <p>
+ * This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
* bluetooth completely on the device, use {@link #DISALLOW_BLUETOOTH}.
- * <p>This restriction has no effect in a managed profile.
+ * <p>
+ * Device owner and profile owner can set this restriction, although the restriction has no
+ * effect in a managed profile. When it is set by the profile owner of an organization-owned
+ * managed profile on the parent profile, it will disallow the personal user from configuring
+ * bluetooth.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -364,8 +378,10 @@
/**
* Specifies if bluetooth is disallowed on the device.
*
- * <p> This restriction can only be set by the device owner and the profile owner on the
- * primary user and it applies globally - i.e. it disables bluetooth on the entire device.
+ * <p> This restriction can only be set by the device owner, the profile owner on the
+ * primary user or the profile owner of an organization-owned managed profile on the
+ * parent profile and it applies globally - i.e. it disables bluetooth on the entire
+ * device.
* <p>The default value is <code>false</code>.
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -377,8 +393,9 @@
/**
* Specifies if outgoing bluetooth sharing is disallowed on the device. Device owner and profile
- * owner can set this restriction. When it is set by device owner, all users on this device will
- * be affected.
+ * owner can set this restriction. When it is set by device owner or the profile owner of an
+ * organization-owned managed profile on the parent profile, all users on this device will be
+ * affected.
*
* <p>Default is <code>true</code> for managed profiles and false for otherwise. When a device
* upgrades to {@link android.os.Build.VERSION_CODES#O}, the system sets it for all existing
@@ -394,7 +411,8 @@
/**
* Specifies if a user is disallowed from transferring files over
- * USB. This can only be set by device owners and profile owners on the primary user.
+ * USB. This can only be set by device owners, profile owners on the primary user or
+ * profile owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -453,8 +471,9 @@
/**
* Specifies if a user is disallowed from enabling or accessing debugging features. When set on
- * the primary user, disables debugging features altogether, including USB debugging. When set
- * on a managed profile or a secondary user, blocks debugging for that user only, including
+ * the primary user or by the profile owner of an organization-owned managed profile on the
+ * parent profile, disables debugging features altogether, including USB debugging. When set on
+ * a managed profile or a secondary user, blocks debugging for that user only, including
* starting activities, making service calls, accessing content providers, sending broadcasts,
* installing/uninstalling packages, clearing user data, etc.
* The default value is <code>false</code>.
@@ -485,18 +504,19 @@
/**
* Specifies if a user is disallowed from enabling or disabling location providers. As a
- * result, user is disallowed from turning on or off location. Device owner and profile owners
- * can set this restriction and it only applies on the managed user.
+ * result, user is disallowed from turning on or off location.
*
- * <p>In a managed profile, location sharing is forced off when it's off on primary user, so
- * user can still turn off location sharing on managed profile when the restriction is set by
- * profile owner on managed profile.
- *
- * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
+ * <p>
+ * In a managed profile, location sharing is forced off when it is turned off on the primary
+ * user or by the profile owner of an organization-owned managed profile on the parent profile.
+ * The user can still turn off location sharing on a managed profile when the restriction is
+ * set by the profile owner on a managed profile.
+ * <p>
+ * This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
* as the device owner or profile owner can still enable or disable location mode via
* {@link DevicePolicyManager#setLocationEnabled} when this restriction is on.
- *
- * <p>The default value is <code>false</code>.
+ * <p>
+ * The default value is <code>false</code>.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -510,7 +530,8 @@
/**
* Specifies if date, time and timezone configuring is disallowed.
*
- * <p>When restriction is set by device owners, it applies globally - i.e., it disables date,
+ * <p>When restriction is set by device owners or profile owners of organization-owned
+ * managed profiles on the parent profile, it applies globally - i.e., it disables date,
* time and timezone setting on the entire device and all users will be affected. When it's set
* by profile owners, it's only applied to the managed user.
* <p>The default value is <code>false</code>.
@@ -526,8 +547,9 @@
/**
* Specifies if a user is disallowed from configuring Tethering
- * & portable hotspots. This can only be set by device owners and profile owners on the
- * primary user. The default value is <code>false</code>.
+ * & portable hotspots. This can only be set by device owners, profile owners on the
+ * primary user or profile owners of organization-owned managed profiles on the parent profile.
+ * The default value is <code>false</code>.
* <p>In Android 9.0 or higher, if tethering is enabled when this restriction is set,
* tethering will be automatically turned off.
*
@@ -571,8 +593,8 @@
/**
* Specifies if a user is disallowed from adding new users. This can only be set by device
- * owners and profile owners on the primary user.
- * The default value is <code>false</code>.
+ * owners, profile owners on the primary user or profile owners of organization-owned managed
+ * profiles on the parent profile. The default value is <code>false</code>.
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can add other users.
*
@@ -621,7 +643,8 @@
/**
* Specifies if a user is disallowed from configuring cell
- * broadcasts. This can only be set by device owners and profile owners on the primary user.
+ * broadcasts. This can only be set by device owners, profile owners on the primary user or
+ * profile owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can configure cell broadcasts.
@@ -636,7 +659,8 @@
/**
* Specifies if a user is disallowed from configuring mobile
- * networks. This can only be set by device owners and profile owners on the primary user.
+ * networks. This can only be set by device owners, profile owners on the primary user or
+ * profile owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can configure mobile networks.
@@ -739,6 +763,10 @@
/**
* Specifies that the user is not allowed to send or receive
* SMS messages. The default value is <code>false</code>.
+ * <p>
+ * Device owner and profile owner can set this restriction. When it is set by the
+ * profile owner of an organization-owned managed profile on the parent profile,
+ * it will disable SMS in the personal profile.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -857,7 +885,8 @@
/**
* Specifies if the user is not allowed to reboot the device into safe boot mode.
- * This can only be set by device owners and profile owners on the primary user.
+ * This can only be set by device owners, profile owners on the primary user or profile
+ * owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -896,6 +925,12 @@
/**
* Specifies if a user is not allowed to use the camera.
+ * <p>
+ * Device owner and profile owner can set this restriction. When the restriction is set by
+ * the device owner or the profile owner of an organization-owned managed profile on the
+ * parent profile, it is applied globally.
+ * <p>
+ * The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -916,7 +951,8 @@
/**
* Specifies if a user is not allowed to use cellular data when roaming. This can only be set by
- * device owners. The default value is <code>false</code>.
+ * device owners or profile owners of organization-owned managed profiles on the parent profile.
+ * The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -1011,8 +1047,9 @@
* Specifies if the contents of a user's screen is not allowed to be captured for artificial
* intelligence purposes.
*
- * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
- * only the target user will be affected.
+ * <p>Device owner and profile owner can set this restriction. When it is set by the
+ * device owner or the profile owner of an organization-owned managed profile on the parent
+ * profile, only the target user will be affected.
*
* <p>The default value is <code>false</code>.
*
@@ -1026,8 +1063,9 @@
* Specifies if the current user is able to receive content suggestions for selections based on
* the contents of their screen.
*
- * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
- * only the target user will be affected.
+ * <p>Device owner and profile owner can set this restriction. When it is set by the
+ * device owner or the profile owner of an organization-owned managed profile on the parent
+ * profile, only the target user will be affected.
*
* <p>The default value is <code>false</code>.
*
@@ -1093,7 +1131,9 @@
*
* <p>The default value is <code>false</code>.
*
- * <p>This user restriction can only be applied by the Device Owner.
+ * <p>This user restriction can only be applied by the device owner or the profile owner
+ * of an organization-owned managed profile on the parent profile.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -2413,6 +2453,13 @@
* by {@link #createUser(String, String, int)} or {@link #createGuest(Context, String)}), it
* takes less time.
*
+ * <p>This method completes the majority of work necessary for user creation: it
+ * creates user data, CE and DE encryption keys, app data directories, initializes the user and
+ * grants default permissions. When pre-created users become "real" users, only then are
+ * components notified of new user creation by firing user creation broadcasts.
+ *
+ * <p>All pre-created users are removed during system upgrade.
+ *
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
*
* @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
@@ -2424,6 +2471,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public @Nullable UserInfo preCreateUser(@NonNull String userType) {
try {
return mService.preCreateUser(userType);
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index ccbb0f1..ae75f3d 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -155,7 +155,7 @@
/**
* Configure an always-on haptics effect.
*
- * @param id The board-specific always-on ID to configure.
+ * @param alwaysOnId The board-specific always-on ID to configure.
* @param effect Vibration effect to assign to always-on id. Passing null will disable it.
* @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
@@ -164,8 +164,17 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
- public boolean setAlwaysOnEffect(int id, @Nullable VibrationEffect effect,
- @Nullable AudioAttributes attributes) {
+ public boolean setAlwaysOnEffect(int alwaysOnId, @Nullable VibrationEffect effect,
+ @Nullable AudioAttributes attributes) {
+ return setAlwaysOnEffect(Process.myUid(), mPackageName, alwaysOnId, effect, attributes);
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
Log.w(TAG, "Always-on effects aren't supported");
return false;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 089122d..e2b33e0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6420,6 +6420,15 @@
"accessibility_button_target_component";
/**
+ * The system class name of magnification controller which is a target to be toggled via
+ * accessibility shortcut or accessibility button.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER =
+ "com.android.server.accessibility.MagnificationController";
+
+ /**
* If touch exploration is enabled.
*/
public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java
new file mode 100644
index 0000000..ada59d6
--- /dev/null
+++ b/core/java/android/timezone/CountryTimeZones.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.icu.util.TimeZone;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Information about a country's time zones.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class CountryTimeZones {
+
+ /**
+ * A mapping to a time zone ID with some associated metadata.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class TimeZoneMapping {
+
+ private libcore.timezone.CountryTimeZones.TimeZoneMapping mDelegate;
+
+ TimeZoneMapping(libcore.timezone.CountryTimeZones.TimeZoneMapping delegate) {
+ this.mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the ID for this mapping. See also {@link #getTimeZone()} which handles when the
+ * ID is unrecognized.
+ */
+ @NonNull
+ public String getTimeZoneId() {
+ return mDelegate.timeZoneId;
+ }
+
+ /**
+ * Returns a {@link TimeZone} object for this mapping, or {@code null} if the ID is
+ * unrecognized.
+ */
+ @Nullable
+ public TimeZone getTimeZone() {
+ return mDelegate.getTimeZone();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneMapping that = (TimeZoneMapping) o;
+ return this.mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.mDelegate.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+ }
+
+ /**
+ * The result of lookup up a time zone using offset information (and possibly more).
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class OffsetResult {
+
+ private final TimeZone mTimeZone;
+ private final boolean mIsOnlyMatch;
+
+ /** Creates an instance with the supplied information. */
+ public OffsetResult(@NonNull TimeZone timeZone, boolean isOnlyMatch) {
+ mTimeZone = Objects.requireNonNull(timeZone);
+ mIsOnlyMatch = isOnlyMatch;
+ }
+
+ /**
+ * Returns a time zone that matches the supplied criteria.
+ */
+ @NonNull
+ public TimeZone getTimeZone() {
+ return mTimeZone;
+ }
+
+ /**
+ * Returns {@code true} if there is only one matching time zone for the supplied criteria.
+ */
+ public boolean isOnlyMatch() {
+ return mIsOnlyMatch;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ OffsetResult that = (OffsetResult) o;
+ return mIsOnlyMatch == that.mIsOnlyMatch
+ && mTimeZone.getID().equals(that.mTimeZone.getID());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTimeZone, mIsOnlyMatch);
+ }
+
+ @Override
+ public String toString() {
+ return "OffsetResult{"
+ + "mTimeZone=" + mTimeZone
+ + ", mIsOnlyMatch=" + mIsOnlyMatch
+ + '}';
+ }
+ }
+
+ @NonNull
+ private final libcore.timezone.CountryTimeZones mDelegate;
+
+ CountryTimeZones(libcore.timezone.CountryTimeZones delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * Returns true if the ISO code for the country is a match for the one specified.
+ */
+ public boolean isForCountryCode(@NonNull String countryIso) {
+ return mDelegate.isForCountryCode(countryIso);
+ }
+
+ /**
+ * Returns the default time zone ID for the country. Can return {@code null} in cases when no
+ * data is available or the time zone ID was not recognized.
+ */
+ @Nullable
+ public String getDefaultTimeZoneId() {
+ return mDelegate.getDefaultTimeZoneId();
+ }
+
+ /**
+ * Returns the default time zone for the country. Can return {@code null} in cases when no data
+ * is available or the time zone ID was not recognized.
+ */
+ @Nullable
+ public TimeZone getDefaultTimeZone() {
+ return mDelegate.getDefaultTimeZone();
+ }
+
+ /**
+ * Qualifier for a country's default time zone. {@code true} indicates whether the default
+ * would be a good choice <em>generally</em> when there's no other information available.
+ */
+ public boolean isDefaultTimeZoneBoosted() {
+ return mDelegate.getDefaultTimeZoneBoost();
+ }
+
+ /**
+ * Returns true if the country has at least one zone that is the same as UTC at the given time.
+ */
+ public boolean hasUtcZone(long whenMillis) {
+ return mDelegate.hasUtcZone(whenMillis);
+ }
+
+ /**
+ * Returns a time zone for the country, if there is one, that matches the desired properties. If
+ * there are multiple matches and the {@code bias} is one of them then it is returned, otherwise
+ * an arbitrary match is returned based on the {@link #getEffectiveTimeZoneMappingsAt(long)}
+ * ordering.
+ *
+ * @param totalOffsetMillis the offset from UTC at {@code whenMillis}
+ * @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST,
+ * {@code false} means not DST, {@code null} means unknown
+ * @param dstOffsetMillis the part of {@code totalOffsetMillis} contributed by DST, only used if
+ * {@code isDst} is {@code true}. The value can be {@code null} if the DST offset is
+ * unknown
+ * @param whenMillis the UTC time to match against
+ * @param bias the time zone to prefer, can be {@code null}
+ */
+ @Nullable
+ public OffsetResult lookupByOffsetWithBias(int totalOffsetMillis, @Nullable Boolean isDst,
+ @SuppressLint("AutoBoxing") @Nullable Integer dstOffsetMillis, long whenMillis,
+ @Nullable TimeZone bias) {
+ libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult =
+ mDelegate.lookupByOffsetWithBias(
+ totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias);
+ return delegateOffsetResult == null ? null :
+ new OffsetResult(delegateOffsetResult.mTimeZone, delegateOffsetResult.mOneMatch);
+ }
+
+ /**
+ * Returns an immutable, ordered list of time zone mappings for the country in an undefined but
+ * "priority" order, filtered so that only "effective" time zone IDs are returned. An
+ * "effective" time zone is one that differs from another time zone used in the country after
+ * {@code whenMillis}. The list can be empty if there were no zones configured or the configured
+ * zone IDs were not recognized.
+ */
+ @NonNull
+ public List<TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long whenMillis) {
+ List<libcore.timezone.CountryTimeZones.TimeZoneMapping> delegateList =
+ mDelegate.getEffectiveTimeZoneMappingsAt(whenMillis);
+
+ List<TimeZoneMapping> toReturn = new ArrayList<>(delegateList.size());
+ for (libcore.timezone.CountryTimeZones.TimeZoneMapping delegateMapping : delegateList) {
+ toReturn.add(new TimeZoneMapping(delegateMapping));
+ }
+ return Collections.unmodifiableList(toReturn);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CountryTimeZones that = (CountryTimeZones) o;
+ return mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDelegate);
+ }
+
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+}
diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java
new file mode 100644
index 0000000..39dbe85
--- /dev/null
+++ b/core/java/android/timezone/TelephonyLookup.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * A class that can find time zone-related information about telephony networks.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyLookup {
+
+ private static Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static TelephonyLookup sInstance;
+
+ @NonNull
+ private final libcore.timezone.TelephonyLookup mDelegate;
+
+ /**
+ * Obtains an instance for use when resolving telephony time zone information. This method never
+ * returns {@code null}.
+ */
+ @NonNull
+ public static TelephonyLookup getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new TelephonyLookup(libcore.timezone.TelephonyLookup.getInstance());
+ }
+ return sInstance;
+ }
+ }
+
+ private TelephonyLookup(@NonNull libcore.timezone.TelephonyLookup delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns an object capable of querying telephony network information. This method can return
+ * {@code null} in the event of an error while reading the underlying data files.
+ */
+ @Nullable
+ public TelephonyNetworkFinder getTelephonyNetworkFinder() {
+ libcore.timezone.TelephonyNetworkFinder telephonyNetworkFinderDelegate =
+ mDelegate.getTelephonyNetworkFinder();
+ return telephonyNetworkFinderDelegate != null
+ ? new TelephonyNetworkFinder(telephonyNetworkFinderDelegate) : null;
+ }
+}
diff --git a/core/java/android/timezone/TelephonyNetwork.java b/core/java/android/timezone/TelephonyNetwork.java
new file mode 100644
index 0000000..ae39fbd
--- /dev/null
+++ b/core/java/android/timezone/TelephonyNetwork.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * Information about a telephony network.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyNetwork {
+
+ @NonNull
+ private final libcore.timezone.TelephonyNetwork mDelegate;
+
+ TelephonyNetwork(@NonNull libcore.timezone.TelephonyNetwork delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the Mobile Country Code of the network.
+ */
+ @NonNull
+ public String getMcc() {
+ return mDelegate.getMcc();
+ }
+
+ /**
+ * Returns the Mobile Network Code of the network.
+ */
+ @NonNull
+ public String getMnc() {
+ return mDelegate.getMnc();
+ }
+
+ /**
+ * Returns the country in which the network operates as an ISO 3166 alpha-2 (lower case).
+ */
+ @NonNull
+ public String getCountryIsoCode() {
+ return mDelegate.getCountryIsoCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TelephonyNetwork that = (TelephonyNetwork) o;
+ return mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDelegate);
+ }
+
+ @Override
+ public String toString() {
+ return "TelephonyNetwork{"
+ + "mDelegate=" + mDelegate
+ + '}';
+ }
+}
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
new file mode 100644
index 0000000..a81a516
--- /dev/null
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * A class that can find telephony networks loaded via {@link TelephonyLookup}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyNetworkFinder {
+
+ @NonNull
+ private final libcore.timezone.TelephonyNetworkFinder mDelegate;
+
+ TelephonyNetworkFinder(libcore.timezone.TelephonyNetworkFinder delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns information held about a specific MCC + MNC combination. It is expected for this
+ * method to return {@code null}. Only known, unusual networks will typically have information
+ * returned, e.g. if they operate in countries other than the one suggested by their MCC.
+ */
+ @Nullable
+ public TelephonyNetwork findNetworkByMccMnc(@NonNull String mcc, @NonNull String mnc) {
+ Objects.requireNonNull(mcc);
+ Objects.requireNonNull(mnc);
+
+ libcore.timezone.TelephonyNetwork telephonyNetworkDelegate =
+ mDelegate.findNetworkByMccMnc(mcc, mnc);
+ return telephonyNetworkDelegate != null
+ ? new TelephonyNetwork(telephonyNetworkDelegate) : null;
+ }
+}
diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java
new file mode 100644
index 0000000..15dfe62
--- /dev/null
+++ b/core/java/android/timezone/TimeZoneFinder.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A class that can be used to find time zones.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class TimeZoneFinder {
+
+ private static Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static TimeZoneFinder sInstance;
+
+ private final libcore.timezone.TimeZoneFinder mDelegate;
+
+ private TimeZoneFinder(libcore.timezone.TimeZoneFinder delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * Obtains an instance for use when resolving telephony time zone information. This method never
+ * returns {@code null}.
+ */
+ @NonNull
+ public static TimeZoneFinder getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new TimeZoneFinder(libcore.timezone.TimeZoneFinder.getInstance());
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Returns a {@link CountryTimeZones} object associated with the specified country code.
+ * Caching is handled as needed. If the country code is not recognized or there is an error
+ * during lookup this method can return null.
+ */
+ @Nullable
+ public CountryTimeZones lookupCountryTimeZones(@NonNull String countryIso) {
+ libcore.timezone.CountryTimeZones delegate = mDelegate.lookupCountryTimeZones(countryIso);
+ return delegate == null ? null : new CountryTimeZones(delegate);
+ }
+}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4368115..178b3c0 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
@@ -1010,6 +1011,9 @@
* @return Supported WCG color spaces.
* @hide
*/
+ @SuppressLint("VisiblySynchronized")
+ @NonNull
+ @TestApi
public @ColorMode ColorSpace[] getSupportedWideColorGamut() {
synchronized (this) {
final ColorSpace[] defaultColorSpaces = new ColorSpace[0];
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index c67ff6e..7986ceb 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -128,6 +128,19 @@
}
/**
+ * Called when a focus event is received.
+ *
+ * @param hasFocus if true, the window associated with this input channel has just received
+ * focus
+ * if false, the window associated with this input channel has just lost focus
+ * @param inTouchMode if true, the device is in touch mode
+ * if false, the device is not in touch mode
+ */
+ // Called from native code.
+ public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+ }
+
+ /**
* Called when a batched input event is pending.
*
* The batched input event will continue to accumulate additional movement
@@ -213,8 +226,13 @@
onBatchedInputEventPending();
}
- public static interface Factory {
- public InputEventReceiver createInputEventReceiver(
- InputChannel inputChannel, Looper looper);
+ /**
+ * Factory for InputEventReceiver
+ */
+ public interface Factory {
+ /**
+ * Create a new InputReceiver for a given inputChannel
+ */
+ InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper);
}
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1782544..0de1a4f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -42,6 +42,7 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceControl.Transaction;
+import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.view.SurfaceCallbackHelper;
@@ -201,6 +202,9 @@
private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
private int mParentSurfaceGenerationId;
+ // The token of embedded windowless view hierarchy.
+ private IBinder mEmbeddedViewHierarchy;
+
public SurfaceView(Context context) {
this(context, null);
}
@@ -1531,4 +1535,27 @@
if (viewRoot == null) return;
viewRoot.setUseBLASTSyncTransaction();
}
+
+ /**
+ * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view
+ * hierarchy.
+ *
+ * @param token IBinder token.
+ * @hide
+ */
+ public void setEmbeddedViewHierarchy(IBinder token) {
+ mEmbeddedViewHierarchy = token;
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
+ if (mEmbeddedViewHierarchy == null) {
+ return;
+ }
+ // Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this
+ // leashed child would return the root node in the embedded hierarchy
+ info.addChild(mEmbeddedViewHierarchy);
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e0f6e06..2ef944f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8044,6 +8044,11 @@
}
@Override
+ public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+ windowFocusChanged(hasFocus, inTouchMode);
+ }
+
+ @Override
public void dispose() {
unscheduleConsumeBatchedInput();
super.dispose();
diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java
index 253794f..c55caa3 100644
--- a/core/java/android/view/WindowContainerTransaction.java
+++ b/core/java/android/view/WindowContainerTransaction.java
@@ -74,6 +74,18 @@
return this;
}
+ /**
+ * Sets whether a container or any of its children can be focusable. When {@code false}, no
+ * child can be focused; however, when {@code true}, it is still possible for children to be
+ * non-focusable due to WM policy.
+ */
+ public WindowContainerTransaction setFocusable(IWindowContainer container, boolean focusable) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mFocusable = focusable;
+ chg.mChangeMask |= Change.CHANGE_FOCUSABLE;
+ return this;
+ }
+
public Map<IBinder, Change> getChanges() {
return mChanges;
}
@@ -112,7 +124,11 @@
* @hide
*/
public static class Change implements Parcelable {
+ public static final int CHANGE_FOCUSABLE = 1;
+
private final Configuration mConfiguration = new Configuration();
+ private boolean mFocusable = true;
+ private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
@@ -123,6 +139,8 @@
protected Change(Parcel in) {
mConfiguration.readFromParcel(in);
+ mFocusable = in.readBoolean();
+ mChangeMask = in.readInt();
mConfigSetMask = in.readInt();
mWindowSetMask = in.readInt();
mSchedulePipCallback = (in.readInt() != 0);
@@ -136,6 +154,18 @@
return mConfiguration;
}
+ /** Gets the requested focusable value */
+ public boolean getFocusable() {
+ if ((mChangeMask & CHANGE_FOCUSABLE) == 0) {
+ throw new RuntimeException("Focusable not set. check CHANGE_FOCUSABLE first");
+ }
+ return mFocusable;
+ }
+
+ public int getChangeMask() {
+ return mChangeMask;
+ }
+
@ActivityInfo.Config
public int getConfigSetMask() {
return mConfigSetMask;
@@ -170,6 +200,9 @@
if (changesSss) {
sb.append("ssw:" + mConfiguration.smallestScreenWidthDp + ",");
}
+ if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
+ sb.append("focusable:" + mFocusable + ",");
+ }
sb.append("}");
return sb.toString();
}
@@ -177,6 +210,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
mConfiguration.writeToParcel(dest, flags);
+ dest.writeBoolean(mFocusable);
+ dest.writeInt(mChangeMask);
dest.writeInt(mConfigSetMask);
dest.writeInt(mWindowSetMask);
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 914ff18..b9f08ad 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -17,10 +17,14 @@
package android.view.accessibility;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -337,6 +341,48 @@
return emptyWindows;
}
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by accessibility id and given leash token instead of
+ * window id. This method is used to find the leashed node on the embedded view hierarchy.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param leashToken The token of the embedded hierarchy.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param bypassCache Whether to bypass the cache while looking for the node.
+ * @param prefetchFlags flags to guide prefetching.
+ * @param arguments Optional action arguments.
+ * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+ */
+ public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
+ int connectionId, @NonNull IBinder leashToken, long accessibilityNodeId,
+ boolean bypassCache, int prefetchFlags, Bundle arguments) {
+ if (leashToken == null) {
+ return null;
+ }
+ int windowId = -1;
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ windowId = connection.getWindowIdForLeashToken(leashToken);
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while calling remote getWindowIdForLeashToken", re);
+ }
+ if (windowId == -1) {
+ return null;
+ }
+ return findAccessibilityNodeInfoByAccessibilityId(connectionId, windowId,
+ accessibilityNodeId, bypassCache, prefetchFlags, arguments);
+ }
+
/**
* Finds an {@link AccessibilityNodeInfo} by accessibility id.
*
@@ -783,6 +829,31 @@
}
/**
+ * Takes the screenshot of the specified display and returns it by bitmap format.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @return The screenshot bitmap on success, null otherwise.
+ */
+ public Bitmap takeScreenshot(int connectionId, int displayId) {
+ Bitmap screenShot = null;
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ screenShot = connection.takeScreenshot(displayId);
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling remote takeScreenshot", re);
+ }
+ return screenShot;
+ }
+
+ /**
* Clears the result state.
*/
private void clearResultLocked() {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 2e5a4b5..3dfeffb 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1195,6 +1195,19 @@
@TestApi
@RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
public void performAccessibilityShortcut() {
+ performAccessibilityShortcut(null);
+ }
+
+ /**
+ * Perform the accessibility shortcut for the given target which is assigned to the shortcut.
+ *
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ public void performAccessibilityShortcut(@Nullable String targetName) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
@@ -1203,7 +1216,7 @@
}
}
try {
- service.performAccessibilityShortcut();
+ service.performAccessibilityShortcut(targetName);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
}
@@ -1270,7 +1283,22 @@
* @param displayId The logical display id.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonClicked(int displayId) {
+ notifyAccessibilityButtonClicked(displayId, null);
+ }
+
+ /**
+ * Perform the accessibility button for the given target which is assigned to the button.
+ *
+ * @param displayId displayId The logical display id.
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+ public void notifyAccessibilityButtonClicked(int displayId, @Nullable String targetName) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
@@ -1279,7 +1307,7 @@
}
}
try {
- service.notifyAccessibilityButtonClicked(displayId);
+ service.notifyAccessibilityButtonClicked(displayId, targetName);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 92aa7d5..184f3302 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -31,6 +31,7 @@
import android.graphics.Region;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputType;
@@ -110,6 +111,9 @@
public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1;
/** @hide */
+ public static final int LEASHED_ITEM_ID = Integer.MAX_VALUE - 2;
+
+ /** @hide */
public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);
/** @hide */
@@ -117,6 +121,10 @@
AccessibilityNodeProvider.HOST_VIEW_ID);
/** @hide */
+ public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID,
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+
+ /** @hide */
public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
/** @hide */
@@ -788,6 +796,10 @@
private TouchDelegateInfo mTouchDelegateInfo;
+ private IBinder mLeashedChild;
+ private IBinder mLeashedParent;
+ private long mLeashedParentNodeId = UNDEFINED_NODE_ID;
+
/**
* Creates a new {@link AccessibilityNodeInfo}.
*/
@@ -1039,7 +1051,12 @@
return null;
}
final long childId = mChildNodeIds.get(index);
- AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ if (mLeashedChild != null && childId == LEASHED_NODE_ID) {
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild,
+ ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null);
+ }
+
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
childId, false, FLAG_PREFETCH_DESCENDANTS, null);
}
@@ -1062,6 +1079,43 @@
}
/**
+ * Adds a view root from leashed content as a child. This method is used to embedded another
+ * view hierarchy.
+ * <p>
+ * <strong>Note:</strong> Only one leashed child is permitted.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * Note that a view cannot be made its own child.
+ * </p>
+ *
+ * @param token The token to which a view root is added.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @hide
+ */
+ @TestApi
+ public void addChild(@NonNull IBinder token) {
+ enforceNotSealed();
+ if (token == null) {
+ return;
+ }
+ if (mChildNodeIds == null) {
+ mChildNodeIds = new LongArray();
+ }
+
+ mLeashedChild = token;
+ // Checking uniqueness.
+ // Since only one leashed child is permitted, skip adding ID if the ID already exists.
+ if (mChildNodeIds.indexOf(LEASHED_NODE_ID) >= 0) {
+ return;
+ }
+ mChildNodeIds.add(LEASHED_NODE_ID);
+ }
+
+ /**
* Unchecked version of {@link #addChild(View)} that does not verify
* uniqueness. For framework use only.
*
@@ -1090,6 +1144,38 @@
}
/**
+ * Removes a leashed child. If the child was not previously added to the node,
+ * calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param token The token of the leashed child
+ * @return true if the child was present
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @hide
+ */
+ public boolean removeChild(IBinder token) {
+ enforceNotSealed();
+ if (mChildNodeIds == null || mLeashedChild == null) {
+ return false;
+ }
+ if (!mLeashedChild.equals(token)) {
+ return false;
+ }
+ final int index = mChildNodeIds.indexOf(LEASHED_NODE_ID);
+ mLeashedChild = null;
+ if (index < 0) {
+ return false;
+ }
+ mChildNodeIds.remove(index);
+ return true;
+ }
+
+ /**
* Adds a virtual child which is a descendant of the given <code>root</code>.
* If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
* is added as a child.
@@ -1668,6 +1754,9 @@
*/
public AccessibilityNodeInfo getParent() {
enforceSealed();
+ if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
+ return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId);
+ }
return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId);
}
@@ -3257,6 +3346,40 @@
}
/**
+ * Sets the token and node id of the leashed parent.
+ *
+ * @param token The token.
+ * @param viewId The accessibility view id.
+ * @hide
+ */
+ @TestApi
+ public void setLeashedParent(@Nullable IBinder token, int viewId) {
+ enforceNotSealed();
+ mLeashedParent = token;
+ mLeashedParentNodeId = makeNodeId(viewId, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Gets the token of the leashed parent.
+ *
+ * @return The token.
+ * @hide
+ */
+ public @Nullable IBinder getLeashedParent() {
+ return mLeashedParent;
+ }
+
+ /**
+ * Gets the node id of the leashed parent.
+ *
+ * @return The accessibility node id.
+ * @hide
+ */
+ public long getLeashedParentNodeId() {
+ return mLeashedParentNodeId;
+ }
+
+ /**
* Sets if this instance is sealed.
*
* @param sealed Whether is sealed.
@@ -3559,6 +3682,18 @@
if (!Objects.equals(mTouchDelegateInfo, DEFAULT.mTouchDelegateInfo)) {
nonDefaultFields |= bitAt(fieldIndex);
}
+ fieldIndex++;
+ if (mLeashedChild != DEFAULT.mLeashedChild) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mLeashedParent != DEFAULT.mLeashedParent) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mLeashedParentNodeId != DEFAULT.mLeashedParentNodeId) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
int totalFields = fieldIndex;
parcel.writeLong(nonDefaultFields);
@@ -3685,6 +3820,16 @@
mTouchDelegateInfo.writeToParcel(parcel, flags);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeStrongBinder(mLeashedChild);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeStrongBinder(mLeashedParent);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeLong(mLeashedParentNodeId);
+ }
+
if (DEBUG) {
fieldIndex--;
if (totalFields != fieldIndex) {
@@ -3768,6 +3913,10 @@
final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo;
mTouchDelegateInfo = (otherInfo != null)
? new TouchDelegateInfo(otherInfo.mTargetMap, true) : null;
+
+ mLeashedChild = other.mLeashedChild;
+ mLeashedParent = other.mLeashedParent;
+ mLeashedParentNodeId = other.mLeashedParentNodeId;
}
private void initPoolingInfos(AccessibilityNodeInfo other) {
@@ -3921,6 +4070,16 @@
mTouchDelegateInfo = TouchDelegateInfo.CREATOR.createFromParcel(parcel);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mLeashedChild = parcel.readStrongBinder();
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mLeashedParent = parcel.readStrongBinder();
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mLeashedParentNodeId = parcel.readLong();
+ }
+
mSealed = sealed;
}
@@ -4200,6 +4359,19 @@
| FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
}
+ private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+ IBinder leashToken, long accessibilityId) {
+ if (!((leashToken != null)
+ && (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID)
+ && (connectionId != UNDEFINED_CONNECTION_ID))) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
+ leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
+ | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+ }
+
/** @hide */
public static String idToString(long accessibilityId) {
int accessibilityViewId = getAccessibilityViewId(accessibilityId);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 392db57..fcaaa2e 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -66,12 +66,12 @@
// Used by UiAutomation
IBinder getWindowToken(int windowId, int userId);
- void notifyAccessibilityButtonClicked(int displayId);
+ void notifyAccessibilityButtonClicked(int displayId, String targetName);
void notifyAccessibilityButtonVisibilityChanged(boolean available);
// Requires Manifest.permission.MANAGE_ACCESSIBILITY
- void performAccessibilityShortcut();
+ void performAccessibilityShortcut(String targetName);
// Requires Manifest.permission.MANAGE_ACCESSIBILITY
List<String> getAccessibilityShortcutTargets(int shortcutType);
diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java
new file mode 100644
index 0000000..fe8bbb8
--- /dev/null
+++ b/core/java/android/webkit/PacProcessor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+
+/**
+ * Class to evaluate PAC scripts.
+ * @hide
+ */
+
+@SystemApi
+public interface PacProcessor {
+
+ /**
+ * Returns the default PacProcessor instance.
+ *
+ * @return the default PacProcessor instance.
+ */
+ @NonNull
+ static PacProcessor getInstance() {
+ return WebViewFactory.getProvider().getPacProcessor();
+ }
+
+ /**
+ * Set PAC script to use.
+ *
+ * @param script PAC script.
+ * @return true if PAC script is successfully set.
+ */
+ boolean setProxyScript(@NonNull String script);
+
+ /**
+ * Gets a list of proxy servers to use.
+ * @param url The URL being accessed.
+ * @return a PAC-style semicolon-separated list of valid proxy servers.
+ * For example: "PROXY xxx.xxx.xxx.xxx:xx; SOCKS yyy.yyy.yyy:yy".
+ */
+ @Nullable
+ String makeProxyRequest(@NonNull String url);
+}
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 6a1ed39..f7c3ec0 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -175,6 +175,15 @@
WebViewDatabase getWebViewDatabase(Context context);
/**
+ * Gets the singleton PacProcessor instance.
+ * @return the PacProcessor instance
+ */
+ @NonNull
+ default PacProcessor getPacProcessor() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ /**
* Gets the classloader used to load internal WebView implementation classes. This interface
* should only be used by the WebView Support Library.
*/
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f851e10..b891af5 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -152,7 +152,7 @@
// Specifies whether to allow starting a cursor drag by dragging anywhere over the text.
@VisibleForTesting
- public static boolean FLAG_ENABLE_CURSOR_DRAG = false;
+ public static boolean FLAG_ENABLE_CURSOR_DRAG = true;
// Specifies whether to use the magnifier when pressing the insertion or selection handles.
private static final boolean FLAG_USE_MAGNIFIER = true;
@@ -5741,10 +5741,10 @@
return;
}
switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mIsDraggingCursor = false;
- break;
case MotionEvent.ACTION_MOVE:
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ break;
+ }
if (mIsDraggingCursor) {
performCursorDrag(event);
} else if (FLAG_ENABLE_CURSOR_DRAG
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index 6277afe..b13ca42 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -173,6 +173,13 @@
mIsDragCloseToVertical = (4 * deltaXSquared) <= distanceSquared;
}
}
+ } else if (action == MotionEvent.ACTION_CANCEL) {
+ mLastDownMillis = 0;
+ mLastUpMillis = 0;
+ mMultiTapStatus = MultiTapStatus.NONE;
+ mMultiTapInSameArea = false;
+ mMovedEnoughForDrag = false;
+ mIsDragCloseToVertical = false;
}
}
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 5731e50..8565493 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -422,7 +422,7 @@
/**
* Update the displayed time if necessary and invalidate the view.
*/
- public void refresh() {
+ public void refreshTime() {
onTimeChanged();
invalidate();
}
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 9bdb4c1..d119b2e 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -16,6 +16,8 @@
package android.widget;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,8 +44,12 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.annotations.GuardedBy;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* A toast is a view containing a quick little message for the user. The toast class
@@ -262,6 +268,29 @@
}
/**
+ * Adds a callback to be notified when the toast is shown or hidden.
+ *
+ * Note that if the toast is blocked for some reason you won't get a call back.
+ *
+ * @see #removeCallback(Callback)
+ */
+ public void addCallback(@NonNull Callback callback) {
+ checkNotNull(callback);
+ synchronized (mTN.mCallbacks) {
+ mTN.mCallbacks.add(callback);
+ }
+ }
+
+ /**
+ * Removes a callback previously added with {@link #addCallback(Callback)}.
+ */
+ public void removeCallback(@NonNull Callback callback) {
+ synchronized (mTN.mCallbacks) {
+ mTN.mCallbacks.remove(callback);
+ }
+ }
+
+ /**
* Gets the LayoutParams for the Toast window.
* @hide
*/
@@ -389,6 +418,9 @@
String mPackageName;
+ @GuardedBy("mCallbacks")
+ private final List<Callback> mCallbacks = new ArrayList<>();
+
static final long SHORT_DURATION_TIMEOUT = 4000;
static final long LONG_DURATION_TIMEOUT = 7000;
@@ -449,6 +481,12 @@
};
}
+ private List<Callback> getCallbacks() {
+ synchronized (mCallbacks) {
+ return new ArrayList<>(mCallbacks);
+ }
+ }
+
/**
* schedule handleShow into the right thread
*/
@@ -522,6 +560,9 @@
try {
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
+ for (Callback callback : getCallbacks()) {
+ callback.onToastShown();
+ }
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
@@ -564,8 +605,30 @@
} catch (RemoteException e) {
}
+ for (Callback callback : getCallbacks()) {
+ callback.onToastHidden();
+ }
mView = null;
}
}
}
+
+ /**
+ * Callback object to be called when the toast is shown or hidden.
+ *
+ * Callback methods will be called on the looper thread provided on construction.
+ *
+ * @see #addCallback(Callback)
+ */
+ public abstract static class Callback {
+ /**
+ * Called when the toast is displayed on the screen.
+ */
+ public void onToastShown() {}
+
+ /**
+ * Called when the toast is hidden.
+ */
+ public void onToastHidden() {}
+ }
}
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index de204ba..a9f0175 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -19,6 +19,15 @@
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static android.view.accessibility.AccessibilityManager.ShortcutType;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.COMPONENT_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.FRAGMENT_TYPE;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.ICON_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.LABEL_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.SETTINGS_KEY;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -63,10 +72,6 @@
* Activity used to display and persist a service or feature target for the Accessibility button.
*/
public class AccessibilityButtonChooserActivity extends Activity {
-
- private static final String MAGNIFICATION_COMPONENT_ID =
- "com.android.server.accessibility.MagnificationController";
-
private static final char SERVICES_SEPARATOR = ':';
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
@@ -76,7 +81,7 @@
ACCESSIBILITY_SHORTCUT_KEY);
private int mShortcutType;
- private List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
+ private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
private AlertDialog mAlertDialog;
private TargetAdapter mTargetAdapter;
@@ -99,7 +104,7 @@
UserShortcutType.TRIPLETAP,
})
/** Denotes the user shortcut type. */
- public @interface UserShortcutType {
+ private @interface UserShortcutType {
int DEFAULT = 0;
int SOFTWARE = 1; // 1 << 0
int HARDWARE = 2; // 1 << 1
@@ -122,7 +127,7 @@
AccessibilityServiceFragmentType.INTUITIVE,
AccessibilityServiceFragmentType.BOUNCE,
})
- public @interface AccessibilityServiceFragmentType {
+ private @interface AccessibilityServiceFragmentType {
int LEGACY = 0;
int INVISIBLE = 1;
int INTUITIVE = 2;
@@ -140,11 +145,61 @@
ShortcutMenuMode.LAUNCH,
ShortcutMenuMode.EDIT,
})
- public @interface ShortcutMenuMode {
+ private @interface ShortcutMenuMode {
int LAUNCH = 0;
int EDIT = 1;
}
+ /**
+ * Annotation for align the element index of white listing feature
+ * {@code WHITE_LISTING_FEATURES}.
+ *
+ * {@code COMPONENT_ID} is to get the service component name.
+ * {@code LABEL_ID} is to get the service label text.
+ * {@code ICON_ID} is to get the service icon.
+ * {@code FRAGMENT_TYPE} is to get the service fragment type.
+ * {@code SETTINGS_KEY} is to get the service settings key.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ WhiteListingFeatureElementIndex.COMPONENT_ID,
+ WhiteListingFeatureElementIndex.LABEL_ID,
+ WhiteListingFeatureElementIndex.ICON_ID,
+ WhiteListingFeatureElementIndex.FRAGMENT_TYPE,
+ WhiteListingFeatureElementIndex.SETTINGS_KEY,
+ })
+ @interface WhiteListingFeatureElementIndex {
+ int COMPONENT_ID = 0;
+ int LABEL_ID = 1;
+ int ICON_ID = 2;
+ int FRAGMENT_TYPE = 3;
+ int SETTINGS_KEY = 4;
+ }
+
+ private static final String[][] WHITE_LISTING_FEATURES = {
+ {
+ COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
+ String.valueOf(R.string.color_inversion_feature_name),
+ String.valueOf(R.drawable.ic_accessibility_color_inversion),
+ String.valueOf(AccessibilityServiceFragmentType.INTUITIVE),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+ },
+ {
+ DALTONIZER_COMPONENT_NAME.flattenToString(),
+ String.valueOf(R.string.color_correction_feature_name),
+ String.valueOf(R.drawable.ic_accessibility_color_correction),
+ String.valueOf(AccessibilityServiceFragmentType.INTUITIVE),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+ },
+ {
+ MAGNIFICATION_CONTROLLER_NAME,
+ String.valueOf(R.string.accessibility_magnification_chooser_text),
+ String.valueOf(R.drawable.ic_accessibility_magnification),
+ String.valueOf(AccessibilityServiceFragmentType.INVISIBLE),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+ },
+ };
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -199,6 +254,20 @@
private static List<AccessibilityButtonTarget> getServiceTargets(@NonNull Context context,
@ShortcutType int shortcutType) {
+ final List<AccessibilityButtonTarget> targets = new ArrayList<>();
+ targets.addAll(getAccessibilityServiceTargets(context));
+ targets.addAll(getWhiteListingServiceTargets(context));
+
+ final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
+ targets.removeIf(target -> !requiredTargets.contains(target.getId()));
+
+ return targets;
+ }
+
+ private static List<AccessibilityButtonTarget> getAccessibilityServiceTargets(
+ @NonNull Context context) {
final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> installedServices =
@@ -209,29 +278,74 @@
final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size());
for (AccessibilityServiceInfo info : installedServices) {
- if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
- targets.add(new AccessibilityButtonTarget(context, info));
- }
- }
-
- final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
- targets.removeIf(target -> !requiredTargets.contains(target.getId()));
-
- // TODO(b/146815874): Will replace it with white list services.
- if (Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) {
- final AccessibilityButtonTarget magnificationTarget = new AccessibilityButtonTarget(
- context,
- MAGNIFICATION_COMPONENT_ID,
- R.string.accessibility_magnification_chooser_text,
- R.drawable.ic_accessibility_magnification,
- AccessibilityServiceFragmentType.INTUITIVE);
- targets.add(magnificationTarget);
+ targets.add(new AccessibilityButtonTarget(context, info));
}
return targets;
}
+ private static List<AccessibilityButtonTarget> getWhiteListingServiceTargets(
+ @NonNull Context context) {
+ final List<AccessibilityButtonTarget> targets = new ArrayList<>();
+
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ final AccessibilityButtonTarget target = new AccessibilityButtonTarget(
+ context,
+ WHITE_LISTING_FEATURES[i][COMPONENT_ID],
+ Integer.parseInt(WHITE_LISTING_FEATURES[i][LABEL_ID]),
+ Integer.parseInt(WHITE_LISTING_FEATURES[i][ICON_ID]),
+ Integer.parseInt(WHITE_LISTING_FEATURES[i][FRAGMENT_TYPE]));
+ targets.add(target);
+ }
+
+ return targets;
+ }
+
+ private static boolean isWhiteListingServiceEnabled(@NonNull Context context,
+ AccessibilityButtonTarget target) {
+
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(target.getId())) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ WHITE_LISTING_FEATURES[i][SETTINGS_KEY],
+ /* settingsValueOff */ 0) == /* settingsValueOn */ 1;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isWhiteListingService(String componentId) {
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void setWhiteListingServiceEnabled(String componentId, int settingsValue) {
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
+ Settings.Secure.putInt(getContentResolver(),
+ WHITE_LISTING_FEATURES[i][SETTINGS_KEY], settingsValue);
+ return;
+ }
+ }
+ }
+
+ private void disableService(ComponentName componentName) {
+ final String componentId = componentName.flattenToString();
+
+ if (isWhiteListingService(componentId)) {
+ setWhiteListingServiceEnabled(componentName.flattenToString(),
+ /* settingsValueOff */ 0);
+ } else {
+ setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+ }
+ }
+
private static class ViewHolder {
ImageView mIconView;
TextView mLabelView;
@@ -350,11 +464,14 @@
private void updateIntuitiveActionItemVisibility(@NonNull Context context,
@NonNull ViewHolder holder, AccessibilityButtonTarget target) {
final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
+ final boolean isServiceEnabled = isWhiteListingService(target.getId())
+ ? isWhiteListingServiceEnabled(context, target)
+ : isAccessibilityServiceEnabled(context, target);
holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
- holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled(context, target));
+ holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled);
holder.mItemContainer.setVisibility(View.VISIBLE);
}
@@ -411,7 +528,7 @@
}
}
- private static boolean isServiceEnabled(@NonNull Context context,
+ private static boolean isAccessibilityServiceEnabled(@NonNull Context context,
AccessibilityButtonTarget target) {
final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
@@ -429,13 +546,65 @@
}
private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
- Settings.Secure.putString(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
- mTargets.get(position).getId());
- // TODO(b/146969684): notify accessibility button clicked.
+ final AccessibilityButtonTarget target = mTargets.get(position);
+ switch (target.getFragmentType()) {
+ case AccessibilityServiceFragmentType.LEGACY:
+ onLegacyTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.INVISIBLE:
+ onInvisibleTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.INTUITIVE:
+ onIntuitiveTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.BOUNCE:
+ // Do nothing
+ break;
+ default:
+ throw new IllegalStateException("Unexpected fragment type");
+ }
+
mAlertDialog.dismiss();
}
+ private void onLegacyTargetSelected(AccessibilityButtonTarget target) {
+ if (mShortcutType == ACCESSIBILITY_BUTTON) {
+ final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+ } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ switchServiceState(target);
+ } else {
+ throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+ }
+ }
+
+ private void onInvisibleTargetSelected(AccessibilityButtonTarget target) {
+ final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+ }
+
+ private void onIntuitiveTargetSelected(AccessibilityButtonTarget target) {
+ switchServiceState(target);
+ }
+
+ private void switchServiceState(AccessibilityButtonTarget target) {
+ final ComponentName componentName =
+ ComponentName.unflattenFromString(target.getId());
+ final String componentId = componentName.flattenToString();
+
+ if (isWhiteListingService(componentId)) {
+ setWhiteListingServiceEnabled(componentId,
+ isWhiteListingServiceEnabled(this, target)
+ ? /* settingsValueOff */ 0
+ : /* settingsValueOn */ 1);
+ } else {
+ setAccessibilityServiceState(this, componentName,
+ /* enabled= */!isAccessibilityServiceEnabled(this, target));
+ }
+ }
+
private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
final AccessibilityButtonTarget target = mTargets.get(position);
final ComponentName targetComponentName =
@@ -470,14 +639,14 @@
if (!hasValueInSettings(this,
ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) {
- setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+ disableService(componentName);
}
} else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
if (!hasValueInSettings(this,
ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) {
- setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+ disableService(componentName);
}
} else {
throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
diff --git a/core/java/com/android/internal/app/BlockedAppActivity.java b/core/java/com/android/internal/app/BlockedAppActivity.java
new file mode 100644
index 0000000..fbdbbfb
--- /dev/null
+++ b/core/java/com/android/internal/app/BlockedAppActivity.java
@@ -0,0 +1,86 @@
+/*
+ * 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.internal.app;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+/**
+ * A dialog shown to the user when they try to launch an app that is not allowed in lock task
+ * mode. The intent to start this activity must be created with the static factory method provided
+ * below.
+ */
+public class BlockedAppActivity extends AlertActivity {
+
+ private static final String TAG = "BlockedAppActivity";
+ private static final String PACKAGE_NAME = "com.android.internal.app";
+ private static final String EXTRA_BLOCKED_PACKAGE = PACKAGE_NAME + ".extra.BLOCKED_PACKAGE";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, /* defaultValue= */ -1);
+ if (userId < 0) {
+ Slog.wtf(TAG, "Invalid user: " + userId);
+ finish();
+ return;
+ }
+
+ String packageName = intent.getStringExtra(EXTRA_BLOCKED_PACKAGE);
+ if (TextUtils.isEmpty(packageName)) {
+ Slog.wtf(TAG, "Invalid package: " + packageName);
+ finish();
+ return;
+ }
+
+ CharSequence appLabel = getAppLabel(userId, packageName);
+
+ mAlertParams.mTitle = getString(R.string.app_blocked_title);
+ mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
+ mAlertParams.mPositiveButtonText = getString(android.R.string.ok);
+ setupAlert();
+ }
+
+ private CharSequence getAppLabel(int userId, String packageName) {
+ PackageManager pm = getPackageManager();
+ try {
+ ApplicationInfo aInfo =
+ pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId);
+ return aInfo.loadLabel(pm);
+ } catch (PackageManager.NameNotFoundException ne) {
+ Slog.e(TAG, "Package " + packageName + " not found", ne);
+ }
+ return packageName;
+ }
+
+
+ /** Creates an intent that launches {@link BlockedAppActivity}. */
+ public static Intent createIntent(int userId, String packageName) {
+ return new Intent()
+ .setClassName("android", BlockedAppActivity.class.getName())
+ .putExtra(Intent.EXTRA_USER_ID, userId)
+ .putExtra(EXTRA_BLOCKED_PACKAGE, packageName);
+ }
+}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 53327bc..c4ee195 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2627,7 +2627,7 @@
gMidAudioRecordRoutingProxy_release =
android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "native_release", "()V");
- AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback);
+ AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
return RegisterMethodsOrDie(env, kEventHandlerClassPathName, gEventHandlerMethods,
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 59dab0c8..649e5d2 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -40,10 +40,15 @@
static const bool kDebugDispatchCycle = false;
+static const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
static struct {
jclass clazz;
jmethodID dispatchInputEvent;
+ jmethodID onFocusEvent;
jmethodID dispatchBatchedInputEventPending;
} gInputEventReceiverClassInfo;
@@ -219,8 +224,7 @@
bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64,
- getInputChannelName().c_str(),
- consumeBatches ? "true" : "false", frameTime);
+ getInputChannelName().c_str(), toString(consumeBatches), frameTime);
}
if (consumeBatches) {
@@ -235,6 +239,7 @@
for (;;) {
uint32_t seq;
InputEvent* inputEvent;
+
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent);
if (status) {
@@ -302,6 +307,19 @@
inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
break;
}
+ case AINPUT_EVENT_TYPE_FOCUS: {
+ FocusEvent* focusEvent = static_cast<FocusEvent*>(inputEvent);
+ if (kDebugDispatchCycle) {
+ ALOGD("channel '%s' ~ Received focus event: hasFocus=%s, inTouchMode=%s.",
+ getInputChannelName().c_str(), toString(focusEvent->getHasFocus()),
+ toString(focusEvent->getInTouchMode()));
+ }
+ env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onFocusEvent,
+ jboolean(focusEvent->getHasFocus()),
+ jboolean(focusEvent->getInTouchMode()));
+ finishInputEvent(seq, true /* handled */);
+ return OK;
+ }
default:
assert(false); // InputConsumer should prevent this from ever happening
@@ -421,6 +439,8 @@
gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz,
"dispatchInputEvent", "(ILandroid/view/InputEvent;)V");
+ gInputEventReceiverClassInfo.onFocusEvent =
+ GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onFocusEvent", "(ZZ)V");
gInputEventReceiverClassInfo.dispatchBatchedInputEventPending = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz, "dispatchBatchedInputEventPending", "()V");
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 673772a..39ea45a 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1431,7 +1431,7 @@
// Isolated process / webview / app zygote should be gated by SELinux and file permission
// so they can't even traverse CE / DE directories.
if (pkg_data_info_list != nullptr
- && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+ && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name,
fail_fn);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7ecd9b7..2665e8a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4976,6 +4976,12 @@
android:process=":ui">
</activity>
+ <activity android:name="com.android.internal.app.BlockedAppActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
<activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true">
diff --git a/core/res/res/drawable/ic_accessibility_color_correction.xml b/core/res/res/drawable/ic_accessibility_color_correction.xml
new file mode 100644
index 0000000..02fa4b8
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_color_correction.xml
@@ -0,0 +1,37 @@
+<vector android:height="24dp" android:viewportHeight="192"
+ android:viewportWidth="192" android:width="24dp"
+ xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#00BCD4" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M183.35,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.37c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.92,82.13 183.73,83.39 183.35,84.63z" android:strokeAlpha="0.2"/>
+ <path android:pathData="M60.01,135L60.01,135H60l48.04,49h33.17c6.28,0 11.82,-4.13 13.67,-10.19l18.68,-57.84L129.51,72.2l-0.01,0c0.27,0.27 0.52,0.5 0.77,0.77l0.55,0.55c1.57,1.56 1.57,4.08 -0.04,5.68l-12.56,12.49l6.36,6.3l1.38,1.37l-5.67,5.64l-5.71,-5.68L79.15,135H60.42H60.01z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="155.9627" android:endY="165.1493"
+ android:startX="98.4649" android:startY="107.6516" android:type="linear">
+ <item android:color="#19263238" android:offset="0"/>
+ <item android:color="#00212121" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path android:fillColor="#0097A7" android:pathData="M68.55,120.35l32.173,-32.173l7.658,7.658l-32.173,32.173z"/>
+ <path android:fillColor="#FFFFFF" android:pathData="M130.83,73.52l-9.42,-9.36c-1.57,-1.56 -4.1,-1.56 -5.67,0l-12.56,12.48L95.42,69l-5.67,5.64l5.71,5.68L60,116.97V135h19.15l35.42,-35.68l5.71,5.68l5.67,-5.64l-7.73,-7.68l12.56,-12.48C132.4,77.6 132.4,75.08 130.83,73.52zM74.98,126.77l-6.43,-6.43l32.17,-32.17l6.43,6.43L74.98,126.77z"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M120.28,105l-5.71,-5.68l-35.42,35.68l-19.15,0l1,1l19.15,0l35.42,-35.68l5.71,5.68l5.68,-5.64l-1,-1z" android:strokeAlpha="0.1"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M90.75,75.64l-0.01,0l4.71,4.68l0.01,0z" android:strokeAlpha="0.1"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M131.83,74.52l-0.97,-0.97c1.54,1.56 1.53,4.06 -0.07,5.65l-12.56,12.48l1,1l12.55,-12.48C133.4,78.6 133.4,76.08 131.83,74.52z" android:strokeAlpha="0.1"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M101.72,89.17l6.67,6.66l0,0l-7.67,-7.66l-32.17,32.17l1,1z" android:strokeAlpha="0.1"/>
+ <path android:pathData="M37.13,173.7L8.62,83.69c-1.7,-5.8 0.3,-12 5,-15.6l73.52,-57.1c5.2,-4 12.5,-4 17.6,0.1l73.82,58.9c4.6,3.7 6.5,9.9 4.8,15.6l-28.51,88.21c-1.8,6.1 -7.4,10.2 -13.7,10.2H50.83C44.53,184 38.93,179.8 37.13,173.7z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:centerX="21.977" android:centerY="23.8809"
+ android:gradientRadius="158.0384" android:type="radial">
+ <item android:color="#19FFFFFF" android:offset="0"/>
+ <item android:color="#00FFFFFF" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+</vector>
diff --git a/core/res/res/drawable/ic_accessibility_color_inversion.xml b/core/res/res/drawable/ic_accessibility_color_inversion.xml
new file mode 100644
index 0000000..97b30b0
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_color_inversion.xml
@@ -0,0 +1,47 @@
+<vector android:height="24dp" android:viewportHeight="192"
+ android:viewportWidth="192" android:width="24dp"
+ xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#546E7A" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M183.37,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.38c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.94,82.13 183.74,83.39 183.37,84.63z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M53,130.05l5.03,-4.79L44.16,112c-0.05,0.78 -0.14,1.52 -0.15,2.34C43.62,136.61 61.27,154.56 84,156v-5.31C70.13,149.64 58.56,141.54 53,130.05z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M109,52v5.31c13.65,1.05 24.67,9.15 30.11,20.64l-4.92,4.79L147.81,96c0.09,-0.78 0.17,-1.53 0.19,-2.34C148.38,71.39 131.36,53.44 109,52z" android:strokeAlpha="0.2"/>
+ <path android:pathData="M154.89,173.81l13.57,-42.02l-46.53,-46.56C125.75,90.5 128,96.98 128,104c0,17.7 -14.3,32 -32,32c-8.64,0 -16.47,-3.42 -22.22,-8.97l0.9,0.91L130.73,184h10.49C147.5,184 153.04,179.87 154.89,173.81z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="153.3523" android:endY="161.6371"
+ android:startX="90.6075" android:startY="98.8923" android:type="linear">
+ <item android:color="#33263238" android:offset="0"/>
+ <item android:color="#05263238" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path android:pathData="M96,129.6V78.4c-14.11,0 -25.6,11.49 -25.6,25.6S81.89,129.6 96,129.6z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="150.8492" android:endY="164.1401"
+ android:startX="88.1044" android:startY="101.3954" android:type="linear">
+ <item android:color="#33263238" android:offset="0"/>
+ <item android:color="#05263238" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M96,136c-17.53,0 -31.72,-14.04 -31.99,-31.5c0,0.17 -0.01,0.33 -0.01,0.5c0,17.7 14.3,32 32,32s32,-14.3 32,-32c0,-0.17 -0.01,-0.33 -0.01,-0.5C127.72,121.96 113.53,136 96,136z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M70.4,104c0,0.17 0.01,0.33 0.01,0.5C70.68,90.62 82.06,79.4 96,79.4v-1C81.89,78.4 70.4,89.88 70.4,104z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z" android:strokeAlpha="0.2"/>
+ <path android:fillColor="#FFFFFF" android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z"/>
+ <path android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="156.2451" android:endY="171.4516"
+ android:startX="37.0633" android:startY="52.269802" android:type="linear">
+ <item android:color="#19FFFFFF" android:offset="0"/>
+ <item android:color="#00FFFFFF" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+</vector>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9c08728..4475415 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3752,6 +3752,8 @@
</p>
-->
<attr name="canRequestFingerprintGestures" format="boolean" />
+ <!-- Attribute whether the accessibility service wants to be able to take screenshot. -->
+ <attr name="canTakeScreenshot" format="boolean" />
<!-- Animated image of the accessibility service purpose or behavior, to help users
understand how the service can help them.-->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index bf7558c..d437aa1 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -777,4 +777,9 @@
<!-- Assistant handles -->
<dimen name="assist_handle_shadow_radius">2dp</dimen>
+ <!-- For Waterfall Display -->
+ <dimen name="waterfall_display_left_edge_size">0px</dimen>
+ <dimen name="waterfall_display_top_edge_size">0px</dimen>
+ <dimen name="waterfall_display_right_edge_size">0px</dimen>
+ <dimen name="waterfall_display_bottom_edge_size">0px</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6cf6a68..9860830 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3007,6 +3007,7 @@
<public name="featureId" />
<public name="supportsInlineSuggestions" />
<public name="crossProfile" />
+ <public name="canTakeScreenshot"/>
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 63509ac..4354c0d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -794,6 +794,11 @@
<string name="capability_desc_canCaptureFingerprintGestures">Can capture gestures performed on
the device\'s fingerprint sensor.</string>
+ <!-- Title for the capability of an accessibility service to take screenshot. [CHAR LIMIT=32] -->
+ <string name="capability_title_canTakeScreenshot">Take screenshot</string>
+ <!-- Description for the capability of an accessibility service to take screenshot. [CHAR LIMIT=NONE] -->
+ <string name="capability_desc_canTakeScreenshot">Can take a screenshot of the display.</string>
+
<!-- Permissions -->
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -4922,6 +4927,13 @@
<!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
<string name="work_mode_turn_on">Turn on</string>
+ <!-- Title of the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=50] -->
+ <string name="app_blocked_title">App is not available</string>
+ <!-- Default message shown in the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=NONE] -->
+ <string name="app_blocked_message">
+ <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is not available right now.
+ </string>
+
<!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
<string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
<!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9e11749..30dbfc7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3036,6 +3036,9 @@
<java-symbol type="string" name="app_suspended_more_details" />
<java-symbol type="string" name="app_suspended_default_message" />
+ <java-symbol type="string" name="app_blocked_title" />
+ <java-symbol type="string" name="app_blocked_message" />
+
<!-- Used internally for assistant to launch activity transitions -->
<java-symbol type="id" name="cross_task_transition" />
@@ -3214,6 +3217,8 @@
<java-symbol type="string" name="edit_accessibility_shortcut_menu_button" />
<java-symbol type="string" name="cancel_accessibility_shortcut_menu_button" />
+ <java-symbol type="drawable" name="ic_accessibility_color_inversion" />
+ <java-symbol type="drawable" name="ic_accessibility_color_correction" />
<java-symbol type="drawable" name="ic_accessibility_magnification" />
<java-symbol type="drawable" name="ic_delete_item" />
@@ -3801,5 +3806,14 @@
<!-- Assistant handles -->
<java-symbol type="dimen" name="assist_handle_shadow_radius" />
+ <!-- For Waterfall Display -->
+ <java-symbol type="dimen" name="waterfall_display_left_edge_size" />
+ <java-symbol type="dimen" name="waterfall_display_top_edge_size" />
+ <java-symbol type="dimen" name="waterfall_display_right_edge_size" />
+ <java-symbol type="dimen" name="waterfall_display_bottom_edge_size" />
+
+ <!-- Accessibility take screenshot -->
+ <java-symbol type="string" name="capability_desc_canTakeScreenshot" />
+ <java-symbol type="string" name="capability_title_canTakeScreenshot" />
</resources>
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
new file mode 100644
index 0000000..0be52c1
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema.IndexingConfig;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.IndexingConfig.TokenizerType;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import org.junit.Test;
+
+@SmallTest
+public class AppSearchSchemaTest {
+ @Test
+ public void testSuccess() {
+ AppSearchSchema schema = AppSearchSchema.newBuilder()
+ .addType(AppSearchSchema.newSchemaTypeBuilder("Email")
+ .addProperty(AppSearchSchema.newPropertyBuilder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+ .build()
+ ).build()
+ ).addProperty(AppSearchSchema.newPropertyBuilder("body")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+ .build()
+ ).build()
+ ).build()
+
+ ).addType(AppSearchSchema.newSchemaTypeBuilder("MusicRecording")
+ .addProperty(AppSearchSchema.newPropertyBuilder("artist")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+ .build()
+ ).build()
+ ).addProperty(AppSearchSchema.newPropertyBuilder("pubDate")
+ .setDataType(PropertyConfig.DATA_TYPE_INT64)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_NONE)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_UNKNOWN)
+ .build()
+ ).build()
+ ).build()
+ ).build();
+
+ SchemaProto expectedProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("Email")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("body")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ )
+
+ ).addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("MusicRecording")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("artist")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("pubDate")
+ .setDataType(PropertyConfigProto.DataType.Code.INT64)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.NONE)
+ .setTermMatchType(TermMatchType.Code.UNKNOWN)
+ )
+ )
+ ).build();
+
+ assertThat(schema.getProto()).isEqualTo(expectedProto);
+ }
+
+ @Test
+ public void testInvalidEnums() {
+ PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+ assertThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+ assertThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+ }
+
+ @Test
+ public void testMissingFields() {
+ PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+ Exception e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: dataType");
+
+ builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+ builder.setSchemaType("TestType");
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+ builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+ builder.build();
+ }
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 1bd52af..ade1e0d 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 35;
+ private static final int NUM_MARSHALLED_PROPERTIES = 38;
/**
* The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 93f4b51..f151b81 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -19,9 +19,11 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.graphics.Region;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteCallback;
/**
* Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement
@@ -143,4 +145,14 @@
public IBinder getOverlayWindowToken(int displayId) {
return null;
}
+
+ public int getWindowIdForLeashToken(IBinder token) {
+ return -1;
+ }
+
+ public Bitmap takeScreenshot(int displayId) {
+ return null;
+ }
+
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
}
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index 8c9b4d0..a602fa3 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -32,6 +32,7 @@
import android.app.Activity;
import android.app.Instrumentation;
+import android.view.InputDevice;
import android.view.MotionEvent;
import androidx.test.InstrumentationRegistry;
@@ -282,6 +283,35 @@
}
@Test
+ public void testEditor_onTouchEvent_mouseDrag() throws Throwable {
+ String text = "testEditor_onTouchEvent_mouseDrag";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ TextView tv = mActivity.findViewById(R.id.textview);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a mouse click and drag. This should NOT trigger a cursor drag.
+ long event1Time = 1001;
+ MotionEvent event1 = mouseDownEvent(event1Time, event1Time, 20f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event2Time = 1002;
+ MotionEvent event2 = mouseMoveEvent(event1Time, event2Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+ long event3Time = 1003;
+ MotionEvent event3 = mouseUpEvent(event1Time, event3Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+ }
+
+ @Test
public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
String text = "testEditor_onTouchEvent_cursorDrag";
onView(withId(R.id.textview)).perform(replaceText(text));
@@ -385,4 +415,25 @@
private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) {
return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
}
+
+ private static MotionEvent mouseDownEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = downEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+ return event;
+ }
+
+ private static MotionEvent mouseUpEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = upEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(0);
+ return event;
+ }
+
+ private static MotionEvent mouseMoveEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = moveEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+ return event;
+ }
}
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 215d0b8..3dc001d 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -48,24 +48,32 @@
}
@Test
+ public void testIsDistanceWithin() throws Exception {
+ assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
+ assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
+ assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
+ assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+ }
+
+ @Test
public void testUpdate_singleTap() throws Exception {
// Simulate an ACTION_DOWN event.
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is after the double-tap timeout.
long event3Time = event2Time + ViewConfiguration.getDoubleTapTimeout() + 1;
MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
@Test
@@ -74,13 +82,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -96,13 +104,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -125,13 +133,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout when
// calculated from the last ACTION_UP event time. Even though the time between the last up
@@ -140,7 +148,7 @@
long event3Time = event2Time + 1;
MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
@Test
@@ -149,19 +157,19 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_MOVE event.
long event2Time = 1001;
MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
// Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
long event3Time = 5000;
MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 200f, 31f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout when
// calculated from the last ACTION_UP event time. Even though the time between the last up
@@ -170,7 +178,7 @@
long event4Time = event3Time + 1;
MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
mTouchState.update(event4, mConfig);
- assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false);
+ assertSingleTap(mTouchState, 200f, 31f, 200f, 31f);
}
@Test
@@ -180,14 +188,14 @@
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
event1.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
event2.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -220,13 +228,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -246,7 +254,7 @@
long event5Time = 1004;
MotionEvent event5 = downEvent(event5Time, event5Time, 22f, 32f);
mTouchState.update(event5, mConfig);
- assertSingleTap(mTouchState, 22f, 32f, 21f, 31f, false);
+ assertSingleTap(mTouchState, 22f, 32f, 21f, 31f);
}
@Test
@@ -255,13 +263,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_MOVE event whose location is not far enough to start a drag.
long event2Time = 1001;
MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate another ACTION_MOVE event whose location is far enough to start a drag.
int touchSlop = mConfig.getScaledTouchSlop();
@@ -270,21 +278,135 @@
long event3Time = 1002;
MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
// Simulate an ACTION_UP event.
long event4Time = 1003;
MotionEvent event4 = upEvent(event3Time, event4Time, 200f, 300f);
mTouchState.update(event4, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 200f, 300f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 200f, 300f);
}
@Test
- public void testIsDistanceWithin() throws Exception {
- assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
- assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
- assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
- assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+ public void testUpdate_drag_startsCloseToVerticalThenHorizontal() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+ // Simulate an ACTION_MOVE event that is < 30 deg from vertical.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+ // Simulate another ACTION_MOVE event that is horizontal from the original down event.
+ // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+ // initial direction of movement.
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f);
+ mTouchState.update(event3, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+ // Simulate an ACTION_UP event.
+ long event4Time = 1004;
+ MotionEvent event4 = upEvent(event1Time, event4Time, 200f, 0f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 200f, 0f);
+ }
+
+ @Test
+ public void testUpdate_drag_startsHorizontalThenVertical() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+ // Simulate an ACTION_MOVE event that is > 30 deg from vertical.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 173f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+ // Simulate another ACTION_MOVE event that is vertical from the original down event.
+ // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+ // initial direction of movement.
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f);
+ mTouchState.update(event3, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+ // Simulate an ACTION_UP event.
+ long event4Time = 1004;
+ MotionEvent event4 = upEvent(event1Time, event4Time, 0f, 200f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0f, 200f);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterDown() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate an ACTION_CANCEL event.
+ long event2Time = 1002;
+ MotionEvent event2 = cancelEvent(event1Time, event2Time, 20f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterDrag() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+
+ // Simulate an ACTION_CANCEL event.
+ long event3Time = 1003;
+ MotionEvent event3 = cancelEvent(event1Time, event3Time, 200f, 30f);
+ mTouchState.update(event3, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterMultitap() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate an ACTION_UP event.
+ long event2Time = 1002;
+ MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
+
+ // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
+ long event3Time = 1003;
+ MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+ mTouchState.update(event3, mConfig);
+ assertMultiTap(mTouchState, 22f, 33f, 20f, 30f,
+ MultiTapStatus.DOUBLE_TAP, true);
+
+ // Simulate an ACTION_CANCEL event.
+ long event4Time = 1004;
+ MotionEvent event4 = cancelEvent(event3Time, event4Time, 20f, 30f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
@@ -299,8 +421,12 @@
return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
}
+ private static MotionEvent cancelEvent(long downTime, long eventTime, float x, float y) {
+ return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL, x, y, 0);
+ }
+
private static void assertSingleTap(EditorTouchState touchState, float lastDownX,
- float lastDownY, float lastUpX, float lastUpY, boolean isMovedEnoughForDrag) {
+ float lastDownY, float lastUpX, float lastUpY) {
assertThat(touchState.getLastDownX(), is(lastDownX));
assertThat(touchState.getLastDownY(), is(lastDownY));
assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -309,7 +435,21 @@
assertThat(touchState.isTripleClick(), is(false));
assertThat(touchState.isMultiTap(), is(false));
assertThat(touchState.isMultiTapInSameArea(), is(false));
- assertThat(touchState.isMovedEnoughForDrag(), is(isMovedEnoughForDrag));
+ assertThat(touchState.isMovedEnoughForDrag(), is(false));
+ }
+
+ private static void assertDrag(EditorTouchState touchState, float lastDownX,
+ float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) {
+ assertThat(touchState.getLastDownX(), is(lastDownX));
+ assertThat(touchState.getLastDownY(), is(lastDownY));
+ assertThat(touchState.getLastUpX(), is(lastUpX));
+ assertThat(touchState.getLastUpY(), is(lastUpY));
+ assertThat(touchState.isDoubleTap(), is(false));
+ assertThat(touchState.isTripleClick(), is(false));
+ assertThat(touchState.isMultiTap(), is(false));
+ assertThat(touchState.isMultiTapInSameArea(), is(false));
+ assertThat(touchState.isMovedEnoughForDrag(), is(true));
+ assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical));
}
private static void assertMultiTap(EditorTouchState touchState,
@@ -325,5 +465,6 @@
|| multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
assertThat(touchState.isMovedEnoughForDrag(), is(false));
+ assertThat(touchState.isDragCloseToVertical(), is(false));
}
}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index aa55e08..a0cfb31 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -19,6 +19,7 @@
import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemDisabled;
import static android.widget.espresso.ContextMenuUtils.assertContextMenuContainsItemEnabled;
import static android.widget.espresso.ContextMenuUtils.assertContextMenuIsNotDisplayed;
+import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
import static android.widget.espresso.DragHandleUtils.onHandleView;
import static android.widget.espresso.TextViewActions.mouseClick;
import static android.widget.espresso.TextViewActions.mouseClickOnTextAtIndex;
@@ -47,7 +48,6 @@
import android.view.textclassifier.TextClassifier;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.Suppress;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -88,11 +88,6 @@
mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
onView(withId(R.id.textview)).check(hasSelection("llo wor"));
- onHandleView(com.android.internal.R.id.selection_start_handle)
- .check(matches(isDisplayed()));
- onHandleView(com.android.internal.R.id.selection_end_handle)
- .check(matches(isDisplayed()));
-
onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
onView(withId(R.id.textview)).check(hasSelection(""));
}
@@ -193,7 +188,6 @@
}
@Test
- @Suppress // Consistently failing. b/29591177
public void testDragAndDrop_longClick() {
final String text = "abc def ghi.";
onView(withId(R.id.textview)).perform(mouseClick());
@@ -395,4 +389,29 @@
mouseTripleClickAndDragOnText(text.indexOf("ird"), text.indexOf("First")));
onView(withId(R.id.textview)).check(hasSelection(text));
}
+
+ @Test
+ public void testSelectionHandlesDisplay() {
+ final String helloWorld = "Hello world!";
+ onView(withId(R.id.textview)).perform(mouseClick());
+ onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+
+ onView(withId(R.id.textview)).perform(
+ mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
+ onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+
+ // Confirm that selection handles are shown when there is a selection.
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .check(matches(isDisplayed()));
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .check(matches(isDisplayed()));
+
+ onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
+ onView(withId(R.id.textview)).check(hasSelection(""));
+
+ // Confirm that the selection handles are not shown when there is no selection. This
+ // assertion is slow so we only do it in this one test case. The rest of the tests just
+ // assert via `hasSelection("")`.
+ assertNoSelectionHandles();
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 82854e5..6784ede 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -348,7 +348,7 @@
verify(mAlertDialog).show();
verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList(
anyInt());
- verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut(null);
verify(mFrameworkObjectProvider, times(0)).getTextToSpeech(any(), any());
}
@@ -365,7 +365,7 @@
assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
mLayoutParams.privateFlags
& WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
- verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut(null);
}
@Test
@@ -433,7 +433,7 @@
verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
verify(mToast).show();
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
@@ -459,7 +459,7 @@
when(mServiceInfo.loadSummary(any())).thenReturn(null);
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
getController().performAccessibilityShortcut();
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
@@ -471,7 +471,7 @@
getController().performAccessibilityShortcut();
verifyZeroInteractions(mToast);
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
@@ -485,7 +485,7 @@
getController().performAccessibilityShortcut();
verifyZeroInteractions(mToast);
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 26a40d3..dfb7a16 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -135,3 +135,10 @@
src: "com.android.car.floatingcardslauncher.xml",
filename_from_src: true,
}
+
+prebuilt_etc {
+ name: "privapp_whitelist_com.android.car.ui.paintbooth",
+ sub_dir: "permissions",
+ src: "com.android.car.ui.paintbooth.xml",
+ filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.ui.paintbooth.xml b/data/etc/car/com.android.car.ui.paintbooth.xml
new file mode 100644
index 0000000..11bf304
--- /dev/null
+++ b/data/etc/car/com.android.car.ui.paintbooth.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<permissions>
+
+ <privapp-permissions package="com.android.car.ui.paintbooth">
+ <!-- For enabling/disabling, and getting list of RROs -->
+ <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+ <!-- For showing the current active activity -->
+ <permission name="android.permission.REAL_GET_TASKS"/>
+ <!-- For getting list of RROs for current user -->
+ <permission name="android.permission.MANAGE_USERS"/>
+ <!-- For getting list of RROs for current user-->
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 9930ea2..ad99ab3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -245,6 +245,7 @@
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
<permission name="android.permission.TETHER_PRIVILEGED"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+ <permission name="android.permission.UPDATE_DEVICE_STATS"/>
</privapp-permissions>
<privapp-permissions package="com.android.server.telecom">
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index ae90995..447f043 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -20,6 +20,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.graphics.fonts.FontVariationAxis;
+import android.os.Build;
import android.text.TextUtils;
import dalvik.annotation.optimization.CriticalNative;
@@ -58,7 +59,8 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public long mNativePtr;
// Points native font family builder. Must be zero after freezing this family.
@@ -67,7 +69,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public FontFamily() {
mBuilderPtr = nInitBuilder(null, 0);
mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
@@ -76,7 +79,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public FontFamily(@Nullable String[] langs, int variant) {
final String langsString;
if (langs == null || langs.length == 0) {
@@ -98,7 +102,8 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean freeze() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen");
@@ -115,7 +120,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public void abortCreation() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen or abandoned");
@@ -127,7 +133,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
int italic) {
if (mBuilderPtr == 0) {
@@ -151,7 +158,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
int weight, int italic) {
if (mBuilderPtr == 0) {
@@ -179,7 +187,8 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
boolean isAsset, int ttcIndex, int weight, int isItalic,
FontVariationAxis[] axes) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 17aacb9..fedde42 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -308,6 +308,9 @@
if (spec.isStrongBoxBacked()) {
flags |= KeyStore.FLAG_STRONGBOX;
}
+ if (spec.isCriticalToDeviceEncryption()) {
+ flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+ }
String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias();
KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
boolean success = false;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 91aac83..c52fd48 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -18,10 +18,8 @@
import android.annotation.Nullable;
import android.security.Credentials;
-import android.security.GateKeeper;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
-import android.security.KeyStoreException;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterCertificateChain;
@@ -458,6 +456,9 @@
if (mSpec.isStrongBoxBacked()) {
flags |= KeyStore.FLAG_STRONGBOX;
}
+ if (mSpec.isCriticalToDeviceEncryption()) {
+ flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+ }
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 52ff9e0..450dd33 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -271,6 +271,7 @@
private final boolean mIsStrongBoxBacked;
private final boolean mUserConfirmationRequired;
private final boolean mUnlockedDeviceRequired;
+ private final boolean mCriticalToDeviceEncryption;
/*
* ***NOTE***: All new fields MUST also be added to the following:
* ParcelableKeyGenParameterSpec class.
@@ -307,7 +308,8 @@
boolean invalidatedByBiometricEnrollment,
boolean isStrongBoxBacked,
boolean userConfirmationRequired,
- boolean unlockedDeviceRequired) {
+ boolean unlockedDeviceRequired,
+ boolean criticalToDeviceEncryption) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -357,6 +359,7 @@
mIsStrongBoxBacked = isStrongBoxBacked;
mUserConfirmationRequired = userConfirmationRequired;
mUnlockedDeviceRequired = unlockedDeviceRequired;
+ mCriticalToDeviceEncryption = criticalToDeviceEncryption;
}
/**
@@ -710,6 +713,16 @@
}
/**
+ * Return whether this key is critical to the device encryption flow.
+ *
+ * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+ * @hide
+ */
+ public boolean isCriticalToDeviceEncryption() {
+ return mCriticalToDeviceEncryption;
+ }
+
+ /**
* Builder of {@link KeyGenParameterSpec} instances.
*/
public final static class Builder {
@@ -741,6 +754,7 @@
private boolean mIsStrongBoxBacked = false;
private boolean mUserConfirmationRequired;
private boolean mUnlockedDeviceRequired = false;
+ private boolean mCriticalToDeviceEncryption = false;
/**
* Creates a new instance of the {@code Builder}.
@@ -804,6 +818,7 @@
mIsStrongBoxBacked = sourceSpec.isStrongBoxBacked();
mUserConfirmationRequired = sourceSpec.isUserConfirmationRequired();
mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
+ mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
}
/**
@@ -1339,6 +1354,20 @@
}
/**
+ * Set whether this key is critical to the device encryption flow
+ *
+ * This is a special flag only available to system servers to indicate the current key
+ * is part of the device encryption flow.
+ *
+ * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+ * @hide
+ */
+ public Builder setCriticalToDeviceEncryption(boolean critical) {
+ mCriticalToDeviceEncryption = critical;
+ return this;
+ }
+
+ /**
* Builds an instance of {@code KeyGenParameterSpec}.
*/
@NonNull
@@ -1370,7 +1399,8 @@
mInvalidatedByBiometricEnrollment,
mIsStrongBoxBacked,
mUserConfirmationRequired,
- mUnlockedDeviceRequired);
+ mUnlockedDeviceRequired,
+ mCriticalToDeviceEncryption);
}
}
}
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index d8030fb..98e4589 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -16,8 +16,8 @@
package android.security.keystore;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import java.math.BigInteger;
import java.security.spec.AlgorithmParameterSpec;
@@ -105,6 +105,7 @@
out.writeBoolean(mSpec.isStrongBoxBacked());
out.writeBoolean(mSpec.isUserConfirmationRequired());
out.writeBoolean(mSpec.isUnlockedDeviceRequired());
+ out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
}
private static Date readDateOrNull(Parcel in) {
@@ -160,6 +161,7 @@
final boolean isStrongBoxBacked = in.readBoolean();
final boolean userConfirmationRequired = in.readBoolean();
final boolean unlockedDeviceRequired = in.readBoolean();
+ final boolean criticalToDeviceEncryption = in.readBoolean();
// The KeyGenParameterSpec is intentionally not constructed using a Builder here:
// The intention is for this class to break if new parameters are added to the
// KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -190,7 +192,8 @@
invalidatedByBiometricEnrollment,
isStrongBoxBacked,
userConfirmationRequired,
- unlockedDeviceRequired);
+ unlockedDeviceRequired,
+ criticalToDeviceEncryption);
}
public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
index fca2775..b7d72fc 100644
--- a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
+++ b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
@@ -84,6 +84,7 @@
.setIsStrongBoxBacked(true)
.setUserConfirmationRequired(true)
.setUnlockedDeviceRequired(true)
+ .setCriticalToDeviceEncryption(true)
.build();
}
@@ -115,6 +116,7 @@
assertThat(spec.isStrongBoxBacked(), is(true));
assertThat(spec.isUserConfirmationRequired(), is(true));
assertThat(spec.isUnlockedDeviceRequired(), is(true));
+ assertThat(spec.isCriticalToDeviceEncryption(), is(true));
}
private Parcel parcelForReading(ParcelableKeyGenParameterSpec spec) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 12681ae..5790150 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -217,13 +217,16 @@
canvas->setMatrix(mMatrix);
switch (mType) {
case Type::Rect:
- canvas->clipRect(mRRect.rect(), mOp);
+ // Don't anti-alias rectangular clips
+ canvas->clipRect(mRRect.rect(), mOp, false);
break;
case Type::RRect:
- canvas->clipRRect(mRRect, mOp);
+ // Ensure rounded rectangular clips are anti-aliased
+ canvas->clipRRect(mRRect, mOp, true);
break;
case Type::Path:
- canvas->clipPath(mPath.value(), mOp);
+ // Ensure path clips are anti-aliased
+ canvas->clipPath(mPath.value(), mOp, true);
break;
}
}
@@ -392,7 +395,7 @@
bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) {
this->recordClip(*path, op);
- mCanvas->clipPath(*path, op);
+ mCanvas->clipPath(*path, op, true);
return !mCanvas->isClipEmpty();
}
diff --git a/media/OWNERS b/media/OWNERS
index 8bd037a..be60583 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -5,6 +5,7 @@
etalvala@google.com
gkasten@google.com
hdmoon@google.com
+hkuang@google.com
hunga@google.com
insun@google.com
jaewan@google.com
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 6d37c2d..bc4da10 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -57,7 +57,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final Object sRouterLock = new Object();
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
private static MediaRouter2 sInstance;
private final Context mContext;
@@ -73,23 +73,23 @@
new CopyOnWriteArrayList<>();
private final String mPackageName;
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
// TODO: Make MediaRouter2 is always connected to the MediaRouterService.
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
Client2 mClient;
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>();
private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1);
final Handler mHandler;
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
private boolean mShouldUpdateRoutes;
private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
@@ -728,16 +728,15 @@
* For example, selecting/deselcting/transferring routes to session can be done through this
* class. Instances are created by {@link MediaRouter2}.
*
- * TODO: Need to add toString()
* @hide
*/
public final class RoutingController {
private final Object mControllerLock = new Object();
- @GuardedBy("mLock")
+ @GuardedBy("mControllerLock")
private RoutingSessionInfo mSessionInfo;
- @GuardedBy("mLock")
+ @GuardedBy("mControllerLock")
private volatile boolean mIsReleased;
RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
@@ -998,6 +997,38 @@
}
}
+ @Override
+ public String toString() {
+ // To prevent logging spam, we only print the ID of each route.
+ List<String> selectedRoutes = getSelectedRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+ List<String> selectableRoutes = getSelectableRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+ List<String> deselectableRoutes = getDeselectableRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+ List<String> transferrableRoutes = getTransferrableRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+
+ StringBuilder result = new StringBuilder()
+ .append("RoutingController{ ")
+ .append("sessionId=").append(getSessionId())
+ .append(", routeFeature=").append(getRouteFeature())
+ .append(", selectedRoutes={")
+ .append(selectedRoutes)
+ .append("}")
+ .append(", selectableRoutes={")
+ .append(selectableRoutes)
+ .append("}")
+ .append(", deselectableRoutes={")
+ .append(deselectableRoutes)
+ .append("}")
+ .append(", transferrableRoutes={")
+ .append(transferrableRoutes)
+ .append("}")
+ .append(" }");
+ return result.toString();
+ }
+
/**
* TODO: Change this to package private. (Hidden for debugging purposes)
* @hide
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index 8f68cbd..ed272d5 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -16,6 +16,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.media.session.MediaSession;
import java.lang.annotation.Retention;
@@ -60,6 +61,7 @@
private final int mControlType;
private final int mMaxVolume;
+ private final String mControlId;
private int mCurrentVolume;
private Callback mCallback;
@@ -73,10 +75,28 @@
* @param maxVolume The maximum allowed volume.
* @param currentVolume The current volume on the output.
*/
+
public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume) {
+ this(volumeControl, maxVolume, currentVolume, null);
+ }
+
+ /**
+ * Create a new volume provider for handling volume events. You must specify
+ * the type of volume control, the maximum volume that can be used, and the
+ * current volume on the output.
+ *
+ * @param volumeControl The method for controlling volume that is used by
+ * this provider.
+ * @param maxVolume The maximum allowed volume.
+ * @param currentVolume The current volume on the output.
+ * @param volumeControlId The volume control id of this provider.
+ */
+ public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume,
+ @Nullable String volumeControlId) {
mControlType = volumeControl;
mMaxVolume = maxVolume;
mCurrentVolume = currentVolume;
+ mControlId = volumeControlId;
}
/**
@@ -122,6 +142,17 @@
}
/**
+ * Gets the volume control id. It can be used to identify which volume provider is
+ * used by the session.
+ *
+ * @return the volume control id or {@code null} if it isn't set.
+ */
+ @Nullable
+ public final String getVolumeControlId() {
+ return mControlId;
+ }
+
+ /**
* Override to handle requests to set the volume of the current output.
* After the volume has been modified {@link #setCurrentVolume} must be
* called to notify the system.
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 4d68a6a..21378c8 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -48,6 +48,6 @@
// These commands relate to volume handling
void setPlaybackToLocal(in AudioAttributes attributes);
- void setPlaybackToRemote(int control, int max);
+ void setPlaybackToRemote(int control, int max, @nullable String controlId);
void setCurrentVolume(int currentVolume);
}
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 1812d9c..c2f6206 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -964,16 +964,26 @@
private final int mMaxVolume;
private final int mCurrentVolume;
private final AudioAttributes mAudioAttrs;
+ private final String mVolumeControlId;
/**
* @hide
*/
public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs) {
+ this(type, control, max, current, attrs, null);
+ }
+
+ /**
+ * @hide
+ */
+ public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs,
+ String volumeControlId) {
mVolumeType = type;
mVolumeControl = control;
mMaxVolume = max;
mCurrentVolume = current;
mAudioAttrs = attrs;
+ mVolumeControlId = volumeControlId;
}
PlaybackInfo(Parcel in) {
@@ -982,6 +992,7 @@
mMaxVolume = in.readInt();
mCurrentVolume = in.readInt();
mAudioAttrs = in.readParcelable(null);
+ mVolumeControlId = in.readString();
}
/**
@@ -1042,11 +1053,24 @@
return mAudioAttrs;
}
+ /**
+ * Gets the volume control ID for this session. It can be used to identify which
+ * volume provider is used by the session.
+ *
+ * @return the volume control ID for this session or {@code null} if it's local playback
+ * or not set.
+ * @see VolumeProvider#getVolumeControlId()
+ */
+ @Nullable
+ public String getVolumeControlId() {
+ return mVolumeControlId;
+ }
+
@Override
public String toString() {
return "volumeType=" + mVolumeType + ", volumeControl=" + mVolumeControl
+ ", maxVolume=" + mMaxVolume + ", currentVolume=" + mCurrentVolume
- + ", audioAttrs=" + mAudioAttrs;
+ + ", audioAttrs=" + mAudioAttrs + ", volumeControlId=" + mVolumeControlId;
}
@Override
@@ -1061,6 +1085,7 @@
dest.writeInt(mMaxVolume);
dest.writeInt(mCurrentVolume);
dest.writeParcelable(mAudioAttrs, flags);
+ dest.writeString(mVolumeControlId);
}
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackInfo> CREATOR =
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 3adee59..9953626 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -335,7 +335,7 @@
try {
mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
- volumeProvider.getMaxVolume());
+ volumeProvider.getMaxVolume(), volumeProvider.getVolumeControlId());
mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
} catch (RemoteException e) {
Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
index 149c1cd..726af76 100644
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -44,4 +44,11 @@
* and this event will be sent in addition to the abort event.
*/
void onRecognitionAvailabilityChange(boolean available);
+ /**
+ * Notifies the client that the associated module has crashed and restarted. The module instance
+ * is no longer usable and will throw a ServiceSpecificException with a Status.DEAD_OBJECT code
+ * for every call. The client should detach, then re-attach to the module in order to get a new,
+ * usable instance. All state for this module has been lost.
+ */
+ void onModuleDied();
}
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
index 5d082e2..85ccacf 100644
--- a/media/java/android/media/soundtrigger_middleware/Status.aidl
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -28,4 +28,6 @@
OPERATION_NOT_SUPPORTED = 2,
/** Temporary lack of permission. */
TEMPORARY_PERMISSION_DENIED = 3,
+ /** The object on which this method is called is dead and all of its state is lost. */
+ DEAD_OBJECT = 4,
}
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index c7cc9e6d..8e579bf 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -23,7 +23,6 @@
import android.annotation.SystemApi;
import android.content.Context;
import android.hardware.tv.tuner.V1_0.Constants;
-import android.media.tv.tuner.Tuner.LnbCallback;
import android.media.tv.tuner.TunerConstants.Result;
import java.lang.annotation.Retention;
diff --git a/media/java/android/media/tv/tuner/LnbCallback.java b/media/java/android/media/tv/tuner/LnbCallback.java
new file mode 100644
index 0000000..99bbf86
--- /dev/null
+++ b/media/java/android/media/tv/tuner/LnbCallback.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner;
+
+
+/**
+ * Callback interface for receiving information from LNBs.
+ *
+ * @hide
+ */
+public interface LnbCallback {
+ /**
+ * Invoked when there is a LNB event.
+ */
+ void onEvent(int lnbEventType);
+
+ /**
+ * Invoked when there is a new DiSEqC message.
+ *
+ * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite
+ * Equipment Control) message which is specified by EUTELSAT Bus Functional
+ * Specification Version 4.2.
+ */
+ void onDiseqcMessage(byte[] diseqcMessage);
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index e3dfaeb..6f5ba84 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -137,29 +137,10 @@
private static native DemuxCapabilities nativeGetDemuxCapabilities();
- /**
- * LNB Callback.
- *
- * @hide
- */
- public interface LnbCallback {
- /**
- * Invoked when there is a LNB event.
- */
- void onEvent(int lnbEventType);
-
- /**
- * Invoked when there is a new DiSEqC message.
- *
- * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite
- * Equipment Control) message which is specified by EUTELSAT Bus Functional
- * Specification Version 4.2.
- */
- void onDiseqcMessage(byte[] diseqcMessage);
- }
/**
* Callback interface for receiving information from the corresponding filters.
+ * TODO: remove
*/
public interface FilterCallback {
/**
@@ -178,22 +159,6 @@
void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
}
- /**
- * DVR Callback.
- *
- * @hide
- */
- public interface DvrCallback {
- /**
- * Invoked when record status changed.
- */
- void onRecordStatus(int status);
- /**
- * Invoked when playback status changed.
- */
- void onPlaybackStatus(int status);
- }
-
@Nullable
private EventHandler createEventHandler() {
Looper looper;
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index 4532122..42229a0 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -124,6 +124,34 @@
*/
public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "INDEX_TYPE_", value =
+ {INDEX_TYPE_NONE, INDEX_TYPE_TS, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC})
+ public @interface IndexType {}
+
+ /**
+ * Index is not used.
+ * @hide
+ */
+ public static final int INDEX_TYPE_NONE = Constants.DemuxRecordIndexType.NONE;
+ /**
+ * TS index.
+ * @hide
+ */
+ public static final int INDEX_TYPE_TS = Constants.DemuxRecordIndexType.TS;
+ /**
+ * Start Code index.
+ * @hide
+ */
+ public static final int INDEX_TYPE_SC = Constants.DemuxRecordIndexType.SC;
+ /**
+ * Start Code index for HEVC.
+ * @hide
+ */
+ public static final int INDEX_TYPE_SC_HEVC = Constants.DemuxRecordIndexType.SC_HEVC;
+
/**
* Indexes can be tagged through TS (Transport Stream) header.
*
diff --git a/media/java/android/media/tv/tuner/dvr/Dvr.java b/media/java/android/media/tv/tuner/dvr/Dvr.java
index f90042b..95508d3 100644
--- a/media/java/android/media/tv/tuner/dvr/Dvr.java
+++ b/media/java/android/media/tv/tuner/dvr/Dvr.java
@@ -18,7 +18,6 @@
import android.annotation.BytesLong;
import android.annotation.NonNull;
-import android.media.tv.tuner.Tuner.DvrCallback;
import android.media.tv.tuner.Tuner.Filter;
import android.media.tv.tuner.TunerConstants.Result;
import android.os.ParcelFileDescriptor;
diff --git a/media/java/android/media/tv/tuner/dvr/DvrCallback.java b/media/java/android/media/tv/tuner/dvr/DvrCallback.java
new file mode 100644
index 0000000..3d140f0
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/DvrCallback.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.dvr;
+
+/**
+ * Callback interface for receiving information from DVR interfaces.
+ *
+ * @hide
+ */
+public interface DvrCallback {
+ /**
+ * Invoked when record status changed.
+ */
+ void onRecordStatusChanged(int status);
+ /**
+ * Invoked when playback status changed.
+ */
+ void onPlaybackStatusChanged(int status);
+}
diff --git a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
index f0fe533..fcca6a1 100644
--- a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
@@ -16,20 +16,123 @@
package android.media.tv.tuner.filter;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Filter configuration for a ALP filter.
* @hide
*/
public class AlpFilterConfiguration extends FilterConfiguration {
- private int mPacketType;
- private int mLengthType;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "LENGTH_TYPE_", value =
+ {LENGTH_TYPE_UNDEFINED, LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER,
+ LENGTH_TYPE_WITH_ADDITIONAL_HEADER})
+ public @interface LengthType {}
- public AlpFilterConfiguration(Settings settings) {
+ /**
+ * Length type not defined.
+ */
+ public static final int LENGTH_TYPE_UNDEFINED = Constants.DemuxAlpLengthType.UNDEFINED;
+ /**
+ * Length does NOT include additional header.
+ */
+ public static final int LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER =
+ Constants.DemuxAlpLengthType.WITHOUT_ADDITIONAL_HEADER;
+ /**
+ * Length includes additional header.
+ */
+ public static final int LENGTH_TYPE_WITH_ADDITIONAL_HEADER =
+ Constants.DemuxAlpLengthType.WITH_ADDITIONAL_HEADER;
+
+
+ private final int mPacketType;
+ private final int mLengthType;
+
+ public AlpFilterConfiguration(Settings settings, int packetType, int lengthType) {
super(settings);
+ mPacketType = packetType;
+ mLengthType = lengthType;
}
@Override
public int getType() {
return FilterConfiguration.FILTER_TYPE_ALP;
}
+
+ /**
+ * Gets packet type.
+ */
+ @FilterConfiguration.PacketType
+ public int getPacketType() {
+ return mPacketType;
+ }
+ /**
+ * Gets length type.
+ */
+ @LengthType
+ public int getLengthType() {
+ return mLengthType;
+ }
+
+ /**
+ * Creates a builder for {@link AlpFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link AlpFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private int mPacketType;
+ private int mLengthType;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets packet type.
+ */
+ @NonNull
+ public Builder setPacketType(@FilterConfiguration.PacketType int packetType) {
+ mPacketType = packetType;
+ return this;
+ }
+ /**
+ * Sets length type.
+ */
+ @NonNull
+ public Builder setLengthType(@LengthType int lengthType) {
+ mLengthType = lengthType;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AlpFilterConfiguration} object.
+ */
+ @NonNull
+ public AlpFilterConfiguration build() {
+ return new AlpFilterConfiguration(mSettings, mPacketType, mLengthType);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java
index a7c49d5..940b5ae 100644
--- a/media/java/android/media/tv/tuner/filter/AvSettings.java
+++ b/media/java/android/media/tv/tuner/filter/AvSettings.java
@@ -16,21 +16,84 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
import android.media.tv.tuner.TunerConstants;
import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
/**
* Filter Settings for a Video and Audio.
+ *
* @hide
*/
public class AvSettings extends Settings {
- private boolean mIsPassthrough;
+ private final boolean mIsPassthrough;
- private AvSettings(int mainType, boolean isAudio) {
+ private AvSettings(int mainType, boolean isAudio, boolean isPassthrough) {
super(TunerUtils.getFilterSubtype(
mainType,
isAudio
? TunerConstants.FILTER_SUBTYPE_AUDIO
: TunerConstants.FILTER_SUBTYPE_VIDEO));
+ mIsPassthrough = isPassthrough;
+ }
+
+ /**
+ * Checks whether it's passthrough.
+ */
+ public boolean isPassthrough() {
+ return mIsPassthrough;
+ }
+
+ /**
+ * Creates a builder for {@link AvSettings}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ * @param isAudio {@code true} if it's audio settings; {@code false} if it's video settings.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(
+ @NonNull Context context, @FilterType int mainType, boolean isAudio) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType, isAudio);
+ }
+
+ /**
+ * Builder for {@link AvSettings}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private final boolean mIsAudio;
+ private boolean mIsPassthrough;
+
+ private Builder(int mainType, boolean isAudio) {
+ super(mainType);
+ mIsAudio = isAudio;
+ }
+
+ /**
+ * Sets whether it's passthrough.
+ */
+ @NonNull
+ public Builder setPassthrough(boolean isPassthrough) {
+ mIsPassthrough = isPassthrough;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AvSettings} object.
+ */
+ @NonNull
+ public AvSettings build() {
+ return new AvSettings(mMainType, mIsAudio, mIsPassthrough);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
}
diff --git a/media/java/android/media/tv/tuner/filter/DownloadSettings.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
index 0742b11..e3e1df0 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadSettings.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
@@ -16,17 +16,75 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
import android.media.tv.tuner.TunerConstants;
import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
/**
* Filter Settings for a Download.
* @hide
*/
public class DownloadSettings extends Settings {
- private int mDownloadId;
+ private final int mDownloadId;
- public DownloadSettings(int mainType) {
+ private DownloadSettings(int mainType, int downloadId) {
super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD));
+ mDownloadId = downloadId;
+ }
+
+ /**
+ * Gets download ID.
+ */
+ public int getDownloadId() {
+ return mDownloadId;
+ }
+
+ /**
+ * Creates a builder for {@link DownloadSettings}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link DownloadSettings}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private int mDownloadId;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets download ID.
+ */
+ @NonNull
+ public Builder setDownloadId(int downloadId) {
+ mDownloadId = downloadId;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DownloadSettings} object.
+ */
+ @NonNull
+ public DownloadSettings build() {
+ return new DownloadSettings(mMainType, mDownloadId);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
}
diff --git a/media/java/android/media/tv/tuner/filter/FilterCallback.java b/media/java/android/media/tv/tuner/filter/FilterCallback.java
new file mode 100644
index 0000000..888adc5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/FilterCallback.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
+
+/**
+ * Callback interface for receiving information from the corresponding filters.
+ *
+ * @hide
+ */
+public interface FilterCallback {
+ /**
+ * Invoked when there are filter events.
+ *
+ * @param filter the corresponding filter which sent the events.
+ * @param events the filter events sent from the filter.
+ */
+ void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events);
+ /**
+ * Invoked when filter status changed.
+ *
+ * @param filter the corresponding filter whose status is changed.
+ * @param status the new status of the filter.
+ */
+ void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
+}
diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
index 6496627..68c722f 100644
--- a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
@@ -33,7 +33,8 @@
public abstract class FilterConfiguration {
/** @hide */
- @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
+ @IntDef(prefix = "FILTER_TYPE_", value =
+ {FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
@Retention(RetentionPolicy.SOURCE)
public @interface FilterType {}
@@ -58,6 +59,30 @@
*/
public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
+
+ /** @hide */
+ @IntDef(prefix = "PACKET_TYPE_", value =
+ {PACKET_TYPE_IPV4, PACKET_TYPE_COMPRESSED, PACKET_TYPE_SIGNALING})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PacketType {}
+
+ /**
+ * IP v4 packet type.
+ * @hide
+ */
+ public static final int PACKET_TYPE_IPV4 = 0;
+ /**
+ * Compressed packet type.
+ * @hide
+ */
+ public static final int PACKET_TYPE_COMPRESSED = 2;
+ /**
+ * Signaling packet type.
+ * @hide
+ */
+ public static final int PACKET_TYPE_SIGNALING = 4;
+
+
@Nullable
/* package */ final Settings mSettings;
@@ -77,4 +102,27 @@
public Settings getSettings() {
return mSettings;
}
+
+ /**
+ * Builder for {@link FilterConfiguration}.
+ *
+ * @param <T> The subclass to be built.
+ * @hide
+ */
+ public abstract static class Builder<T extends Builder<T>> {
+ /* package */ Settings mSettings;
+
+ /* package */ Builder() {
+ }
+
+ /**
+ * Sets filter settings.
+ */
+ @Nullable
+ public T setFrequency(Settings settings) {
+ mSettings = settings;
+ return self();
+ }
+ /* package */ abstract T self();
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
index c896368..98edf10 100644
--- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -16,23 +16,152 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.Size;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
/**
* Filter configuration for a IP filter.
* @hide
*/
public class IpFilterConfiguration extends FilterConfiguration {
- private byte[] mSrcIpAddress;
- private byte[] mDstIpAddress;
- private int mSrcPort;
- private int mDstPort;
- private boolean mPassthrough;
+ private final byte[] mSrcIpAddress;
+ private final byte[] mDstIpAddress;
+ private final int mSrcPort;
+ private final int mDstPort;
+ private final boolean mPassthrough;
- public IpFilterConfiguration(Settings settings) {
+ public IpFilterConfiguration(Settings settings, byte[] srcAddr, byte[] dstAddr, int srcPort,
+ int dstPort, boolean passthrough) {
super(settings);
+ mSrcIpAddress = srcAddr;
+ mDstIpAddress = dstAddr;
+ mSrcPort = srcPort;
+ mDstPort = dstPort;
+ mPassthrough = passthrough;
}
@Override
public int getType() {
return FilterConfiguration.FILTER_TYPE_IP;
}
+
+ /**
+ * Gets source IP address.
+ */
+ @Size(min = 4, max = 16)
+ public byte[] getSrcIpAddress() {
+ return mSrcIpAddress;
+ }
+ /**
+ * Gets destination IP address.
+ */
+ @Size(min = 4, max = 16)
+ public byte[] getDstIpAddress() {
+ return mDstIpAddress;
+ }
+ /**
+ * Gets source port.
+ */
+ public int getSrcPort() {
+ return mSrcPort;
+ }
+ /**
+ * Gets destination port.
+ */
+ public int getDstPort() {
+ return mDstPort;
+ }
+ /**
+ * Checks whether the filter is passthrough.
+ *
+ * @return {@code true} if the data from IP subtype go to next filter directly;
+ * {@code false} otherwise.
+ */
+ public boolean isPassthrough() {
+ return mPassthrough;
+ }
+
+ /**
+ * Creates a builder for {@link IpFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IpFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private byte[] mSrcIpAddress;
+ private byte[] mDstIpAddress;
+ private int mSrcPort;
+ private int mDstPort;
+ private boolean mPassthrough;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets source IP address.
+ */
+ @NonNull
+ public Builder setSrcIpAddress(byte[] srcIpAddress) {
+ mSrcIpAddress = srcIpAddress;
+ return this;
+ }
+ /**
+ * Sets destination IP address.
+ */
+ @NonNull
+ public Builder setDstIpAddress(byte[] dstIpAddress) {
+ mDstIpAddress = dstIpAddress;
+ return this;
+ }
+ /**
+ * Sets source port.
+ */
+ @NonNull
+ public Builder setSrcPort(int srcPort) {
+ mSrcPort = srcPort;
+ return this;
+ }
+ /**
+ * Sets destination port.
+ */
+ @NonNull
+ public Builder setDstPort(int dstPort) {
+ mDstPort = dstPort;
+ return this;
+ }
+ /**
+ * Sets passthrough.
+ */
+ @NonNull
+ public Builder setPassthrough(boolean passthrough) {
+ mPassthrough = passthrough;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IpFilterConfiguration} object.
+ */
+ @NonNull
+ public IpFilterConfiguration build() {
+ return new IpFilterConfiguration(
+ mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
index 9045ce6..83246e5 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
@@ -16,19 +16,78 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
/**
* Filter configuration for a MMTP filter.
* @hide
*/
public class MmtpFilterConfiguration extends FilterConfiguration {
- private int mMmtpPid;
+ private final int mMmtpPid;
- public MmtpFilterConfiguration(Settings settings) {
+ public MmtpFilterConfiguration(Settings settings, int mmtpPid) {
super(settings);
+ mMmtpPid = mmtpPid;
}
@Override
public int getType() {
return FilterConfiguration.FILTER_TYPE_MMTP;
}
+
+ /**
+ * Gets MMPT PID.
+ *
+ * <p>Packet ID is used to specify packets in MMTP.
+ */
+ public int getMmtpPid() {
+ return mMmtpPid;
+ }
+
+ /**
+ * Creates a builder for {@link IpFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IpFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private int mMmtpPid;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets MMPT PID.
+ */
+ @NonNull
+ public Builder setMmtpPid(int mmtpPid) {
+ mMmtpPid = mmtpPid;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IpFilterConfiguration} object.
+ */
+ @NonNull
+ public MmtpFilterConfiguration build() {
+ return new MmtpFilterConfiguration(mSettings, mMmtpPid);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index 701868a..2930f2d 100644
--- a/media/java/android/media/tv/tuner/filter/RecordSettings.java
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -16,18 +16,96 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerConstants.IndexType;
import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
/**
* The Settings for the record in DVR.
* @hide
*/
public class RecordSettings extends Settings {
- private int mIndexType;
- private int mIndexMask;
+ private final int mIndexType;
+ private final int mIndexMask;
- public RecordSettings(int mainType) {
+ private RecordSettings(int mainType, int indexType, int indexMask) {
super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD));
+ mIndexType = indexType;
+ mIndexMask = indexMask;
}
+
+ /**
+ * Gets index type.
+ */
+ @IndexType
+ public int getIndexType() {
+ return mIndexType;
+ }
+ /**
+ * Gets index mask.
+ */
+ @TsRecordEvent.IndexMask
+ public int getIndexMask() {
+ return mIndexMask;
+ }
+
+ /**
+ * Creates a builder for {@link RecordSettings}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link RecordSettings}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private int mIndexType;
+ private int mIndexMask;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets index type.
+ */
+ @NonNull
+ public Builder setIndexType(@IndexType int indexType) {
+ mIndexType = indexType;
+ return this;
+ }
+ /**
+ * Sets index mask.
+ */
+ @NonNull
+ public Builder setIndexMask(@TsRecordEvent.IndexMask int indexMask) {
+ mIndexMask = indexMask;
+ return this;
+ }
+
+ /**
+ * Builds a {@link RecordSettings} object.
+ */
+ @NonNull
+ public RecordSettings build() {
+ return new RecordSettings(mMainType, mIndexType, mIndexMask);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
}
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
index 414ea67..0fa982e 100644
--- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
@@ -16,18 +16,116 @@
package android.media.tv.tuner.filter;
-import java.util.List;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
/**
- * Bits Settings for Section Filter.
+ * Bits Settings for Section Filters.
* @hide
*/
public class SectionSettingsWithSectionBits extends SectionSettings {
- private List<Byte> mFilter;
- private List<Byte> mMask;
- private List<Byte> mMode;
+ private final byte[] mFilter;
+ private final byte[] mMask;
+ private final byte[] mMode;
- private SectionSettingsWithSectionBits(int mainType) {
+
+ private SectionSettingsWithSectionBits(int mainType, byte[] filter, byte[] mask, byte[] mode) {
super(mainType);
+ mFilter = filter;
+ mMask = mask;
+ mMode = mode;
+ }
+
+ /**
+ * Gets the bytes configured for Section Filter
+ */
+ public byte[] getFilterBytes() {
+ return mFilter;
+ }
+ /**
+ * Gets bit mask.
+ *
+ * <p>The bits in the bytes are used for filtering.
+ */
+ public byte[] getMask() {
+ return mMask;
+ }
+ /**
+ * Gets mode.
+ *
+ * <p>Do positive match at the bit position of the configured bytes when the bit at same
+ * position of the mode is 0.
+ * <p>Do negative match at the bit position of the configured bytes when the bit at same
+ * position of the mode is 1.
+ */
+ public byte[] getMode() {
+ return mMode;
+ }
+
+ /**
+ * Creates a builder for {@link SectionSettingsWithSectionBits}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link SectionSettingsWithSectionBits}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private byte[] mFilter;
+ private byte[] mMask;
+ private byte[] mMode;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets filter bytes.
+ */
+ @NonNull
+ public Builder setFilter(byte[] filter) {
+ mFilter = filter;
+ return this;
+ }
+ /**
+ * Sets bit mask.
+ */
+ @NonNull
+ public Builder setMask(byte[] mask) {
+ mMask = mask;
+ return this;
+ }
+ /**
+ * Sets mode.
+ */
+ @NonNull
+ public Builder setMode(byte[] mode) {
+ mMode = mode;
+ return this;
+ }
+
+ /**
+ * Builds a {@link SectionSettingsWithSectionBits} object.
+ */
+ @NonNull
+ public SectionSettingsWithSectionBits build() {
+ return new SectionSettingsWithSectionBits(mMainType, mFilter, mMask, mMode);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
}
}
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
index 0df1d73..6542b89 100644
--- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
@@ -16,15 +16,92 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
/**
* Table information for Section Filter.
* @hide
*/
public class SectionSettingsWithTableInfo extends SectionSettings {
- private int mTableId;
- private int mVersion;
+ private final int mTableId;
+ private final int mVersion;
- private SectionSettingsWithTableInfo(int mainType) {
+ private SectionSettingsWithTableInfo(int mainType, int tableId, int version) {
super(mainType);
+ mTableId = tableId;
+ mVersion = version;
}
+
+ /**
+ * Gets table ID.
+ */
+ public int getTableId() {
+ return mTableId;
+ }
+ /**
+ * Gets version.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Creates a builder for {@link SectionSettingsWithTableInfo}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link SectionSettingsWithTableInfo}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private int mTableId;
+ private int mVersion;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets table ID.
+ */
+ @NonNull
+ public Builder setTableId(int tableId) {
+ mTableId = tableId;
+ return this;
+ }
+ /**
+ * Sets version.
+ */
+ @NonNull
+ public Builder setVersion(int version) {
+ mVersion = version;
+ return this;
+ }
+
+ /**
+ * Builds a {@link SectionSettingsWithTableInfo} object.
+ */
+ @NonNull
+ public SectionSettingsWithTableInfo build() {
+ return new SectionSettingsWithTableInfo(mMainType, mTableId, mVersion);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
}
diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java
index 9155926..d697280 100644
--- a/media/java/android/media/tv/tuner/filter/Settings.java
+++ b/media/java/android/media/tv/tuner/filter/Settings.java
@@ -39,4 +39,20 @@
public int getType() {
return mType;
}
+
+
+ /**
+ * Builder for {@link Settings}.
+ *
+ * @param <T> The subclass to be built.
+ * @hide
+ */
+ public abstract static class Builder<T extends Builder<T>> {
+ /* package */ final int mMainType;
+
+ /* package */ Builder(int mainType) {
+ mMainType = mainType;
+ }
+ /* package */ abstract T self();
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
index de8ee75..eb97fc0 100644
--- a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
@@ -16,21 +16,118 @@
package android.media.tv.tuner.filter;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
/**
* Filter configuration for a TLV filter.
* @hide
*/
public class TlvFilterConfiguration extends FilterConfiguration {
- private int mPacketType;
- private boolean mIsCompressedIpPacket;
- private boolean mPassthrough;
+ private final int mPacketType;
+ private final boolean mIsCompressedIpPacket;
+ private final boolean mPassthrough;
- public TlvFilterConfiguration(Settings settings) {
+ public TlvFilterConfiguration(Settings settings, int packetType, boolean isCompressed,
+ boolean passthrough) {
super(settings);
+ mPacketType = packetType;
+ mIsCompressedIpPacket = isCompressed;
+ mPassthrough = passthrough;
}
@Override
public int getType() {
return FilterConfiguration.FILTER_TYPE_TLV;
}
+
+ /**
+ * Gets packet type.
+ */
+ @FilterConfiguration.PacketType
+ public int getPacketType() {
+ return mPacketType;
+ }
+ /**
+ * Checks whether the data is compressed IP packet.
+ *
+ * @return {@code true} if the filtered data is compressed IP packet; {@code false} otherwise.
+ */
+ public boolean isCompressedIpPacket() {
+ return mIsCompressedIpPacket;
+ }
+ /**
+ * Checks whether it's passthrough.
+ *
+ * @return {@code true} if the data from TLV subtype go to next filter directly;
+ * {@code false} otherwise.
+ */
+ public boolean isPassthrough() {
+ return mPassthrough;
+ }
+
+ /**
+ * Creates a builder for {@link TlvFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link TlvFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private int mPacketType;
+ private boolean mIsCompressedIpPacket;
+ private boolean mPassthrough;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets packet type.
+ */
+ @NonNull
+ public Builder setPacketType(@FilterConfiguration.PacketType int packetType) {
+ mPacketType = packetType;
+ return this;
+ }
+ /**
+ * Sets whether the data is compressed IP packet.
+ */
+ @NonNull
+ public Builder setIsCompressedIpPacket(boolean isCompressedIpPacket) {
+ mIsCompressedIpPacket = isCompressedIpPacket;
+ return this;
+ }
+ /**
+ * Sets whether it's passthrough.
+ */
+ @NonNull
+ public Builder setPassthrough(boolean passthrough) {
+ mPassthrough = passthrough;
+ return this;
+ }
+
+ /**
+ * Builds a {@link TlvFilterConfiguration} object.
+ */
+ @NonNull
+ public TlvFilterConfiguration build() {
+ return new TlvFilterConfiguration(
+ mSettings, mPacketType, mIsCompressedIpPacket, mPassthrough);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 747ceb1..50d3a5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -262,13 +262,28 @@
}
}
+ /**
+ * Connect this device.
+ *
+ * @param connectAllProfiles {@code true} to connect all profile, {@code false} otherwise.
+ *
+ * @deprecated use {@link #connect()} instead.
+ */
+ @Deprecated
public void connect(boolean connectAllProfiles) {
+ connect();
+ }
+
+ /**
+ * Connect this device.
+ */
+ public void connect() {
if (!ensurePaired()) {
return;
}
mConnectAttempted = SystemClock.elapsedRealtime();
- connectWithoutResettingTimer(connectAllProfiles);
+ connectAllEnabledProfiles();
}
public long getHiSyncId() {
@@ -289,10 +304,10 @@
void onBondingDockConnect() {
// Attempt to connect if UUIDs are available. Otherwise,
// we will connect when the ACTION_UUID intent arrives.
- connect(false);
+ connect();
}
- private void connectWithoutResettingTimer(boolean connectAllProfiles) {
+ private void connectAllEnabledProfiles() {
synchronized (mProfileLock) {
// Try to initialize the profiles if they were not.
if (mProfiles.isEmpty()) {
@@ -307,36 +322,7 @@
return;
}
- int preferredProfiles = 0;
- for (LocalBluetoothProfile profile : mProfiles) {
- if (connectAllProfiles ? profile.accessProfileEnabled()
- : profile.isAutoConnectable()) {
- if (profile.isPreferred(mDevice)) {
- ++preferredProfiles;
- connectInt(profile);
- }
- }
- }
- if (BluetoothUtils.D) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
-
- if (preferredProfiles == 0) {
- connectAutoConnectableProfiles();
- }
- }
- }
-
- private void connectAutoConnectableProfiles() {
- if (!ensurePaired()) {
- return;
- }
-
- synchronized (mProfileLock) {
- for (LocalBluetoothProfile profile : mProfiles) {
- if (profile.isAutoConnectable()) {
- profile.setPreferred(mDevice, true);
- connectInt(profile);
- }
- }
+ mLocalAdapter.connectAllEnabledProfiles(mDevice);
}
}
@@ -703,7 +689,7 @@
*/
if (!mProfiles.isEmpty()
&& ((mConnectAttempted + timeout) > SystemClock.elapsedRealtime())) {
- connectWithoutResettingTimer(false);
+ connectAllEnabledProfiles();
}
dispatchAttributesChanged();
@@ -722,7 +708,7 @@
refresh();
if (bondState == BluetoothDevice.BOND_BONDED && mDevice.isBondingInitiatedLocally()) {
- connect(false);
+ connect();
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
index 61e47f8..6e7a429 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -87,8 +87,10 @@
if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
return;
}
- final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
- mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+ if (mCreationTimestamp != 0L) {
+ final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
+ mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+ }
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4ab9a9a..b07fc2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -61,6 +61,8 @@
"com.android.settings.category.ia.my_device_info";
public static final String CATEGORY_BATTERY_SAVER_SETTINGS =
"com.android.settings.category.ia.battery_saver_settings";
+ public static final String CATEGORY_SMART_BATTERY_SETTINGS =
+ "com.android.settings.category.ia.smart_battery_settings";
public static final Map<String, String> KEY_COMPAT_MAP;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 70b56ed..e85a102 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -122,7 +122,7 @@
final CachedBluetoothDevice cachedDevice =
((BluetoothMediaDevice) device).getCachedDevice();
if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
- cachedDevice.connect(true);
+ cachedDevice.connect();
return;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index 853c77e..05a6ce4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -40,7 +40,7 @@
final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
telephonyManager.createForSubscriptionId(subId).getSubscriberId());
- if (!subscriptionManager.isActiveSubId(subId)) {
+ if (!subscriptionManager.isActiveSubscriptionId(subId)) {
Log.i(TAG, "Subscription is not active: " + subId);
return mobileAll;
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER
new file mode 100644
index 0000000..5c2a7b8
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER
@@ -0,0 +1,4 @@
+# People who can approve changes for submission
+arcwang@google.com
+govenliu@google.com
+qal@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 605c861..340a6c7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -59,8 +59,9 @@
allKeys.add(CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
allKeys.add(CategoryKey.CATEGORY_GESTURES);
allKeys.add(CategoryKey.CATEGORY_NIGHT_DISPLAY);
+ allKeys.add(CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS);
// DO NOT REMOVE ANYTHING ABOVE
- assertThat(allKeys.size()).isEqualTo(18);
+ assertThat(allKeys.size()).isEqualTo(19);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 894aa78..c780a64 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -127,7 +127,7 @@
mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.connectDevice(device);
- verify(cachedDevice).connect(true);
+ verify(cachedDevice).connect();
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
index d98f50b..b0a647e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
@@ -31,6 +31,7 @@
import android.telephony.TelephonyManager;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -71,12 +72,13 @@
when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID);
when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2);
when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
- when(mSubscriptionManager.isActiveSubId(anyInt())).thenReturn(true);
+ when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
}
@Test
+ @Ignore
public void getMobileTemplate_infoNull_returnMobileAll() {
- when(mSubscriptionManager.isActiveSubId(SUB_ID)).thenReturn(false);
+ when(mSubscriptionManager.isActiveSubscriptionId(SUB_ID)).thenReturn(false);
final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
@@ -84,6 +86,7 @@
}
@Test
+ @Ignore
public void getMobileTemplate_groupUuidNull_returnMobileAll() {
when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
when(mInfo1.getGroupUuid()).thenReturn(null);
@@ -96,6 +99,7 @@
}
@Test
+ @Ignore
public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
index 21aa526..2bd20a9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
@@ -72,7 +72,7 @@
assertThat(AccessPointPreference.buildContentDescription(
RuntimeEnvironment.application, pref, ap))
- .isEqualTo("ssid,connected,Wifi signal full.,Secure network");
+ .isEqualTo("ssid,connected,Wifi disconnected.,Secure network");
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER
new file mode 100644
index 0000000..5c2a7b8
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER
@@ -0,0 +1,4 @@
+# People who can approve changes for submission
+arcwang@google.com
+govenliu@google.com
+qal@google.com
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1e0c1d8..4443524 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -20,6 +20,7 @@
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
import android.Manifest;
@@ -3304,7 +3305,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 185;
+ private static final int SETTINGS_VERSION = 186;
private final int mUserId;
@@ -4547,6 +4548,32 @@
currentVersion = 185;
}
+ if (currentVersion == 185) {
+ // Deprecate ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, and migrate it
+ // to ACCESSIBILITY_BUTTON_TARGET_COMPONENT.
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting magnifyNavbarEnabled = secureSettings.getSettingLocked(
+ Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+ if ("1".equals(magnifyNavbarEnabled.getValue())) {
+ secureSettings.insertSettingLocked(
+ Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
+ null /* tag */, false /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ } else {
+ // Clear a11y button targets list setting. A11yManagerService will end up
+ // adding all legacy enabled services that want the button to the list, so
+ // there's no need to keep tracking them.
+ secureSettings.insertSettingLocked(
+ Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ null, null /* tag */, false /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ secureSettings.deleteSettingLocked(
+ Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+ currentVersion = 186;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 7278225..1a1a480 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -734,7 +734,8 @@
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
Settings.Secure.FACE_UNLOCK_RE_ENROLL,
Settings.Secure.TAP_GESTURE,
- Settings.Secure.WINDOW_MAGNIFICATION);
+ Settings.Secure.WINDOW_MAGNIFICATION,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2a1e74e..bdb594f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -211,6 +211,7 @@
<!-- accessibility -->
<uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" />
+ <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
<!-- to control accessibility volume -->
<uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
@@ -307,7 +308,8 @@
</receiver>
<activity android:name=".screenrecord.ScreenRecordDialog"
- android:theme="@style/ScreenRecord" />
+ android:theme="@style/ScreenRecord"
+ android:excludeFromRecents="true" />
<service android:name=".screenrecord.RecordingService" />
<receiver android:name=".SysuiRestartReceiver"
@@ -636,6 +638,23 @@
</intent-filter>
</activity>
+ <activity android:name=".controls.management.ControlsProviderSelectorActivity"
+ android:label="Controls Providers"
+ android:theme="@style/Theme.SystemUI"
+ android:exported="true"
+ android:excludeFromRecents="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:visibleToInstantApps="true">
+ </activity>
+
+ <activity android:name=".controls.management.ControlsFavoritingActivity"
+ android:parentActivityName=".controls.management.ControlsProviderSelectorActivity"
+ android:theme="@style/Theme.SystemUI"
+ android:excludeFromRecents="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:visibleToInstantApps="true">
+ </activity>
+
<!-- Doze with notifications, run in main sysui process for every user -->
<service
android:name=".doze.DozeService"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 8db0d02..02c4c5e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -46,6 +46,8 @@
*/
public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
+ public void snooze(StatusBarNotification sbn, int hours);
+
public float getMinDismissVelocity();
public boolean isDismissGesture(MotionEvent ev);
diff --git a/packages/SystemUI/res/drawable/ic_add_to_home.xml b/packages/SystemUI/res/drawable/ic_add_to_home.xml
new file mode 100644
index 0000000..2b40c62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_add_to_home.xml
@@ -0,0 +1,26 @@
+<!--
+ 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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,1.01L8,1c-1.1,0 -2,0.9 -2,2v3h2V5h10v14H8v-1H6v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM10,15h2V8H5v2h3.59L3,15.59 4.41,17 10,11.41V15z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_demote_conversation.xml b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
new file mode 100644
index 0000000..5a88160
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
@@ -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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,2L4.83,2l2,2L20,4v12h-1.17l1.87,1.87c0.75,-0.29 1.3,-1.02 1.3,-1.87L22,4c0,-1.1 -0.9,-2 -2,-2zM6,12h2v2L6,14zM18,11L18,9h-6.17l2,2zM18,6h-8v1.17l0.83,0.83L18,8zM0.69,3.51l1.32,1.32L2,22l4,-4h9.17l5.31,5.31 1.41,-1.41L2.1,2.1 0.69,3.51zM6,16h-0.83l-0.59,0.59 -0.58,0.58L4,6.83l2,2L6,11h2.17l5,5L6,16z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
new file mode 100644
index 0000000..687c9c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,10.48L18,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11l-4,3.98zM16,9.69L16,18L4,18L4,6h12v3.69z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_snooze.xml b/packages/SystemUI/res/drawable/ic_snooze.xml
index b0b03a9..f4c074d 100644
--- a/packages/SystemUI/res/drawable/ic_snooze.xml
+++ b/packages/SystemUI/res/drawable/ic_snooze.xml
@@ -1,12 +1,24 @@
+<!--
+ 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
+ -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
- android:fillColor="#757575"/>
- <path
- android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
- android:fillColor="#757575"/>
+ android:fillColor="@android:color/white"
+ android:pathData="M9,11h3.63L9,15.2L9,17h6v-2h-3.63L15,10.8L15,9L9,9v2zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM3.336,7.19l-1.28,-1.536L6.662,1.81l1.28,1.536zM12,6c3.86,0 7,3.14 7,7s-3.14,7 -7,7 -7,-3.14 -7,-7 3.14,-7 7,-7m0,-2c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml
new file mode 100644
index 0000000..4a731b3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star.xml
@@ -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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml
new file mode 100644
index 0000000..9ede40b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star_border.xml
@@ -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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/app_item.xml b/packages/SystemUI/res/layout/app_item.xml
new file mode 100644
index 0000000..83e7887
--- /dev/null
+++ b/packages/SystemUI/res/layout/app_item.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|center_vertical"
+ android:minWidth="56dp"
+ android:orientation="horizontal"
+ android:paddingEnd="8dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/app_icon_size"
+ android:layout_height="@dimen/app_icon_size"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical|end"
+ android:minWidth="64dp"
+ android:orientation="vertical"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/control_item.xml b/packages/SystemUI/res/layout/control_item.xml
new file mode 100644
index 0000000..85701aa
--- /dev/null
+++ b/packages/SystemUI/res/layout/control_item.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:padding="15dp"
+ android:clickable="true"
+ android:focusable="true">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <TextView
+ android:id="@+id/status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:paddingLeft="3dp"
+ app:layout_constraintBottom_toBottomOf="@+id/icon"
+ app:layout_constraintStart_toEndOf="@+id/icon" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ app:layout_constraintBottom_toTopOf="@+id/subtitle"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/icon" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="16sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <CheckBox
+ android:id="@+id/favorite"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
new file mode 100644
index 0000000..8749b1a
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.systemui.statusbar.notification.row.NotificationConversationInfo
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/notification_guts"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:clipChildren="false"
+ android:clipToPadding="true"
+ android:orientation="vertical"
+ android:background="@color/notification_material_background_color"
+ android:paddingStart="@*android:dimen/notification_content_margin_start">
+
+ <!-- Package Info -->
+ <RelativeLayout
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_guts_conversation_header_height"
+ android:gravity="center_vertical"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+ <ImageView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/notification_guts_conversation_icon_size"
+ android:layout_height="@dimen/notification_guts_conversation_icon_size"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="6dp" />
+ <LinearLayout
+ android:id="@+id/names"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_guts_conversation_icon_size"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ android:layout_toEndOf="@id/conversation_icon">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/parent_channel_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/pkg_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.NotificationImportanceChannelGroup"
+ android:ellipsize="end"
+ android:maxLines="1"/>
+ <TextView
+ android:id="@+id/group_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/group_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/pkg_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:layout_toEndOf="@id/name"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/delegate_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:ellipsize="end"
+ android:text="@string/notification_delegate_header"
+ android:layout_toEndOf="@id/pkg_divider"
+ android:maxLines="1" />
+
+ <!-- end aligned fields -->
+ <ImageButton
+ android:id="@+id/demote"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/demote"
+ android:src="@drawable/ic_demote_conversation"
+ android:layout_toStartOf="@id/app_settings"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ <!-- Optional link to app. Only appears if the channel is not disabled and the app
+asked for it -->
+ <ImageButton
+ android:id="@+id/app_settings"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:visibility="gone"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/notification_app_settings"
+ android:src="@drawable/ic_info"
+ android:layout_toStartOf="@id/info"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ <ImageButton
+ android:id="@+id/info"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/notification_more_settings"
+ android:src="@drawable/ic_settings"
+ android:layout_alignParentEnd="true"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/bubble"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_favorite"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_create_bubble"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/home"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_home_screen"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_add_to_home"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/fave"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:gravity="left|center_vertical"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/snooze"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_menu_snooze_action"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_snooze"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/mute"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_mute"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_notifications_silence"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ </LinearLayout>
+
+</com.android.systemui.statusbar.notification.row.NotificationConversationInfo>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
deleted file mode 100644
index 3d63b7d..0000000
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:gravity="top"
- android:orientation="vertical"
- android:padding="@dimen/global_actions_padding"
- android:background="@drawable/rounded_bg_full">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <CheckBox
- android:id="@+id/checkbox_mic"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/screenrecord_mic_label"/>
- <CheckBox
- android:id="@+id/checkbox_taps"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/screenrecord_taps_label"/>
- <Button
- android:id="@+id/record_button"
- android:layout_width="match_parent"
- android:layout_height="50dp"
- android:text="@string/screenrecord_start_label"
- />
- </LinearLayout>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 640f31b..8963157 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -109,7 +109,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
+ wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord
</string>
<!-- The minimum number of tiles to display in QuickSettings -->
@@ -117,7 +117,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls,screenrecord
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c26cb9a..53df025 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -213,6 +213,11 @@
<!-- The horizontal space around the buttons in the inline settings -->
<dimen name="notification_guts_button_horizontal_spacing">8dp</dimen>
+ <dimen name="notification_guts_conversation_header_height">84dp</dimen>
+ <dimen name="notification_guts_conversation_icon_size">52dp</dimen>
+ <dimen name="notification_guts_conversation_action_height">56dp</dimen>
+ <dimen name="notification_guts_conversation_action_text_padding_start">32dp</dimen>
+
<!-- The height of the header in inline settings -->
<dimen name="notification_guts_header_height">24dp</dimen>
@@ -1160,4 +1165,5 @@
<dimen name="magnifier_up_down_controls_width">45dp</dimen>
<dimen name="magnifier_up_down_controls_height">40dp</dimen>
+ <dimen name="app_icon_size">32dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4f532b7..9129938 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -928,6 +928,13 @@
<!-- QuickSettings: NFC (on) [CHAR LIMIT=NONE] -->
<string name="quick_settings_nfc_on">NFC is enabled</string>
+ <!-- QuickSettings: Screen record tile [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_screen_record_label">Screen Record</string>
+ <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] -->
+ <string name="quick_settings_screen_record_start">Start</string>
+ <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
+ <string name="quick_settings_screen_record_stop">Stop</string>
+
<!-- Recents: Text that shows above the navigation bar after launching a few apps. [CHAR LIMIT=NONE] -->
<string name="recents_swipe_up_onboarding">Swipe up to switch apps</string>
<!-- Recents: Text that shows above the navigation bar after launching several apps. [CHAR LIMIT=NONE] -->
@@ -1791,6 +1798,29 @@
<string name="notification_done">Done</string>
<!-- Notification: inline controls: undo block button -->
<string name="inline_undo">Undo</string>
+ <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification -->
+ <string name="demote">Mark this notification as not a conversation</string>
+
+ <!-- [CHAR LIMIT=100] Mark this conversation as a favorite -->
+ <string name="notification_conversation_favorite">Favorite</string>
+
+ <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite -->
+ <string name="notification_conversation_unfavorite">Unfavorite</string>
+
+ <!-- [CHAR LIMIT=100] Mute this conversation -->
+ <string name="notification_conversation_mute">Mute</string>
+
+ <!-- [CHAR LIMIT=100] Umute this conversation -->
+ <string name="notification_conversation_unmute">Unmute</string>
+
+ <!-- [CHAR LIMIT=100] Show notification as bubble -->
+ <string name="notification_conversation_bubble">Show as bubble</string>
+
+ <!-- [CHAR LIMIT=100] Turn off bubbles for notification -->
+ <string name="notification_conversation_unbubble">Turn off bubbles</string>
+
+ <!-- [CHAR LIMIT=100] Add this conversation to home screen -->
+ <string name="notification_conversation_home_screen">Add to home screen</string>
<!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] -->
<string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index df0dc46..e475ef1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -387,8 +387,8 @@
* Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
*/
public void refresh() {
- mClockView.refresh();
- mClockViewBold.refresh();
+ mClockView.refreshTime();
+ mClockViewBold.refreshTime();
if (mClockPlugin != null) {
mClockPlugin.onTimeTick();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
index eba2400..99e122e 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
@@ -188,7 +188,7 @@
public void onTimeTick() {
mAnalogClock.onTimeChanged();
mBigClockView.onTimeChanged();
- mLockClock.refresh();
+ mLockClock.refreshTime();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
index 3a2fbe5..fac923c01 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -195,7 +195,7 @@
public void onTimeTick() {
mAnalogClock.onTimeChanged();
mView.onTimeChanged();
- mLockClock.refresh();
+ mLockClock.refreshTime();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index bbe972d..d149591 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -56,6 +56,7 @@
import com.android.systemui.power.PowerUI;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
+import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
@@ -323,6 +324,7 @@
@Inject Lazy<DisplayWindowController> mDisplayWindowController;
@Inject Lazy<SystemWindows> mSystemWindows;
@Inject Lazy<DisplayImeController> mDisplayImeController;
+ @Inject Lazy<RecordingController> mRecordingController;
@Inject
public Dependency() {
@@ -519,6 +521,8 @@
// Dependency problem.
mProviders.put(AutoHideController.class, mAutoHideController::get);
+ mProviders.put(RecordingController.class, mRecordingController::get);
+
sDependency = this;
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
new file mode 100644
index 0000000..e6cdf50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -0,0 +1,21 @@
+/*
+ * 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
+
+import android.service.controls.Control
+
+data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
new file mode 100644
index 0000000..265ddd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls
+
+import android.content.Context
+import android.content.pm.ServiceInfo
+import com.android.settingslib.applications.DefaultAppInfo
+
+class ControlsServiceInfo(
+ context: Context,
+ serviceInfo: ServiceInfo
+) : DefaultAppInfo(
+ context,
+ context.packageManager,
+ context.userId,
+ serviceInfo.componentName
+)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
new file mode 100644
index 0000000..b6cca3f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.util.Log
+
+/**
+ * Stores basic information about a [Control] to persist and keep track of favorites.
+ */
+data class ControlInfo(
+ val component: ComponentName,
+ val controlId: String,
+ val controlTitle: CharSequence,
+ @DeviceTypes.DeviceType val deviceType: Int
+) {
+
+ companion object {
+ private const val TAG = "ControlInfo"
+ private const val SEPARATOR = ":"
+ fun createFromString(string: String): ControlInfo? {
+ val parts = string.split(SEPARATOR)
+ val component = ComponentName.unflattenFromString(parts[0])
+ if (parts.size != 4 || component == null) {
+ Log.e(TAG, "Cannot parse ControlInfo from $string")
+ return null
+ }
+ val type = try {
+ parts[3].toInt()
+ } catch (e: Exception) {
+ Log.e(TAG, "Cannot parse deviceType from ${parts[3]}")
+ return null
+ }
+ return ControlInfo(
+ component,
+ parts[1],
+ parts[2],
+ if (DeviceTypes.validDeviceType(type)) type else DeviceTypes.TYPE_UNKNOWN)
+ }
+ }
+ override fun toString(): String {
+ return component.flattenToString() +
+ "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
+ }
+
+ class Builder {
+ lateinit var componentName: ComponentName
+ lateinit var controlId: String
+ lateinit var controlTitle: CharSequence
+ var deviceType: Int = DeviceTypes.TYPE_UNKNOWN
+
+ fun build() = ControlInfo(componentName, controlId, controlTitle, deviceType)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
new file mode 100644
index 0000000..6b7fc4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+
+interface ControlsBindingController {
+ fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit)
+ fun bindServices(components: List<ComponentName>)
+ fun subscribe(controls: List<ControlInfo>)
+ fun action(controlInfo: ControlInfo, action: ControlAction)
+ fun unsubscribe()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
new file mode 100644
index 0000000..80e48b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.IBinder
+import android.service.controls.Control
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.util.ArrayMap
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import dagger.Lazy
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+@VisibleForTesting
+open class ControlsBindingControllerImpl @Inject constructor(
+ private val context: Context,
+ @Background private val backgroundExecutor: DelayableExecutor,
+ private val lazyController: Lazy<ControlsController>
+) : ControlsBindingController {
+
+ companion object {
+ private const val TAG = "ControlsBindingControllerImpl"
+ }
+
+ private val refreshing = AtomicBoolean(false)
+
+ @GuardedBy("componentMap")
+ private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> =
+ ArrayMap<IBinder, ControlsProviderLifecycleManager>()
+ @GuardedBy("componentMap")
+ private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> =
+ ArrayMap<ComponentName, ControlsProviderLifecycleManager>()
+
+ private val serviceCallback = object : IControlsProviderCallback.Stub() {
+ override fun onLoad(token: IBinder, controls: MutableList<Control>) {
+ backgroundExecutor.execute(OnLoadRunnable(token, controls))
+ }
+
+ override fun onRefreshState(token: IBinder, controlStates: List<Control>) {
+ if (!refreshing.get()) {
+ Log.d(TAG, "Refresh outside of window for token:$token")
+ } else {
+ backgroundExecutor.execute(OnRefreshStateRunnable(token, controlStates))
+ }
+ }
+
+ override fun onControlActionResponse(
+ token: IBinder,
+ controlId: String,
+ @ControlAction.ResponseResult response: Int
+ ) {
+ backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response))
+ }
+ }
+
+ @VisibleForTesting
+ internal open fun createProviderManager(component: ComponentName):
+ ControlsProviderLifecycleManager {
+ return ControlsProviderLifecycleManager(
+ context,
+ backgroundExecutor,
+ serviceCallback,
+ component
+ )
+ }
+
+ private fun retrieveLifecycleManager(component: ComponentName):
+ ControlsProviderLifecycleManager {
+ synchronized(componentMap) {
+ val provider = componentMap.getOrPut(component) {
+ createProviderManager(component)
+ }
+ tokenMap.putIfAbsent(provider.token, provider)
+ return provider
+ }
+ }
+
+ override fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) {
+ val provider = retrieveLifecycleManager(component)
+ provider.maybeBindAndLoad(callback)
+ }
+
+ override fun subscribe(controls: List<ControlInfo>) {
+ val controlsByComponentName = controls.groupBy { it.component }
+ if (refreshing.compareAndSet(false, true)) {
+ controlsByComponentName.forEach {
+ val provider = retrieveLifecycleManager(it.key)
+ backgroundExecutor.execute {
+ provider.maybeBindAndSubscribe(it.value.map { it.controlId })
+ }
+ }
+ }
+ // Unbind unneeded providers
+ val providersWithFavorites = controlsByComponentName.keys
+ synchronized(componentMap) {
+ componentMap.forEach {
+ if (it.key !in providersWithFavorites) {
+ backgroundExecutor.execute { it.value.unbindService() }
+ }
+ }
+ }
+ }
+
+ override fun unsubscribe() {
+ if (refreshing.compareAndSet(true, false)) {
+ val providers = synchronized(componentMap) {
+ componentMap.values.toList()
+ }
+ providers.forEach {
+ backgroundExecutor.execute { it.unsubscribe() }
+ }
+ }
+ }
+
+ override fun action(controlInfo: ControlInfo, action: ControlAction) {
+ val provider = retrieveLifecycleManager(controlInfo.component)
+ provider.maybeBindAndSendAction(controlInfo.controlId, action)
+ }
+
+ override fun bindServices(components: List<ComponentName>) {
+ components.forEach {
+ val provider = retrieveLifecycleManager(it)
+ backgroundExecutor.execute { provider.bindPermanently() }
+ }
+ }
+
+ private abstract inner class CallbackRunnable(val token: IBinder) : Runnable {
+ protected val provider: ControlsProviderLifecycleManager? =
+ synchronized(componentMap) {
+ tokenMap.get(token)
+ }
+ }
+
+ private inner class OnLoadRunnable(
+ token: IBinder,
+ val list: List<Control>
+ ) : CallbackRunnable(token) {
+ override fun run() {
+ if (provider == null) {
+ Log.e(TAG, "No provider found for token:$token")
+ return
+ }
+ synchronized(componentMap) {
+ if (token !in tokenMap.keys) {
+ Log.e(TAG, "Provider for token:$token does not exist anymore")
+ return
+ }
+ }
+ provider.lastLoadCallback?.invoke(list) ?: run {
+ Log.w(TAG, "Null callback")
+ }
+ provider.maybeUnbindAndRemoveCallback()
+ }
+ }
+
+ private inner class OnRefreshStateRunnable(
+ token: IBinder,
+ val list: List<Control>
+ ) : CallbackRunnable(token) {
+ override fun run() {
+ if (!refreshing.get()) {
+ Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}")
+ }
+ provider?.let {
+ lazyController.get().refreshStatus(it.componentName, list)
+ }
+ }
+ }
+
+ private inner class OnActionResponseRunnable(
+ token: IBinder,
+ val controlId: String,
+ @ControlAction.ResponseResult val response: Int
+ ) : CallbackRunnable(token) {
+ override fun run() {
+ provider?.let {
+ lazyController.get().onActionResponse(it.componentName, controlId, response)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
new file mode 100644
index 0000000..4d95822
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+import com.android.systemui.controls.ControlStatus
+
+interface ControlsController {
+ val available: Boolean
+
+ fun getFavoriteControls(): List<ControlInfo>
+ fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit)
+ fun subscribeToFavorites()
+ fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
+ fun unsubscribe()
+ fun action(controlInfo: ControlInfo, action: ControlAction)
+ fun refreshStatus(componentName: ComponentName, controls: List<Control>)
+ fun onActionResponse(
+ componentName: ComponentName,
+ controlId: String,
+ @ControlAction.ResponseResult response: Int
+ )
+ fun clearFavorites()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
new file mode 100644
index 0000000..7e328e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Environment
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+import android.util.ArrayMap
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.DumpController
+import com.android.systemui.Dumpable
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlsControllerImpl @Inject constructor (
+ private val context: Context,
+ @Background private val executor: DelayableExecutor,
+ private val uiController: ControlsUiController,
+ private val bindingController: ControlsBindingController,
+ private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+ dumpController: DumpController
+) : Dumpable, ControlsController {
+
+ companion object {
+ private const val TAG = "ControlsControllerImpl"
+ const val CONTROLS_AVAILABLE = "systemui.controls_available"
+ }
+
+ override val available = Settings.Secure.getInt(
+ context.contentResolver, CONTROLS_AVAILABLE, 0) != 0
+ val persistenceWrapper = optionalWrapper.orElseGet {
+ ControlsFavoritePersistenceWrapper(
+ Environment.buildPath(
+ context.filesDir,
+ ControlsFavoritePersistenceWrapper.FILE_NAME),
+ executor
+ )
+ }
+
+ // Map of map: ComponentName -> (String -> ControlInfo)
+ @GuardedBy("currentFavorites")
+ private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
+
+ init {
+ if (available) {
+ dumpController.registerDumpable(this)
+ loadFavorites()
+ }
+ }
+
+ private fun loadFavorites() {
+ val infos = persistenceWrapper.readFavorites()
+ synchronized(currentFavorites) {
+ infos.forEach {
+ currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() })
+ .put(it.controlId, it)
+ }
+ }
+ }
+
+ override fun loadForComponent(
+ componentName: ComponentName,
+ callback: (List<ControlStatus>) -> Unit
+ ) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ bindingController.bindAndLoad(componentName) {
+ synchronized(currentFavorites) {
+ val favoritesForComponentKeys: Set<String> =
+ currentFavorites.get(componentName)?.keys ?: emptySet()
+ val changed = updateFavoritesLocked(componentName, it)
+ if (changed) {
+ persistenceWrapper.storeFavorites(favoritesAsListLocked())
+ }
+ val removed = findRemovedLocked(favoritesForComponentKeys, it)
+ callback(removed.map { currentFavorites.getValue(componentName).getValue(it) }
+ .map(::createRemovedStatus) +
+ it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) })
+ }
+ }
+ }
+
+ private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus {
+ val intent = Intent(context, ControlsFavoritingActivity::class.java).apply {
+ putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+ val pendingIntent = PendingIntent.getActivity(context,
+ controlInfo.component.hashCode(),
+ intent,
+ 0)
+ val control = Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+ .setTitle(controlInfo.controlTitle)
+ .setDeviceType(controlInfo.deviceType)
+ .build()
+ return ControlStatus(control, true, true)
+ }
+
+ @GuardedBy("currentFavorites")
+ private fun findRemovedLocked(favoriteKeys: Set<String>, list: List<Control>): Set<String> {
+ val controlsKeys = list.map { it.controlId }
+ return favoriteKeys.minus(controlsKeys)
+ }
+
+ @GuardedBy("currentFavorites")
+ private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean {
+ val favorites = currentFavorites.get(componentName) ?: mutableMapOf()
+ val favoriteKeys = favorites.keys
+ if (favoriteKeys.isEmpty()) return false // early return
+ var changed = false
+ list.forEach {
+ if (it.controlId in favoriteKeys) {
+ val value = favorites.getValue(it.controlId)
+ if (value.controlTitle != it.title || value.deviceType != it.deviceType) {
+ favorites[it.controlId] = value.copy(controlTitle = it.title,
+ deviceType = it.deviceType)
+ changed = true
+ }
+ }
+ }
+ return changed
+ }
+
+ @GuardedBy("currentFavorites")
+ private fun favoritesAsListLocked(): List<ControlInfo> {
+ return currentFavorites.flatMap { it.value.values }
+ }
+
+ override fun subscribeToFavorites() {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ // Make a copy of the favorites list
+ val favorites = synchronized(currentFavorites) {
+ currentFavorites.flatMap { it.value.values.toList() }
+ }
+ bindingController.subscribe(favorites)
+ }
+
+ override fun unsubscribe() {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ bindingController.unsubscribe()
+ }
+
+ override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ var changed = false
+ val listOfControls = synchronized(currentFavorites) {
+ if (state) {
+ if (controlInfo.component !in currentFavorites) {
+ currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>())
+ changed = true
+ }
+ val controlsForComponent = currentFavorites.getValue(controlInfo.component)
+ if (controlInfo.controlId !in controlsForComponent) {
+ controlsForComponent.put(controlInfo.controlId, controlInfo)
+ changed = true
+ } else {
+ if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) {
+ controlsForComponent.put(controlInfo.controlId, controlInfo)
+ changed = true
+ }
+ }
+ } else {
+ changed = currentFavorites.get(controlInfo.component)
+ ?.remove(controlInfo.controlId) != null
+ }
+ favoritesAsListLocked()
+ }
+ if (changed) {
+ persistenceWrapper.storeFavorites(listOfControls)
+ }
+ }
+
+ override fun refreshStatus(componentName: ComponentName, controls: List<Control>) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ executor.execute {
+ synchronized(currentFavorites) {
+ val changed = updateFavoritesLocked(componentName, controls)
+ if (changed) {
+ persistenceWrapper.storeFavorites(favoritesAsListLocked())
+ }
+ }
+ }
+ uiController.onRefreshState(componentName, controls)
+ }
+
+ override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ uiController.onActionResponse(componentName, controlId, response)
+ }
+
+ override fun getFavoriteControls(): List<ControlInfo> {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return emptyList()
+ }
+ synchronized(currentFavorites) {
+ return favoritesAsListLocked()
+ }
+ }
+
+ override fun action(controlInfo: ControlInfo, action: ControlAction) {
+ bindingController.action(controlInfo, action)
+ }
+
+ override fun clearFavorites() {
+ val changed = synchronized(currentFavorites) {
+ currentFavorites.isNotEmpty().also {
+ currentFavorites.clear()
+ }
+ }
+ if (changed) {
+ persistenceWrapper.storeFavorites(emptyList())
+ }
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("ControlsController state:")
+ pw.println(" Favorites:")
+ synchronized(currentFavorites) {
+ currentFavorites.forEach {
+ it.value.forEach {
+ pw.println(" ${it.value}")
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
new file mode 100644
index 0000000..6f2d71f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.controller
+
+import android.app.ActivityManager
+import android.content.ComponentName
+import android.util.AtomicFile
+import android.util.Log
+import android.util.Slog
+import android.util.Xml
+import com.android.systemui.util.concurrency.DelayableExecutor
+import libcore.io.IoUtils
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.IOException
+
+class ControlsFavoritePersistenceWrapper(
+ val file: File,
+ val executor: DelayableExecutor
+) {
+
+ companion object {
+ private const val TAG = "ControlsFavoritePersistenceWrapper"
+ const val FILE_NAME = "controls_favorites.xml"
+ private const val TAG_CONTROLS = "controls"
+ private const val TAG_CONTROL = "control"
+ private const val TAG_COMPONENT = "component"
+ private const val TAG_ID = "id"
+ private const val TAG_TITLE = "title"
+ private const val TAG_TYPE = "type"
+ }
+
+ val currentUser: Int
+ get() = ActivityManager.getCurrentUser()
+
+ fun storeFavorites(list: List<ControlInfo>) {
+ executor.execute {
+ val atomicFile = AtomicFile(file)
+ val writer = try {
+ atomicFile.startWrite()
+ } catch (e: IOException) {
+ Log.e(TAG, "Failed to start write file", e)
+ return@execute
+ }
+ try {
+ Xml.newSerializer().apply {
+ setOutput(writer, "utf-8")
+ setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
+ startDocument(null, true)
+ startTag(null, TAG_CONTROLS)
+ list.forEach {
+ startTag(null, TAG_CONTROL)
+ attribute(null, TAG_COMPONENT, it.component.flattenToString())
+ attribute(null, TAG_ID, it.controlId)
+ attribute(null, TAG_TITLE, it.controlTitle.toString())
+ attribute(null, TAG_TYPE, it.deviceType.toString())
+ endTag(null, TAG_CONTROL)
+ }
+ endTag(null, TAG_CONTROLS)
+ endDocument()
+ atomicFile.finishWrite(writer)
+ }
+ } catch (t: Throwable) {
+ Log.e(TAG, "Failed to write file, reverting to previous version")
+ atomicFile.failWrite(writer)
+ } finally {
+ IoUtils.closeQuietly(writer)
+ }
+ }
+ }
+
+ fun readFavorites(): List<ControlInfo> {
+ if (!file.exists()) {
+ Log.d(TAG, "No favorites, returning empty list")
+ return emptyList()
+ }
+ val reader = try {
+ FileInputStream(file)
+ } catch (fnfe: FileNotFoundException) {
+ Slog.i(TAG, "No file found")
+ return emptyList()
+ }
+ try {
+ val parser = Xml.newPullParser()
+ parser.setInput(reader, null)
+ return parseXml(parser)
+ } catch (e: XmlPullParserException) {
+ throw IllegalStateException("Failed parsing favorites file: $file", e)
+ } catch (e: IOException) {
+ throw IllegalStateException("Failed parsing favorites file: $file", e)
+ } finally {
+ IoUtils.closeQuietly(reader)
+ }
+ }
+
+ private fun parseXml(parser: XmlPullParser): List<ControlInfo> {
+ var type: Int = 0
+ val infos = mutableListOf<ControlInfo>()
+ while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue
+ }
+ val tagName = parser.name
+ if (tagName == TAG_CONTROL) {
+ val component = ComponentName.unflattenFromString(
+ parser.getAttributeValue(null, TAG_COMPONENT))
+ val id = parser.getAttributeValue(null, TAG_ID)
+ val title = parser.getAttributeValue(null, TAG_TITLE)
+ val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt()
+ if (component != null && id != null && title != null && type != null) {
+ infos.add(ControlInfo(component, id, title, type))
+ }
+ }
+ }
+ return infos
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
new file mode 100644
index 0000000..79057ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.os.RemoteException
+import android.service.controls.Control
+import android.service.controls.ControlsProviderService.CALLBACK_BINDER
+import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
+import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.util.ArraySet
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+
+typealias LoadCallback = (List<Control>) -> Unit
+class ControlsProviderLifecycleManager(
+ private val context: Context,
+ private val executor: DelayableExecutor,
+ private val serviceCallback: IControlsProviderCallback.Stub,
+ val componentName: ComponentName
+) : IBinder.DeathRecipient {
+
+ var lastLoadCallback: LoadCallback? = null
+ private set
+ val token: IBinder = Binder()
+ private var unbindImmediate = false
+ private var requiresBound = false
+ private var isBound = false
+ @GuardedBy("queuedMessages")
+ private val queuedMessages: MutableSet<Message> = ArraySet()
+ private var wrapper: ControlsProviderServiceWrapper? = null
+ private var bindTryCount = 0
+ private val TAG = javaClass.simpleName
+ private var onLoadCanceller: Runnable? = null
+
+ companion object {
+ private const val MSG_LOAD = 0
+ private const val MSG_SUBSCRIBE = 1
+ private const val MSG_UNSUBSCRIBE = 2
+ private const val MSG_ON_ACTION = 3
+ private const val MSG_UNBIND = 4
+ private const val BIND_RETRY_DELAY = 1000L // ms
+ private const val LOAD_TIMEOUT = 5000L // ms
+ private const val MAX_BIND_RETRIES = 5
+ private const val DEBUG = true
+ private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
+ Context.BIND_WAIVE_PRIORITY
+ }
+
+ private val intent = Intent().apply {
+ component = componentName
+ putExtra(CALLBACK_BUNDLE, Bundle().apply {
+ putBinder(CALLBACK_BINDER, serviceCallback)
+ putBinder(CALLBACK_TOKEN, token)
+ })
+ }
+
+ private fun bindService(bind: Boolean) {
+ requiresBound = bind
+ if (bind) {
+ if (bindTryCount == MAX_BIND_RETRIES) {
+ return
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Binding service $intent")
+ }
+ bindTryCount++
+ try {
+ isBound = context.bindService(intent, serviceConnection, BIND_FLAGS)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "Failed to bind to service", e)
+ isBound = false
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Unbinding service $intent")
+ }
+ bindTryCount = 0
+ wrapper = null
+ if (isBound) {
+ context.unbindService(serviceConnection)
+ isBound = false
+ }
+ }
+ }
+
+ fun bindPermanently() {
+ unbindImmediate = false
+ unqueueMessage(Message.Unbind)
+ bindService(true)
+ }
+
+ private val serviceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ if (DEBUG) Log.d(TAG, "onServiceConnected $name")
+ bindTryCount = 0
+ wrapper = ControlsProviderServiceWrapper(IControlsProvider.Stub.asInterface(service))
+ try {
+ service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
+ } catch (_: RemoteException) {}
+ handlePendingMessages()
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ if (DEBUG) Log.d(TAG, "onServiceDisconnected $name")
+ isBound = false
+ bindService(false)
+ }
+ }
+
+ private fun handlePendingMessages() {
+ val queue = synchronized(queuedMessages) {
+ ArraySet(queuedMessages).also {
+ queuedMessages.clear()
+ }
+ }
+ if (Message.Unbind in queue) {
+ bindService(false)
+ return
+ }
+ if (Message.Load in queue) {
+ load()
+ }
+ queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run {
+ subscribe(this)
+ }
+ queue.filter { it is Message.Action }.forEach {
+ val msg = it as Message.Action
+ onAction(msg.id, msg.action)
+ }
+ }
+
+ override fun binderDied() {
+ if (wrapper == null) return
+ wrapper = null
+ if (requiresBound) {
+ if (DEBUG) {
+ Log.d(TAG, "binderDied")
+ }
+ // Try rebinding some time later
+ }
+ }
+
+ private fun queueMessage(message: Message) {
+ synchronized(queuedMessages) {
+ queuedMessages.add(message)
+ }
+ }
+
+ private fun unqueueMessage(message: Message) {
+ synchronized(queuedMessages) {
+ queuedMessages.removeIf { it.type == message.type }
+ }
+ }
+
+ private fun load() {
+ if (DEBUG) {
+ Log.d(TAG, "load $componentName")
+ }
+ if (!(wrapper?.load() ?: false)) {
+ queueMessage(Message.Load)
+ binderDied()
+ }
+ }
+
+ fun maybeBindAndLoad(callback: LoadCallback) {
+ unqueueMessage(Message.Unbind)
+ lastLoadCallback = callback
+ onLoadCanceller = executor.executeDelayed({
+ // Didn't receive a response in time, log and send back empty list
+ Log.d(TAG, "Timeout waiting onLoad for $componentName")
+ serviceCallback.onLoad(token, emptyList())
+ }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
+ if (isBound) {
+ load()
+ } else {
+ queueMessage(Message.Load)
+ unbindImmediate = true
+ bindService(true)
+ }
+ }
+
+ fun maybeBindAndSubscribe(controlIds: List<String>) {
+ if (isBound) {
+ subscribe(controlIds)
+ } else {
+ queueMessage(Message.Subscribe(controlIds))
+ bindService(true)
+ }
+ }
+
+ private fun subscribe(controlIds: List<String>) {
+ if (DEBUG) {
+ Log.d(TAG, "subscribe $componentName - $controlIds")
+ }
+ if (!(wrapper?.subscribe(controlIds) ?: false)) {
+ queueMessage(Message.Subscribe(controlIds))
+ binderDied()
+ }
+ }
+
+ fun maybeBindAndSendAction(controlId: String, action: ControlAction) {
+ if (isBound) {
+ onAction(controlId, action)
+ } else {
+ queueMessage(Message.Action(controlId, action))
+ bindService(true)
+ }
+ }
+
+ private fun onAction(controlId: String, action: ControlAction) {
+ if (DEBUG) {
+ Log.d(TAG, "onAction $componentName - $controlId")
+ }
+ if (!(wrapper?.onAction(controlId, action) ?: false)) {
+ queueMessage(Message.Action(controlId, action))
+ binderDied()
+ }
+ }
+
+ fun unsubscribe() {
+ if (DEBUG) {
+ Log.d(TAG, "unsubscribe $componentName")
+ }
+ unqueueMessage(Message.Subscribe(emptyList())) // Removes all subscribe messages
+ wrapper?.unsubscribe()
+ }
+
+ fun maybeUnbindAndRemoveCallback() {
+ lastLoadCallback = null
+ onLoadCanceller?.run()
+ onLoadCanceller = null
+ if (unbindImmediate) {
+ bindService(false)
+ }
+ }
+
+ fun unbindService() {
+ unbindImmediate = true
+ maybeUnbindAndRemoveCallback()
+ }
+
+ sealed class Message {
+ abstract val type: Int
+ object Load : Message() {
+ override val type = MSG_LOAD
+ }
+ object Unbind : Message() {
+ override val type = MSG_UNBIND
+ }
+ class Subscribe(val list: List<String>) : Message() {
+ override val type = MSG_SUBSCRIBE
+ }
+ class Action(val id: String, val action: ControlAction) : Message() {
+ override val type = MSG_ON_ACTION
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt
new file mode 100644
index 0000000..882a10d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.service.controls.actions.ControlAction
+import android.service.controls.IControlsProvider
+import android.util.Log
+
+class ControlsProviderServiceWrapper(val service: IControlsProvider) {
+ companion object {
+ private const val TAG = "ControlsProviderServiceWrapper"
+ }
+
+ private fun callThroughService(block: () -> Unit): Boolean {
+ try {
+ block()
+ return true
+ } catch (ex: Exception) {
+ Log.d(TAG, "Caught exception from ControlsProviderService", ex)
+ return false
+ }
+ }
+
+ fun load(): Boolean {
+ return callThroughService {
+ service.load()
+ }
+ }
+
+ fun subscribe(controlIds: List<String>): Boolean {
+ return callThroughService {
+ service.subscribe(controlIds)
+ }
+ }
+
+ fun unsubscribe(): Boolean {
+ return callThroughService {
+ service.unsubscribe()
+ }
+ }
+
+ fun onAction(controlId: String, action: ControlAction): Boolean {
+ return callThroughService {
+ service.onAction(controlId, action)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
new file mode 100644
index 0000000..859311e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.dagger
+
+import android.app.Activity
+import com.android.systemui.controls.controller.ControlsBindingController
+import com.android.systemui.controls.controller.ControlsBindingControllerImpl
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.management.ControlsListingControllerImpl
+import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.ControlsUiControllerImpl
+import dagger.Binds
+import dagger.BindsOptionalOf
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class ControlsModule {
+
+ @Binds
+ abstract fun provideControlsListingController(
+ controller: ControlsListingControllerImpl
+ ): ControlsListingController
+
+ @Binds
+ abstract fun provideControlsController(controller: ControlsControllerImpl): ControlsController
+
+ @Binds
+ abstract fun provideControlsBindingController(
+ controller: ControlsBindingControllerImpl
+ ): ControlsBindingController
+
+ @Binds
+ abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
+
+ @BindsOptionalOf
+ abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
+
+ @Binds
+ @IntoMap
+ @ClassKey(ControlsProviderSelectorActivity::class)
+ abstract fun provideControlsProviderActivity(
+ activity: ControlsProviderSelectorActivity
+ ): Activity
+
+ @Binds
+ @IntoMap
+ @ClassKey(ControlsFavoritingActivity::class)
+ abstract fun provideControlsFavoritingActivity(
+ activity: ControlsFavoritingActivity
+ ): Activity
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
new file mode 100644
index 0000000..d62bb4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.recyclerview.widget.RecyclerView
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.R
+import java.util.concurrent.Executor
+
+/**
+ * Adapter for binding [CandidateInfo] related to [ControlsProviderService].
+ *
+ * This class handles subscribing and keeping track of the list of valid applications for
+ * displaying.
+ *
+ * @param uiExecutor an executor on the view thread of the containing [RecyclerView]
+ * @param lifecycle the lifecycle of the containing [LifecycleOwner] to control listening status
+ * @param controlsListingController the controller to keep track of valid applications
+ * @param layoutInflater an inflater for the views in the containing [RecyclerView]
+ * @param onAppSelected a callback to indicate that an app has been selected in the list.
+ */
+class AppAdapter(
+ uiExecutor: Executor,
+ lifecycle: Lifecycle,
+ controlsListingController: ControlsListingController,
+ private val layoutInflater: LayoutInflater,
+ private val onAppSelected: (ComponentName?) -> Unit = {}
+) : RecyclerView.Adapter<AppAdapter.Holder>() {
+
+ private var listOfServices = emptyList<CandidateInfo>()
+
+ private val callback = object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(list: List<CandidateInfo>) {
+ uiExecutor.execute {
+ listOfServices = list
+ notifyDataSetChanged()
+ }
+ }
+ }
+
+ init {
+ controlsListingController.observe(lifecycle, callback)
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
+ return Holder(layoutInflater.inflate(R.layout.app_item, parent, false))
+ }
+
+ override fun getItemCount() = listOfServices.size
+
+ override fun onBindViewHolder(holder: Holder, index: Int) {
+ holder.bindData(listOfServices[index])
+ holder.itemView.setOnClickListener {
+ onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key))
+ }
+ }
+
+ /**
+ * Holder for binding views in the [RecyclerView]-
+ */
+ class Holder(view: View) : RecyclerView.ViewHolder(view) {
+ private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon)
+ private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title)
+
+ /**
+ * Bind data to the view
+ * @param data Information about the [ControlsProviderService] to bind to the data
+ */
+ fun bindData(data: CandidateInfo) {
+ icon.setImageDrawable(data.loadIcon())
+ title.text = data.loadLabel()
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
new file mode 100644
index 0000000..e6d3c26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CheckBox
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.controller.ControlInfo
+
+/**
+ * Adapter for binding [Control] information to views.
+ *
+ * @param layoutInflater an inflater for the views in the containing [RecyclerView]
+ * @param favoriteCallback a callback to be called when the favorite status of a [Control] is
+ * changed. The callback will take a [ControlInfo.Builder] that's
+ * pre-populated with the [Control] information and the new favorite
+ * status.
+ */
+class ControlAdapter(
+ private val layoutInflater: LayoutInflater,
+ private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit
+) : RecyclerView.Adapter<ControlAdapter.Holder>() {
+
+ var listOfControls = emptyList<ControlStatus>()
+
+ override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
+ return Holder(layoutInflater.inflate(R.layout.control_item, parent, false))
+ }
+
+ override fun getItemCount() = listOfControls.size
+
+ override fun onBindViewHolder(holder: Holder, index: Int) {
+ holder.bindData(listOfControls[index], favoriteCallback)
+ }
+
+ /**
+ * Holder for binding views in the [RecyclerView]-
+ */
+ class Holder(view: View) : RecyclerView.ViewHolder(view) {
+ private val title: TextView = itemView.requireViewById(R.id.title)
+ private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
+ private val favorite: CheckBox = itemView.requireViewById(R.id.favorite)
+
+ /**
+ * Bind data to the view
+ * @param data information about the [Control]
+ * @param callback a callback to be called when the favorite status of the [Control] is
+ * changed. The callback will take a [ControlInfo.Builder] that's
+ * pre-populated with the [Control] information and the new favorite status.
+ */
+ fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) {
+ title.text = data.control.title
+ subtitle.text = data.control.subtitle
+ favorite.isChecked = data.favorite
+ favorite.setOnClickListener {
+ val infoBuilder = ControlInfo.Builder().apply {
+ controlId = data.control.controlId
+ controlTitle = data.control.title
+ deviceType = data.control.deviceType
+ }
+ callback(infoBuilder, favorite.isChecked)
+ }
+ }
+ }
+
+ fun setItems(list: List<ControlStatus>) {
+ listOfControls = list
+ notifyDataSetChanged()
+ }
+}
\ 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
new file mode 100644
index 0000000..01c4fef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.os.Bundle
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.controls.controller.ControlInfo
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class ControlsFavoritingActivity @Inject constructor(
+ @Main private val executor: Executor,
+ private val controller: ControlsControllerImpl
+) : Activity() {
+
+ companion object {
+ private const val TAG = "ControlsFavoritingActivity"
+ const val EXTRA_APP = "extra_app_label"
+ const val EXTRA_COMPONENT = "extra_component"
+ }
+
+ private lateinit var recyclerView: RecyclerView
+ private lateinit var adapter: ControlAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val app = intent.getCharSequenceExtra(EXTRA_APP)
+ val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
+
+ // If we have no component name, there's not much we can do.
+ val callback = component?.let {
+ { infoBuilder: ControlInfo.Builder, status: Boolean ->
+ infoBuilder.componentName = it
+ controller.changeFavoriteStatus(infoBuilder.build(), status)
+ }
+ } ?: { _, _ -> Unit }
+
+ recyclerView = RecyclerView(applicationContext)
+ adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback)
+ recyclerView.adapter = adapter
+ recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+ if (app != null) {
+ setTitle("Controls for $app")
+ } else {
+ setTitle("Controls")
+ }
+ setContentView(recyclerView)
+
+ component?.let {
+ controller.loadForComponent(it) {
+ executor.execute {
+ adapter.setItems(it)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
new file mode 100644
index 0000000..09e0ce9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.statusbar.policy.CallbackController
+
+interface ControlsListingController :
+ CallbackController<ControlsListingController.ControlsListingCallback> {
+
+ fun getCurrentServices(): List<CandidateInfo>
+ fun getAppLabel(name: ComponentName): CharSequence? = ""
+
+ interface ControlsListingCallback {
+ fun onServicesUpdated(list: List<CandidateInfo>)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
new file mode 100644
index 0000000..9372162
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ServiceInfo
+import android.service.controls.ControlsProviderService
+import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settingslib.applications.DefaultAppInfo
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Provides a listing of components to be used as ControlsServiceProvider.
+ *
+ * This controller keeps track of components that satisfy:
+ *
+ * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
+ * * Has the bind permission `android.permission.BIND_CONTROLS`
+ */
+@Singleton
+class ControlsListingControllerImpl @VisibleForTesting constructor(
+ private val context: Context,
+ @Background private val backgroundExecutor: Executor,
+ private val serviceListing: ServiceListing
+) : ControlsListingController {
+
+ @Inject
+ constructor(context: Context, executor: Executor): this(
+ context,
+ executor,
+ ServiceListing.Builder(context)
+ .setIntentAction(ControlsProviderService.CONTROLS_ACTION)
+ .setPermission("android.permission.BIND_CONTROLS")
+ .setNoun("Controls Provider")
+ .setSetting("controls_providers")
+ .setTag("controls_providers")
+ .build()
+ )
+
+ companion object {
+ private const val TAG = "ControlsListingControllerImpl"
+ }
+
+ private var availableServices = emptyList<ServiceInfo>()
+
+ init {
+ serviceListing.addCallback {
+ Log.d(TAG, "ServiceConfig reloaded")
+ availableServices = it.toList()
+
+ backgroundExecutor.execute {
+ callbacks.forEach {
+ it.onServicesUpdated(getCurrentServices())
+ }
+ }
+ }
+ }
+
+ // All operations in background thread
+ private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
+
+ /**
+ * Adds a callback to this controller.
+ *
+ * The callback will be notified after it is added as well as any time that the valid
+ * components change.
+ *
+ * @param listener a callback to be notified
+ */
+ override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
+ backgroundExecutor.execute {
+ callbacks.add(listener)
+ if (callbacks.size == 1) {
+ serviceListing.setListening(true)
+ serviceListing.reload()
+ } else {
+ listener.onServicesUpdated(getCurrentServices())
+ }
+ }
+ }
+
+ /**
+ * Removes a callback from this controller.
+ *
+ * @param listener the callback to be removed.
+ */
+ override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) {
+ backgroundExecutor.execute {
+ callbacks.remove(listener)
+ if (callbacks.size == 0) {
+ serviceListing.setListening(false)
+ }
+ }
+ }
+
+ /**
+ * @return a list of components that satisfy the requirements to be a
+ * [ControlsProviderService]
+ */
+ override fun getCurrentServices(): List<CandidateInfo> =
+ availableServices.map { ControlsServiceInfo(context, it) }
+
+ /**
+ * Get the localized label for the component.
+ *
+ * @param name the name of the component
+ * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
+ */
+ override fun getAppLabel(name: ComponentName): CharSequence? {
+ return getCurrentServices().firstOrNull { (it as? DefaultAppInfo)?.componentName == name }
+ ?.loadLabel()
+ }
+}
\ 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
new file mode 100644
index 0000000..69af516
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.LifecycleActivity
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Activity to select an application to favorite the [Control] provided by them.
+ */
+class ControlsProviderSelectorActivity @Inject constructor(
+ @Main private val executor: Executor,
+ private val listingController: ControlsListingController
+) : LifecycleActivity() {
+
+ companion object {
+ private const val TAG = "ControlsProviderSelectorActivity"
+ }
+
+ private lateinit var recyclerView: RecyclerView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ recyclerView = RecyclerView(applicationContext)
+ recyclerView.adapter = AppAdapter(executor, lifecycle, listingController,
+ LayoutInflater.from(this), ::launchFavoritingActivity)
+ recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+ setContentView(recyclerView)
+ }
+
+ /**
+ * Launch the [ControlsFavoritingActivity] for the specified component.
+ * @param component a component name for a [ControlsProviderService]
+ */
+ fun launchFavoritingActivity(component: ComponentName?) {
+ component?.let {
+ val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply {
+ putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it))
+ putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+ startActivity(intent)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
new file mode 100644
index 0000000..0270c2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.ui
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+
+interface ControlsUiController {
+ fun onRefreshState(componentName: ComponentName, controls: List<Control>)
+ fun onActionResponse(
+ componentName: ComponentName,
+ controlId: String,
+ @ControlAction.ResponseResult response: Int
+ )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
new file mode 100644
index 0000000..0ace126
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.ui
+
+import android.content.ComponentName
+import android.service.controls.Control
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlsUiControllerImpl @Inject constructor() : ControlsUiController {
+
+ override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
+ TODO("not implemented")
+ }
+
+ override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+ TODO("not implemented")
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index df79310..9598e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,6 +20,7 @@
import com.android.systemui.ForegroundServicesDialog;
import com.android.systemui.keyguard.WorkLockActivity;
+import com.android.systemui.screenrecord.ScreenRecordDialog;
import com.android.systemui.settings.BrightnessDialog;
import com.android.systemui.tuner.TunerActivity;
@@ -29,7 +30,7 @@
import dagger.multibindings.IntoMap;
/**
- * Services and Activities that are injectable should go here.
+ * Activities that are injectable should go here.
*/
@Module
public abstract class DefaultActivityBinder {
@@ -56,4 +57,10 @@
@IntoMap
@ClassKey(BrightnessDialog.class)
public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
+
+ /** Inject into ScreenRecordDialog */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenRecordDialog.class)
+ public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index f790d99..f006acf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -22,6 +22,7 @@
import com.android.systemui.SystemUIService;
import com.android.systemui.doze.DozeService;
import com.android.systemui.keyguard.KeyguardService;
+import com.android.systemui.screenrecord.RecordingService;
import com.android.systemui.screenshot.TakeScreenshotService;
import dagger.Binds;
@@ -63,4 +64,10 @@
@IntoMap
@ClassKey(TakeScreenshotService.class)
public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+
+ /** Inject into RecordingService */
+ @Binds
+ @IntoMap
+ @ClassKey(RecordingService.class)
+ public abstract Service bindRecordingService(RecordingService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 20917bd..2877ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -20,6 +20,7 @@
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.appops.AppOpsControllerImpl;
import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.globalactions.GlobalActionsImpl;
@@ -85,7 +86,7 @@
/**
* Maps interfaces to implementations for use with Dagger.
*/
-@Module
+@Module(includes = {ControlsModule.class})
public abstract class DependencyBinder {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index f06c849..2b53727 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -42,6 +42,7 @@
import com.android.systemui.qs.tiles.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.ScreenRecordTile;
import com.android.systemui.qs.tiles.UiModeNightTile;
import com.android.systemui.qs.tiles.UserTile;
import com.android.systemui.qs.tiles.WifiTile;
@@ -77,6 +78,7 @@
private final Provider<NfcTile> mNfcTileProvider;
private final Provider<GarbageMonitor.MemoryTile> mMemoryTileProvider;
private final Provider<UiModeNightTile> mUiModeNightTileProvider;
+ private final Provider<ScreenRecordTile> mScreenRecordTileProvider;
private QSTileHost mHost;
@@ -100,7 +102,8 @@
Provider<NightDisplayTile> nightDisplayTileProvider,
Provider<NfcTile> nfcTileProvider,
Provider<GarbageMonitor.MemoryTile> memoryTileProvider,
- Provider<UiModeNightTile> uiModeNightTileProvider) {
+ Provider<UiModeNightTile> uiModeNightTileProvider,
+ Provider<ScreenRecordTile> screenRecordTileProvider) {
mWifiTileProvider = wifiTileProvider;
mBluetoothTileProvider = bluetoothTileProvider;
mControlsTileProvider = controlsTileProvider;
@@ -121,6 +124,7 @@
mNfcTileProvider = nfcTileProvider;
mMemoryTileProvider = memoryTileProvider;
mUiModeNightTileProvider = uiModeNightTileProvider;
+ mScreenRecordTileProvider = screenRecordTileProvider;
}
public void setHost(QSTileHost host) {
@@ -179,6 +183,8 @@
return mNfcTileProvider.get();
case "dark":
return mUiModeNightTileProvider.get();
+ case "screenrecord":
+ return mScreenRecordTileProvider.get();
}
// Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
new file mode 100644
index 0000000..596c3b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -0,0 +1,132 @@
+/*
+ * 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.qs.tiles;
+
+import android.content.Intent;
+import android.service.quicksettings.Tile;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.screenrecord.RecordingController;
+
+import javax.inject.Inject;
+
+/**
+ * Quick settings tile for screen recording
+ */
+public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> {
+ private static final String TAG = "ScreenRecordTile";
+ private RecordingController mController;
+ private long mMillisUntilFinished = 0;
+
+ @Inject
+ public ScreenRecordTile(QSHost host, RecordingController controller) {
+ super(host);
+ mController = controller;
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ if (mController.isStarting()) {
+ cancelCountdown();
+ } else if (mController.isRecording()) {
+ stopRecording();
+ } else {
+ startCountdown();
+ }
+ refreshState();
+ }
+
+ /**
+ * Refresh tile state
+ * @param millisUntilFinished Time until countdown completes, or 0 if not counting down
+ */
+ public void refreshState(long millisUntilFinished) {
+ mMillisUntilFinished = millisUntilFinished;
+ refreshState();
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ boolean isStarting = mController.isStarting();
+ boolean isRecording = mController.isRecording();
+
+ state.label = mContext.getString(R.string.quick_settings_screen_record_label);
+ state.value = isRecording || isStarting;
+ state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.handlesLongClick = false;
+
+ if (isRecording) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+ state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_stop);
+ } else if (isStarting) {
+ // round, since the timer isn't exact
+ int countdown = (int) Math.floorDiv(mMillisUntilFinished + 500, 1000);
+ // TODO update icon
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+ state.secondaryLabel = String.format("%d...", countdown);
+ } else {
+ // TODO update icon
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+ state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return 0;
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return null;
+ }
+
+ @Override
+ protected void handleSetListening(boolean listening) {
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.quick_settings_screen_record_label);
+ }
+
+ private void startCountdown() {
+ Log.d(TAG, "Starting countdown");
+ // Close QS, otherwise the permission dialog appears beneath it
+ getHost().collapsePanels();
+ mController.launchRecordPrompt(this);
+ }
+
+ private void cancelCountdown() {
+ Log.d(TAG, "Cancelling countdown");
+ mController.cancelCountdown();
+ }
+
+ private void stopRecording() {
+ Log.d(TAG, "Stopping recording from tile");
+ mController.stopRecording();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
new file mode 100644
index 0000000..188501e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -0,0 +1,166 @@
+/*
+ * 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.screenrecord;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.CountDownTimer;
+import android.util.Log;
+
+import com.android.systemui.qs.tiles.ScreenRecordTile;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class to initiate a screen recording
+ */
+@Singleton
+public class RecordingController {
+ private static final String TAG = "RecordingController";
+ private static final String SYSUI_PACKAGE = "com.android.systemui";
+ private static final String SYSUI_SCREENRECORD_LAUNCHER =
+ "com.android.systemui.screenrecord.ScreenRecordDialog";
+
+ private final Context mContext;
+ private boolean mIsStarting;
+ private boolean mIsRecording;
+ private ScreenRecordTile mTileToUpdate;
+ private PendingIntent mStopIntent;
+ private CountDownTimer mCountDownTimer = null;
+
+ /**
+ * Create a new RecordingController
+ * @param context Context for the controller
+ */
+ @Inject
+ public RecordingController(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Show dialog of screen recording options to user.
+ */
+ public void launchRecordPrompt(ScreenRecordTile tileToUpdate) {
+ final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
+ SYSUI_SCREENRECORD_LAUNCHER);
+ final Intent intent = new Intent();
+ intent.setComponent(launcherComponent);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra("com.android.systemui.screenrecord.EXTRA_SETTINGS_ONLY", true);
+ mContext.startActivity(intent);
+
+ mTileToUpdate = tileToUpdate;
+ }
+
+ /**
+ * Start counting down in preparation to start a recording
+ * @param ms Time in ms to count down
+ * @param startIntent Intent to start a recording
+ * @param stopIntent Intent to stop a recording
+ */
+ public void startCountdown(long ms, PendingIntent startIntent, PendingIntent stopIntent) {
+ mIsStarting = true;
+ mStopIntent = stopIntent;
+
+ mCountDownTimer = new CountDownTimer(ms, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ refreshTile(millisUntilFinished);
+ }
+
+ @Override
+ public void onFinish() {
+ mIsStarting = false;
+ mIsRecording = true;
+ refreshTile();
+ try {
+ startIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
+ }
+ }
+ };
+
+ mCountDownTimer.start();
+ }
+
+ private void refreshTile() {
+ refreshTile(0);
+ }
+
+ private void refreshTile(long millisUntilFinished) {
+ if (mTileToUpdate != null) {
+ mTileToUpdate.refreshState(millisUntilFinished);
+ } else {
+ Log.e(TAG, "No tile to refresh");
+ }
+ }
+
+ /**
+ * Cancel a countdown in progress. This will not stop the recording if it already started.
+ */
+ public void cancelCountdown() {
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ } else {
+ Log.e(TAG, "Timer was null");
+ }
+ mIsStarting = false;
+ refreshTile();
+ }
+
+ /**
+ * Check if the recording is currently counting down to begin
+ * @return
+ */
+ public boolean isStarting() {
+ return mIsStarting;
+ }
+
+ /**
+ * Check if the recording is ongoing
+ * @return
+ */
+ public boolean isRecording() {
+ return mIsRecording;
+ }
+
+ /**
+ * Stop the recording
+ */
+ public void stopRecording() {
+ updateState(false);
+ try {
+ mStopIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Error stopping: " + e.getMessage());
+ }
+ refreshTile();
+ }
+
+ /**
+ * Update the current status
+ * @param isRecording
+ */
+ public void updateState(boolean isRecording) {
+ mIsRecording = isRecording;
+ refreshTile();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 77c3ad9..1b32168 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -53,10 +53,14 @@
import java.text.SimpleDateFormat;
import java.util.Date;
+import javax.inject.Inject;
+
/**
* A service which records the device screen and optionally microphone input.
*/
public class RecordingService extends Service {
+ public static final int REQUEST_CODE = 2;
+
private static final int NOTIFICATION_ID = 1;
private static final String TAG = "RecordingService";
private static final String CHANNEL_ID = "screen_record";
@@ -65,7 +69,6 @@
private static final String EXTRA_PATH = "extra_path";
private static final String EXTRA_USE_AUDIO = "extra_useAudio";
private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
- private static final int REQUEST_CODE = 2;
private static final String ACTION_START = "com.android.systemui.screenrecord.START";
private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
@@ -81,6 +84,7 @@
private static final int AUDIO_BIT_RATE = 16;
private static final int AUDIO_SAMPLE_RATE = 44100;
+ private final RecordingController mController;
private MediaProjectionManager mMediaProjectionManager;
private MediaProjection mMediaProjection;
private Surface mInputSurface;
@@ -92,6 +96,11 @@
private boolean mShowTaps;
private File mTempFile;
+ @Inject
+ public RecordingService(RecordingController controller) {
+ mController = controller;
+ }
+
/**
* Get an intent to start the recording service.
*
@@ -272,6 +281,7 @@
null);
mMediaRecorder.start();
+ mController.updateState(true);
} catch (IOException e) {
Log.e(TAG, "Error starting screen recording: " + e.getMessage());
e.printStackTrace();
@@ -285,7 +295,7 @@
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
getString(R.string.screenrecord_name),
- NotificationManager.IMPORTANCE_HIGH);
+ NotificationManager.IMPORTANCE_LOW);
channel.setDescription(getString(R.string.screenrecord_channel_description));
channel.enableVibration(true);
NotificationManager notificationManager =
@@ -399,6 +409,7 @@
mInputSurface.release();
mVirtualDisplay.release();
stopSelf();
+ mController.updateState(false);
}
private void saveRecording(NotificationManager notificationManager) {
@@ -439,7 +450,12 @@
Settings.System.SHOW_TOUCHES, value);
}
- private static Intent getStopIntent(Context context) {
+ /**
+ * Get an intent to stop the recording service.
+ * @param context Context from the requesting activity
+ * @return
+ */
+ public static Intent getStopIntent(Context context) {
return new Intent(context, RecordingService.class).setAction(ACTION_STOP);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index 27e9fba..8324986 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -18,55 +18,41 @@
import android.Manifest;
import android.app.Activity;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
-import android.util.Log;
-import android.widget.Button;
-import android.widget.CheckBox;
import android.widget.Toast;
import com.android.systemui.R;
+import javax.inject.Inject;
+
/**
* Activity to select screen recording options
*/
public class ScreenRecordDialog extends Activity {
- private static final String TAG = "ScreenRecord";
private static final int REQUEST_CODE_VIDEO_ONLY = 200;
private static final int REQUEST_CODE_VIDEO_TAPS = 201;
private static final int REQUEST_CODE_PERMISSIONS = 299;
private static final int REQUEST_CODE_VIDEO_AUDIO = 300;
private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301;
private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399;
- private boolean mUseAudio;
- private boolean mShowTaps;
+ private static final long DELAY_MS = 3000;
+
+ private final RecordingController mController;
+
+ @Inject
+ public ScreenRecordDialog(RecordingController controller) {
+ mController = controller;
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.screen_record_dialog);
-
- final CheckBox micCheckBox = findViewById(R.id.checkbox_mic);
- final CheckBox tapsCheckBox = findViewById(R.id.checkbox_taps);
-
- final Button recordButton = findViewById(R.id.record_button);
- recordButton.setOnClickListener(v -> {
- mUseAudio = micCheckBox.isChecked();
- mShowTaps = tapsCheckBox.isChecked();
- Log.d(TAG, "Record button clicked: audio " + mUseAudio + ", taps " + mShowTaps);
-
- if (mUseAudio && checkSelfPermission(Manifest.permission.RECORD_AUDIO)
- != PackageManager.PERMISSION_GRANTED) {
- Log.d(TAG, "Requesting permission for audio");
- requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
- REQUEST_CODE_PERMISSIONS_AUDIO);
- } else {
- requestScreenCapture();
- }
- });
+ requestScreenCapture();
}
private void requestScreenCapture() {
@@ -74,18 +60,23 @@
Context.MEDIA_PROJECTION_SERVICE);
Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
- if (mUseAudio) {
+ // TODO get saved settings
+ boolean useAudio = false;
+ boolean showTaps = false;
+ if (useAudio) {
startActivityForResult(permissionIntent,
- mShowTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
+ showTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
} else {
startActivityForResult(permissionIntent,
- mShowTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
+ showTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- mShowTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+ boolean showTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+ || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
+ boolean useAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
|| requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
switch (requestCode) {
case REQUEST_CODE_VIDEO_TAPS:
@@ -93,11 +84,17 @@
case REQUEST_CODE_VIDEO_ONLY:
case REQUEST_CODE_VIDEO_AUDIO:
if (resultCode == RESULT_OK) {
- mUseAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
- || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
- startForegroundService(
- RecordingService.getStartIntent(this, resultCode, data, mUseAudio,
- mShowTaps));
+ PendingIntent startIntent = PendingIntent.getForegroundService(
+ this, RecordingService.REQUEST_CODE, RecordingService.getStartIntent(
+ ScreenRecordDialog.this, resultCode, data, useAudio,
+ showTaps),
+ PendingIntent.FLAG_UPDATE_CURRENT
+ );
+ PendingIntent stopIntent = PendingIntent.getService(
+ this, RecordingService.REQUEST_CODE,
+ RecordingService.getStopIntent(this),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mController.startCountdown(DELAY_MS, startIntent, stopIntent);
} else {
Toast.makeText(this,
getResources().getString(R.string.screenrecord_permission_error),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index 73bfe25..eaa9d78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -16,42 +16,135 @@
package com.android.systemui.statusbar.notification.collection;
+import java.util.Arrays;
import java.util.List;
-
/**
* Utility class for dumping the results of a {@link ShadeListBuilder} to a debug string.
*/
public class ListDumper {
- /** See class description */
- public static String dumpList(List<ListEntry> entries) {
+ /**
+ * Creates a debug string for a list of grouped notifications that will be printed
+ * in the order given in a tiered/tree structure.
+ * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+ * entry to be in its current state (ie: filter, lifeExtender)
+ */
+ public static String dumpTree(
+ List<ListEntry> entries,
+ boolean includeRecordKeeping,
+ String indent) {
StringBuilder sb = new StringBuilder();
- for (int i = 0; i < entries.size(); i++) {
- ListEntry entry = entries.get(i);
- dumpEntry(entry, Integer.toString(i), "", sb);
+ final String childEntryIndent = indent + INDENT;
+ for (int topEntryIndex = 0; topEntryIndex < entries.size(); topEntryIndex++) {
+ ListEntry entry = entries.get(topEntryIndex);
+ dumpEntry(entry,
+ Integer.toString(topEntryIndex),
+ indent,
+ sb,
+ true,
+ includeRecordKeeping);
if (entry instanceof GroupEntry) {
GroupEntry ge = (GroupEntry) entry;
- for (int j = 0; j < ge.getChildren().size(); j++) {
- dumpEntry(
- ge.getChildren().get(j),
- Integer.toString(j),
- INDENT,
- sb);
+ List<NotificationEntry> children = ge.getChildren();
+ for (int childIndex = 0; childIndex < children.size(); childIndex++) {
+ dumpEntry(children.get(childIndex),
+ Integer.toString(topEntryIndex) + "." + Integer.toString(childIndex),
+ childEntryIndent,
+ sb,
+ true,
+ includeRecordKeeping);
}
}
}
return sb.toString();
}
+ /**
+ * Creates a debug string for a flat list of notifications
+ * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+ * entry to be in its current state (ie: filter, lifeExtender)
+ */
+ public static String dumpList(
+ List<NotificationEntry> entries,
+ boolean includeRecordKeeping,
+ String indent) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < entries.size(); j++) {
+ dumpEntry(
+ entries.get(j),
+ Integer.toString(j),
+ indent,
+ sb,
+ false,
+ includeRecordKeeping);
+ }
+ return sb.toString();
+ }
+
private static void dumpEntry(
- ListEntry entry, String index, String indent, StringBuilder sb) {
+ ListEntry entry,
+ String index,
+ String indent,
+ StringBuilder sb,
+ boolean includeParent,
+ boolean includeRecordKeeping) {
sb.append(indent)
.append("[").append(index).append("] ")
- .append(entry.getKey())
- .append(" (parent=")
- .append(entry.getParent() != null ? entry.getParent().getKey() : null)
- .append(")\n");
+ .append(entry.getKey());
+
+ if (includeParent) {
+ sb.append(" (parent=")
+ .append(entry.getParent() != null ? entry.getParent().getKey() : null)
+ .append(")");
+ }
+
+ if (entry.mNotifSection != null) {
+ sb.append(" sectionIndex=")
+ .append(entry.getSection())
+ .append(" sectionName=")
+ .append(entry.mNotifSection.getName());
+ }
+
+ if (includeRecordKeeping) {
+ NotificationEntry notifEntry = entry.getRepresentativeEntry();
+ StringBuilder rksb = new StringBuilder();
+
+ if (!notifEntry.mLifetimeExtenders.isEmpty()) {
+ String[] lifetimeExtenderNames = new String[notifEntry.mLifetimeExtenders.size()];
+ for (int i = 0; i < lifetimeExtenderNames.length; i++) {
+ lifetimeExtenderNames[i] = notifEntry.mLifetimeExtenders.get(i).getName();
+ }
+ rksb.append("lifetimeExtenders=")
+ .append(Arrays.toString(lifetimeExtenderNames))
+ .append(" ");
+ }
+
+ if (notifEntry.mExcludingFilter != null) {
+ rksb.append("filter=")
+ .append(notifEntry.mExcludingFilter)
+ .append(" ");
+ }
+
+ if (notifEntry.mNotifPromoter != null) {
+ rksb.append("promoter=")
+ .append(notifEntry.mNotifPromoter)
+ .append(" ");
+ }
+
+ if (notifEntry.hasInflationError()) {
+ rksb.append("hasInflationError ");
+ }
+
+ String rkString = rksb.toString();
+ if (!rkString.isEmpty()) {
+ sb.append("\n\t")
+ .append(indent)
+ .append(rkString);
+ }
+ }
+
+ sb.append("\n");
}
private static final String INDENT = " ";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index dc68c4b..56ad0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,6 +18,8 @@
import android.annotation.Nullable;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+
/**
* Abstract superclass for top-level entries, i.e. things that can appear in the final notification
* list shown to users. In practice, this means either GroupEntries or NotificationEntries.
@@ -27,7 +29,9 @@
@Nullable private GroupEntry mParent;
@Nullable private GroupEntry mPreviousParent;
- private int mSection;
+ @Nullable NotifSection mNotifSection;
+
+ private int mSection = -1;
int mFirstAddedIteration = -1;
ListEntry(String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 4b15b7f..c488c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -47,6 +47,8 @@
import android.util.Log;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
@@ -56,6 +58,8 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.util.Assert;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -92,7 +96,7 @@
*/
@MainThread
@Singleton
-public class NotifCollection {
+public class NotifCollection implements Dumpable {
private final IStatusBarService mStatusBarService;
private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
@@ -107,9 +111,10 @@
private boolean mAmDispatchingToOtherCode;
@Inject
- public NotifCollection(IStatusBarService statusBarService) {
+ public NotifCollection(IStatusBarService statusBarService, DumpController dumpController) {
Assert.isMainThread();
mStatusBarService = statusBarService;
+ dumpController.registerDumpable(TAG, this);
}
/** Initializes the NotifCollection and registers it to receive notification events. */
@@ -442,4 +447,19 @@
public @interface CancellationReason {}
public static final int REASON_UNKNOWN = 0;
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
+
+ pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
+ if (entries.size() == 0) {
+ pw.println("\t\t None");
+ }
+ pw.println(
+ ListDumper.dumpList(
+ entries,
+ true,
+ "\t\t"));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 7124517..0377f57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -22,7 +22,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -48,7 +48,7 @@
* GroupEntry. These groups are then transformed in order to remove children or completely split
* them apart. To participate, see {@link #addPromoter}.
* - Sorted: All top-level notifications are sorted. To participate, see
- * {@link #setSectionsProvider} and {@link #setComparators}
+ * {@link #setSections} and {@link #setComparators}
*
* The exact order of all hooks is as follows:
* 0. Collection listeners are fired ({@link #addCollectionListener}).
@@ -58,7 +58,7 @@
* 3. OnBeforeTransformGroupListeners are fired ({@link #addOnBeforeTransformGroupsListener})
* 4. NotifPromoters are called on each notification with a parent ({@link #addPromoter})
* 5. OnBeforeSortListeners are fired ({@link #addOnBeforeSortListener})
- * 6. SectionsProvider is called on each top-level entry in the list ({@link #setSectionsProvider})
+ * 6. Top-level entries are assigned sections by NotifSections ({@link #setSections})
* 7. Top-level entries within the same section are sorted by NotifComparators
* ({@link #setComparators})
* 8. Pre-render filters are fired on each notification ({@link #addPreRenderFilter})
@@ -142,14 +142,13 @@
}
/**
- * Assigns sections to each top-level entry, where a section is simply an integer. Sections are
- * the primary metric by which top-level entries are sorted; NotifComparators are only consulted
- * when two entries are in the same section. The pipeline doesn't assign any particular meaning
- * to section IDs -- from it's perspective they're just numbers and it sorts them by a simple
- * numerical comparison.
+ * Sections that are used to sort top-level entries. If two entries have the same section,
+ * NotifComparators are consulted. Sections from this list are called in order for each
+ * notification passed through the pipeline. The first NotifSection to return true for
+ * {@link NotifSection#isInSection(ListEntry)} sets the entry as part of its Section.
*/
- public void setSectionsProvider(SectionsProvider provider) {
- mShadeListBuilder.setSectionsProvider(provider);
+ public void setSections(List<NotifSection> sections) {
+ mShadeListBuilder.setSections(sections);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 3bbd722..820c042 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -176,12 +176,14 @@
}
entry.ranking = newRanking
- val oldSbn = entry.sbn.cloneLight()
val newOverrideGroupKey = newRanking.overrideGroupKey
- if (!Objects.equals(oldSbn.overrideGroupKey, newOverrideGroupKey)) {
+ if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) {
+ val oldGroupKey = entry.sbn.groupKey
+ val oldIsGroup = entry.sbn.isGroup
+ val oldIsGroupSummary = entry.sbn.notification.isGroupSummary
entry.sbn.overrideGroupKey = newOverrideGroupKey
- // TODO: notify group manager here?
- groupManager.onEntryUpdated(entry, oldSbn)
+ groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup,
+ oldIsGroupSummary)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 76c524b..97f8ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.collection;
import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
@@ -31,7 +30,10 @@
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.util.ArrayMap;
+import android.util.Pair;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
@@ -39,13 +41,15 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.SystemClock;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -63,7 +67,7 @@
*/
@MainThread
@Singleton
-public class ShadeListBuilder {
+public class ShadeListBuilder implements Dumpable {
private final SystemClock mSystemClock;
private final NotifLog mNotifLog;
@@ -79,7 +83,7 @@
private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>();
private final List<NotifComparator> mNotifComparators = new ArrayList<>();
- private SectionsProvider mSectionsProvider = new DefaultSectionsProvider();
+ private final List<NotifSection> mNotifSections = new ArrayList<>();
private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
new ArrayList<>();
@@ -92,10 +96,14 @@
private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
@Inject
- public ShadeListBuilder(SystemClock systemClock, NotifLog notifLog) {
+ public ShadeListBuilder(
+ SystemClock systemClock,
+ NotifLog notifLog,
+ DumpController dumpController) {
Assert.isMainThread();
mSystemClock = systemClock;
mNotifLog = notifLog;
+ dumpController.registerDumpable(TAG, this);
}
/**
@@ -163,12 +171,15 @@
promoter.setInvalidationListener(this::onPromoterInvalidated);
}
- void setSectionsProvider(SectionsProvider provider) {
+ void setSections(List<NotifSection> sections) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
- mSectionsProvider = provider;
- provider.setInvalidationListener(this::onSectionsProviderInvalidated);
+ mNotifSections.clear();
+ for (NotifSection section : sections) {
+ mNotifSections.add(section);
+ section.setInvalidationListener(this::onNotifSectionInvalidated);
+ }
}
void setComparators(List<NotifComparator> comparators) {
@@ -223,12 +234,12 @@
rebuildListIfBefore(STATE_TRANSFORMING);
}
- private void onSectionsProviderInvalidated(SectionsProvider provider) {
+ private void onNotifSectionInvalidated(NotifSection section) {
Assert.isMainThread();
- mNotifLog.log(NotifEvent.SECTIONS_PROVIDER_INVALIDATED, String.format(
- "Sections provider \"%s\" invalidated; pipeline state is %d",
- provider.getName(),
+ mNotifLog.log(NotifEvent.SECTION_INVALIDATED, String.format(
+ "Section \"%s\" invalidated; pipeline state is %d",
+ section.getName(),
mPipelineState.getState()));
rebuildListIfBefore(STATE_SORTING);
@@ -311,7 +322,7 @@
sortList();
// Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
- // Now filters can see grouping information to determine whether to filter or not
+ // Now filters can see grouping information to determine whether to filter or not.
mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
applyNewNotifList();
@@ -324,7 +335,7 @@
// Step 6: Dispatch the new list, first to any listeners and then to the view layer
mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
- + dumpList(mNotifList));
+ + ListDumper.dumpTree(mNotifList, false, "\t\t"));
dispatchOnBeforeRenderList(mReadOnlyNotifList);
if (mOnRenderListListener != null) {
mOnRenderListListener.onRenderList(mReadOnlyNotifList);
@@ -573,6 +584,8 @@
* filtered out during any of the filtering steps.
*/
private void annulAddition(ListEntry entry) {
+ entry.setSection(-1);
+ entry.mNotifSection = null;
entry.setParent(null);
if (entry.mFirstAddedIteration == mIterationCount) {
entry.mFirstAddedIteration = -1;
@@ -582,11 +595,12 @@
private void sortList() {
// Assign sections to top-level elements and sort their children
for (ListEntry entry : mNotifList) {
- entry.setSection(mSectionsProvider.getSection(entry));
+ Pair<NotifSection, Integer> sectionWithIndex = applySections(entry);
if (entry instanceof GroupEntry) {
GroupEntry parent = (GroupEntry) entry;
for (NotificationEntry child : parent.getChildren()) {
- child.setSection(0);
+ child.mNotifSection = sectionWithIndex.first;
+ child.setSection(sectionWithIndex.second);
}
parent.sortChildren(sChildComparator);
}
@@ -747,6 +761,45 @@
return null;
}
+ private Pair<NotifSection, Integer> applySections(ListEntry entry) {
+ final Pair<NotifSection, Integer> sectionWithIndex = findSection(entry);
+ final NotifSection section = sectionWithIndex.first;
+ final Integer sectionIndex = sectionWithIndex.second;
+
+ if (section != entry.mNotifSection) {
+ if (entry.mNotifSection == null) {
+ mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
+ "%s: sectioned by '%s' [index=%d].",
+ entry.getKey(),
+ section.getName(),
+ sectionIndex));
+ } else {
+ mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
+ "%s: section changed: '%s' [index=%d] -> '%s [index=%d]'.",
+ entry.getKey(),
+ entry.mNotifSection,
+ entry.getSection(),
+ section,
+ sectionIndex));
+ }
+
+ entry.mNotifSection = section;
+ entry.setSection(sectionIndex);
+ }
+
+ return sectionWithIndex;
+ }
+
+ private Pair<NotifSection, Integer> findSection(ListEntry entry) {
+ for (int i = 0; i < mNotifSections.size(); i++) {
+ NotifSection sectioner = mNotifSections.get(i);
+ if (sectioner.isInSection(entry)) {
+ return new Pair<>(sectioner, i);
+ }
+ }
+ return new Pair<>(sDefaultSection, mNotifSections.size());
+ }
+
private void rebuildListIfBefore(@PipelineState.StateName int state) {
mPipelineState.requireIsBefore(state);
if (mPipelineState.is(STATE_IDLE)) {
@@ -772,6 +825,19 @@
}
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("\t" + TAG + " shade notifications:");
+ if (getShadeList().size() == 0) {
+ pw.println("\t\t None");
+ }
+
+ pw.println(ListDumper.dumpTree(
+ getShadeList(),
+ true,
+ "\t\t"));
+ }
+
/** See {@link #setOnRenderListListener(OnRenderListListener)} */
public interface OnRenderListListener {
/**
@@ -783,16 +849,13 @@
void onRenderList(List<ListEntry> entries);
}
- private static class DefaultSectionsProvider extends SectionsProvider {
- DefaultSectionsProvider() {
- super("DefaultSectionsProvider");
- }
-
- @Override
- public int getSection(ListEntry entry) {
- return 0;
- }
- }
+ private static final NotifSection sDefaultSection =
+ new NotifSection("DefaultSection") {
+ @Override
+ public boolean isInSection(ListEntry entry) {
+ return true;
+ }
+ };
private static final String TAG = "NotifListBuilderImpl";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
index c1a11b2..d8b2e40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
/**
@@ -28,4 +29,8 @@
* Coordinators should register their listeners and {@link Pluggable}s to the pipeline.
*/
void attach(NotifPipeline pipeline);
+
+ default NotifSection getSection() {
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 562a618..8d0dd5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -16,9 +16,11 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -39,18 +41,21 @@
public class NotifCoordinators implements Dumpable {
private static final String TAG = "NotifCoordinators";
private final List<Coordinator> mCoordinators = new ArrayList<>();
-
+ private final List<NotifSection> mOrderedSections = new ArrayList<>();
/**
* Creates all the coordinators.
*/
@Inject
public NotifCoordinators(
+ DumpController dumpController,
FeatureFlags featureFlags,
KeyguardCoordinator keyguardCoordinator,
RankingCoordinator rankingCoordinator,
ForegroundCoordinator foregroundCoordinator,
DeviceProvisionedCoordinator deviceProvisionedCoordinator,
PreparationCoordinator preparationCoordinator) {
+ dumpController.registerDumpable(TAG, this);
+
mCoordinators.add(keyguardCoordinator);
mCoordinators.add(rankingCoordinator);
mCoordinators.add(foregroundCoordinator);
@@ -59,6 +64,13 @@
mCoordinators.add(preparationCoordinator);
}
// TODO: add new Coordinators here! (b/145134683, b/112656837)
+
+ // TODO: add the sections in a particular ORDER (HeadsUp < People < Alerting)
+ for (Coordinator c : mCoordinators) {
+ if (c.getSection() != null) {
+ mOrderedSections.add(c.getSection());
+ }
+ }
}
/**
@@ -69,6 +81,8 @@
for (Coordinator c : mCoordinators) {
c.attach(pipeline);
}
+
+ pipeline.setSections(mOrderedSections);
}
@Override
@@ -78,5 +92,9 @@
for (Coordinator c : mCoordinators) {
pw.println("\t" + c.getClass());
}
+
+ for (NotifSection s : mOrderedSections) {
+ pw.println("\t" + s.getName());
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 1ab20a9..5cd3e94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -41,6 +41,9 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.people.NotificationPersonExtractorPluginBoundary;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifierImpl;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
@@ -49,6 +52,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.Objects;
@@ -66,7 +70,6 @@
private final NotificationGroupManager mGroupManager;
private final NotificationGutsManager mGutsManager;
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
-
private final Context mContext;
private final NotificationRowContentBinder mRowContentBinder;
private final NotificationMessagingUtil mMessagingUtil;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
new file mode 100644
index 0000000..fe5ba3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+
+/**
+ * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}.
+ */
+public abstract class NotifSection extends Pluggable<NotifSection> {
+ protected NotifSection(String name) {
+ super(name);
+ }
+
+ /**
+ * If returns true, this notification is considered within this section.
+ * Sectioning is performed on each top level notification each time the pipeline is run.
+ * However, this doesn't necessarily mean that your section will get called on each top-level
+ * notification. The first section to return true determines the section of the notification.
+ */
+ public abstract boolean isInSection(ListEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java
deleted file mode 100644
index 11ea850..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
-
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-
-/**
- * Interface for sorting notifications into "sections", such as a heads-upping section, people
- * section, alerting section, silent section, etc.
- */
-public abstract class SectionsProvider extends Pluggable<SectionsProvider> {
-
- protected SectionsProvider(String name) {
- super(name);
- }
-
- /**
- * Returns the section that this entry belongs to. A section can be any non-negative integer.
- * When entries are sorted, they are first sorted by section and then by any remainining
- * comparators.
- */
- public abstract int getSection(ListEntry entry);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index 02acc81..2374cde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -94,7 +94,7 @@
LIST_BUILD_COMPLETE,
PRE_GROUP_FILTER_INVALIDATED,
PROMOTER_INVALIDATED,
- SECTIONS_PROVIDER_INVALIDATED,
+ SECTION_INVALIDATED,
COMPARATOR_INVALIDATED,
PARENT_CHANGED,
FILTER_CHANGED,
@@ -132,12 +132,13 @@
"ListBuildComplete",
"FilterInvalidated",
"PromoterInvalidated",
- "SectionsProviderInvalidated",
+ "SectionInvalidated",
"ComparatorInvalidated",
"ParentChanged",
"FilterChanged",
"PromoterChanged",
"FinalFilterInvalidated",
+ "SectionerChanged",
// NEM event labels:
"NotifAdded",
@@ -170,13 +171,14 @@
public static final int LIST_BUILD_COMPLETE = 4;
public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
public static final int PROMOTER_INVALIDATED = 6;
- public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
+ public static final int SECTION_INVALIDATED = 7;
public static final int COMPARATOR_INVALIDATED = 8;
public static final int PARENT_CHANGED = 9;
public static final int FILTER_CHANGED = 10;
public static final int PROMOTER_CHANGED = 11;
public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
- private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13;
+ public static final int SECTION_CHANGED = 13;
+ private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14;
/**
* Events related to {@link NotificationEntryManager}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a8a35d0..b71beda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1150,6 +1150,7 @@
mMenuRow = plugin;
if (mMenuRow.shouldUseDefaultMenuItems()) {
ArrayList<MenuItem> items = new ArrayList<>();
+ items.add(NotificationMenuRow.createConversationItem(mContext));
items.add(NotificationMenuRow.createInfoItem(mContext));
items.add(NotificationMenuRow.createSnoozeItem(mContext));
items.add(NotificationMenuRow.createAppOpsItem(mContext));
@@ -1163,7 +1164,7 @@
@Override
public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
boolean existed = mMenuRow.getMenuView() != null;
- mMenuRow = new NotificationMenuRow(mContext); // Back to default
+ mMenuRow = new NotificationMenuRow(mContext);
if (existed) {
createMenu();
}
@@ -1720,6 +1721,8 @@
*/
public void reset() {
mShowingPublicInitialized = false;
+ unDismiss();
+ resetTranslation();
onHeightReset();
requestLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
new file mode 100644
index 0000000..ec420f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
+import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The guts of a conversation notification revealed when performing a long press.
+ */
+public class NotificationConversationInfo extends LinearLayout implements
+ NotificationGuts.GutsContent {
+ private static final String TAG = "ConversationGuts";
+
+
+ private INotificationManager mINotificationManager;
+ private LauncherApps mLauncherApps;
+ ShortcutManager mShortcutManager;
+ private PackageManager mPm;
+ private VisualStabilityManager mVisualStabilityManager;
+
+ private String mPackageName;
+ private String mAppName;
+ private int mAppUid;
+ private String mDelegatePkg;
+ private NotificationChannel mNotificationChannel;
+ private ShortcutInfo mShortcutInfo;
+ private String mConversationId;
+ private NotificationEntry mEntry;
+ private StatusBarNotification mSbn;
+ private boolean mIsDeviceProvisioned;
+
+ private int mStartingChannelImportance;
+ private boolean mStartedAsBubble;
+ private boolean mIsBubbleable;
+ // TODO: remove when launcher api works
+ @VisibleForTesting
+ boolean mShowHomeScreen = false;
+
+ private @UpdateChannelRunnable.Action int mSelectedAction = -1;
+
+ private OnSnoozeClickListener mOnSnoozeClickListener;
+ private OnSettingsClickListener mOnSettingsClickListener;
+ private OnAppSettingsClickListener mAppSettingsClickListener;
+ private NotificationGuts mGutsContainer;
+ private BubbleController mBubbleController;
+
+ @VisibleForTesting
+ boolean mSkipPost = false;
+
+ private OnClickListener mOnBubbleClick = v -> {
+ mSelectedAction = ACTION_BUBBLE;
+ if (mStartedAsBubble) {
+ mBubbleController.onUserDemotedBubbleFromNotification(mEntry);
+ } else {
+ mBubbleController.onUserCreatedBubbleFromNotification(mEntry);
+ }
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnHomeClick = v -> {
+ mSelectedAction = ACTION_HOME;
+ mShortcutManager.requestPinShortcut(mShortcutInfo, null);
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnFavoriteClick = v -> {
+ mSelectedAction = ACTION_FAVORITE;
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnSnoozeClick = v -> {
+ mSelectedAction = ACTION_SNOOZE;
+ mOnSnoozeClickListener.onClick(v, 1);
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnMuteClick = v -> {
+ mSelectedAction = ACTION_MUTE;
+ closeControls(v, true);
+ };
+
+ public NotificationConversationInfo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public interface OnSettingsClickListener {
+ void onClick(View v, NotificationChannel channel, int appUid);
+ }
+
+ public interface OnAppSettingsClickListener {
+ void onClick(View v, Intent intent);
+ }
+
+ public interface OnSnoozeClickListener {
+ void onClick(View v, int hoursToSnooze);
+ }
+
+ public void bindNotification(
+ ShortcutManager shortcutManager,
+ LauncherApps launcherApps,
+ PackageManager pm,
+ INotificationManager iNotificationManager,
+ VisualStabilityManager visualStabilityManager,
+ String pkg,
+ NotificationChannel notificationChannel,
+ NotificationEntry entry,
+ OnSettingsClickListener onSettingsClick,
+ OnAppSettingsClickListener onAppSettingsClick,
+ OnSnoozeClickListener onSnoozeClickListener,
+ boolean isDeviceProvisioned) {
+ mSelectedAction = -1;
+ mINotificationManager = iNotificationManager;
+ mVisualStabilityManager = visualStabilityManager;
+ mBubbleController = Dependency.get(BubbleController.class);
+ mPackageName = pkg;
+ mEntry = entry;
+ mSbn = entry.getSbn();
+ mPm = pm;
+ mAppSettingsClickListener = onAppSettingsClick;
+ mAppName = mPackageName;
+ mOnSettingsClickListener = onSettingsClick;
+ mNotificationChannel = notificationChannel;
+ mStartingChannelImportance = mNotificationChannel.getImportance();
+ mAppUid = mSbn.getUid();
+ mDelegatePkg = mSbn.getOpPkg();
+ mIsDeviceProvisioned = isDeviceProvisioned;
+ mOnSnoozeClickListener = onSnoozeClickListener;
+
+ mShortcutManager = shortcutManager;
+ mLauncherApps = launcherApps;
+ mConversationId = mNotificationChannel.getConversationId();
+ if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+ mConversationId = mSbn.getNotification().getShortcutId();
+ }
+ // TODO: flag this when flag exists
+ if (TextUtils.isEmpty(mConversationId)) {
+ mConversationId = mSbn.getId() + mSbn.getTag() + PLACEHOLDER_CONVERSATION_ID;
+ }
+ // TODO: consider querying this earlier in the notification pipeline and passing it in
+ LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
+ .setPackage(mPackageName)
+ .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
+ .setShortcutIds(Arrays.asList(mConversationId));
+ List<ShortcutInfo> shortcuts = mLauncherApps.getShortcuts(query, mSbn.getUser());
+ if (shortcuts != null && !shortcuts.isEmpty()) {
+ mShortcutInfo = shortcuts.get(0);
+ }
+
+ mIsBubbleable = mEntry.getBubbleMetadata() != null;
+ mStartedAsBubble = mEntry.isBubble();
+
+ createConversationChannelIfNeeded();
+
+ bindHeader();
+ bindActions();
+
+ }
+
+ void createConversationChannelIfNeeded() {
+ // If this channel is not already a customized conversation channel, create
+ // a custom channel
+ if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+ try {
+ // TODO: associate this key with this channel service side so the customization
+ // isn't forgotten on the next update
+ mINotificationManager.createConversationNotificationChannelForPackage(
+ mPackageName, mAppUid, mNotificationChannel, mConversationId);
+ mNotificationChannel = mINotificationManager.getConversationNotificationChannel(
+ mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName,
+ mNotificationChannel.getId(), false, mConversationId);
+
+ // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a
+ // time
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not create conversation channel", e);
+ }
+ }
+ }
+
+ private void bindActions() {
+ // TODO: figure out what should happen for non-configurable channels
+
+ Button bubble = findViewById(R.id.bubble);
+ bubble.setVisibility(mIsBubbleable ? VISIBLE : GONE);
+ bubble.setOnClickListener(mOnBubbleClick);
+ if (mStartedAsBubble) {
+ bubble.setText(R.string.notification_conversation_unbubble);
+ } else {
+ bubble.setText(R.string.notification_conversation_bubble);
+ }
+
+ Button home = findViewById(R.id.home);
+ home.setOnClickListener(mOnHomeClick);
+ home.setVisibility(mShowHomeScreen && mShortcutInfo != null
+ && mShortcutManager.isRequestPinShortcutSupported()
+ ? VISIBLE : GONE);
+
+ Button favorite = findViewById(R.id.fave);
+ favorite.setOnClickListener(mOnFavoriteClick);
+ if (mNotificationChannel.canBypassDnd()) {
+ favorite.setText(R.string.notification_conversation_unfavorite);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_star), null, null, null);
+ } else {
+ favorite.setText(R.string.notification_conversation_favorite);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_star_border), null, null, null);
+ }
+
+ Button snooze = findViewById(R.id.snooze);
+ snooze.setOnClickListener(mOnSnoozeClick);
+
+ Button mute = findViewById(R.id.mute);
+ mute.setOnClickListener(mOnMuteClick);
+ if (mStartingChannelImportance >= IMPORTANCE_DEFAULT
+ || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) {
+ mute.setText(R.string.notification_conversation_mute);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null);
+ } else {
+ mute.setText(R.string.notification_conversation_unmute);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null);
+ }
+
+ }
+
+ private void bindHeader() {
+ bindConversationDetails();
+
+ // Delegate
+ bindDelegate();
+
+ // Set up app settings link (i.e. Customize)
+ View settingsLinkView = findViewById(R.id.app_settings);
+ Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
+ mNotificationChannel,
+ mSbn.getId(), mSbn.getTag());
+ if (settingsIntent != null
+ && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
+ settingsLinkView.setVisibility(VISIBLE);
+ settingsLinkView.setOnClickListener((View view) -> {
+ mAppSettingsClickListener.onClick(view, settingsIntent);
+ });
+ } else {
+ settingsLinkView.setVisibility(View.GONE);
+ }
+
+ // System Settings button.
+ final View settingsButton = findViewById(R.id.info);
+ settingsButton.setOnClickListener(getSettingsOnClickListener());
+ settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
+ }
+
+ private OnClickListener getSettingsOnClickListener() {
+ if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
+ final int appUidF = mAppUid;
+ return ((View view) -> {
+ mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF);
+ });
+ }
+ return null;
+ }
+
+ private void bindConversationDetails() {
+ final TextView channelName = findViewById(R.id.parent_channel_name);
+ channelName.setText(mNotificationChannel.getName());
+
+ bindGroup();
+ bindName();
+ bindPackage();
+ bindIcon();
+
+ }
+
+ private void bindIcon() {
+ ImageView image = findViewById(R.id.conversation_icon);
+ if (mShortcutInfo != null) {
+ image.setImageDrawable(mLauncherApps.getShortcutBadgedIconDrawable(mShortcutInfo,
+ mContext.getResources().getDisplayMetrics().densityDpi));
+ } else {
+ // TODO: flag this behavior
+ if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
+ // TODO: maybe use a generic group icon, or a composite of recent senders
+ image.setImageDrawable(mPm.getDefaultActivityIcon());
+ } else {
+ final List<Notification.MessagingStyle.Message> messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ (Parcelable[]) mSbn.getNotification().extras.get(
+ Notification.EXTRA_MESSAGES));
+
+ final Notification.MessagingStyle.Message latestMessage =
+ Notification.MessagingStyle.findLatestIncomingMessage(messages);
+ Icon personIcon = latestMessage.getSenderPerson().getIcon();
+ if (personIcon != null) {
+ image.setImageIcon(latestMessage.getSenderPerson().getIcon());
+ } else {
+ // TODO: choose something better
+ image.setImageDrawable(mPm.getDefaultActivityIcon());
+ }
+ }
+ }
+ }
+
+ private void bindName() {
+ TextView name = findViewById(R.id.name);
+ if (mShortcutInfo != null) {
+ name.setText(mShortcutInfo.getShortLabel());
+ } else {
+ // TODO: flag this behavior
+ Bundle extras = mSbn.getNotification().extras;
+ String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
+ if (TextUtils.isEmpty(nameString)) {
+ nameString = extras.getString(Notification.EXTRA_TITLE);
+ }
+ name.setText(nameString);
+ }
+ }
+
+ private void bindPackage() {
+ ApplicationInfo info;
+ try {
+ info = mPm.getApplicationInfo(
+ mPackageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ mAppName = String.valueOf(mPm.getApplicationLabel(info));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
+ }
+
+ private void bindDelegate() {
+ TextView delegateView = findViewById(R.id.delegate_name);
+ TextView dividerView = findViewById(R.id.pkg_divider);
+
+ if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
+ // this notification was posted by a delegate!
+ delegateView.setVisibility(View.VISIBLE);
+ dividerView.setVisibility(View.VISIBLE);
+ } else {
+ delegateView.setVisibility(View.GONE);
+ dividerView.setVisibility(View.GONE);
+ }
+ }
+
+ private void bindGroup() {
+ // Set group information if this channel has an associated group.
+ CharSequence groupName = null;
+ if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) {
+ try {
+ final NotificationChannelGroup notificationChannelGroup =
+ mINotificationManager.getNotificationChannelGroupForPackage(
+ mNotificationChannel.getGroup(), mPackageName, mAppUid);
+ if (notificationChannelGroup != null) {
+ groupName = notificationChannelGroup.getName();
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ TextView groupNameView = findViewById(R.id.group_name);
+ View groupDivider = findViewById(R.id.group_divider);
+ if (groupName != null) {
+ groupNameView.setText(groupName);
+ groupNameView.setVisibility(VISIBLE);
+ groupDivider.setVisibility(VISIBLE);
+ } else {
+ groupNameView.setVisibility(GONE);
+ groupDivider.setVisibility(GONE);
+ }
+ }
+
+ @Override
+ public boolean post(Runnable action) {
+ if (mSkipPost) {
+ action.run();
+ return true;
+ } else {
+ return super.post(action);
+ }
+ }
+
+ @Override
+ public void onFinishedClosing() {
+ // TODO: do we need to do anything here?
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (mGutsContainer != null &&
+ event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ if (mGutsContainer.isExposed()) {
+ event.getText().add(mContext.getString(
+ R.string.notification_channel_controls_opened_accessibility, mAppName));
+ } else {
+ event.getText().add(mContext.getString(
+ R.string.notification_channel_controls_closed_accessibility, mAppName));
+ }
+ }
+ }
+
+ private Intent getAppSettingsIntent(PackageManager pm, String packageName,
+ NotificationChannel channel, int id, String tag) {
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
+ .setPackage(packageName);
+ final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
+ intent,
+ PackageManager.MATCH_DEFAULT_ONLY
+ );
+ if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
+ return null;
+ }
+ final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
+ intent.setClassName(activityInfo.packageName, activityInfo.name);
+ if (channel != null) {
+ intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
+ }
+ intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
+ intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
+ return intent;
+ }
+
+ /**
+ * Closes the controls and commits the updated importance values (indirectly).
+ *
+ * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
+ * user does not have the ability to undo the action anymore.
+ */
+ @VisibleForTesting
+ void closeControls(View v, boolean save) {
+ int[] parentLoc = new int[2];
+ int[] targetLoc = new int[2];
+ mGutsContainer.getLocationOnScreen(parentLoc);
+ v.getLocationOnScreen(targetLoc);
+ final int centerX = v.getWidth() / 2;
+ final int centerY = v.getHeight() / 2;
+ final int x = targetLoc[0] - parentLoc[0] + centerX;
+ final int y = targetLoc[1] - parentLoc[1] + centerY;
+ mGutsContainer.closeControls(x, y, save, false /* force */);
+ }
+
+ @Override
+ public void setGutsParent(NotificationGuts guts) {
+ mGutsContainer = guts;
+ }
+
+ @Override
+ public boolean willBeRemoved() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldBeSaved() {
+ return mSelectedAction > -1;
+ }
+
+ @Override
+ public View getContentView() {
+ return this;
+ }
+
+ @Override
+ public boolean handleCloseControls(boolean save, boolean force) {
+ if (save && mSelectedAction > -1) {
+ Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+ bgHandler.post(
+ new UpdateChannelRunnable(mINotificationManager, mPackageName,
+ mAppUid, mSelectedAction, mNotificationChannel));
+ mVisualStabilityManager.temporarilyAllowReordering();
+ }
+ return false;
+ }
+
+ @Override
+ public int getActualHeight() {
+ return getHeight();
+ }
+
+ @VisibleForTesting
+ public boolean isAnimating() {
+ return false;
+ }
+
+ static class UpdateChannelRunnable implements Runnable {
+
+ @Retention(SOURCE)
+ @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE,
+ ACTION_DEMOTE})
+ private @interface Action {}
+ static final int ACTION_BUBBLE = 0;
+ static final int ACTION_HOME = 1;
+ static final int ACTION_FAVORITE = 2;
+ static final int ACTION_SNOOZE = 3;
+ static final int ACTION_MUTE = 4;
+ static final int ACTION_DEMOTE = 5;
+
+ private final INotificationManager mINotificationManager;
+ private final String mAppPkg;
+ private final int mAppUid;
+ private NotificationChannel mChannelToUpdate;
+ private final @Action int mAction;
+
+ public UpdateChannelRunnable(INotificationManager notificationManager,
+ String packageName, int appUid, @Action int action,
+ @NonNull NotificationChannel channelToUpdate) {
+ mINotificationManager = notificationManager;
+ mAppPkg = packageName;
+ mAppUid = appUid;
+ mChannelToUpdate = channelToUpdate;
+ mAction = action;
+ }
+
+ @Override
+ public void run() {
+ try {
+ switch (mAction) {
+ case ACTION_BUBBLE:
+ mChannelToUpdate.setAllowBubbles(!mChannelToUpdate.canBubble());
+ break;
+ case ACTION_FAVORITE:
+ // TODO: extend beyond DND
+ mChannelToUpdate.setBypassDnd(!mChannelToUpdate.canBypassDnd());
+ break;
+ case ACTION_MUTE:
+ if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED
+ || mChannelToUpdate.getImportance() >= IMPORTANCE_DEFAULT) {
+ mChannelToUpdate.setImportance(IMPORTANCE_LOW);
+ } else {
+ mChannelToUpdate.setImportance(Math.max(
+ mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
+ }
+ break;
+ case ACTION_DEMOTE:
+ // TODO: when demotion status field exists on notificationchannel
+ break;
+
+ }
+
+ if (mAction != ACTION_HOME && mAction != ACTION_SNOOZE) {
+ mINotificationManager.updateNotificationChannelForPackage(
+ mAppPkg, mAppUid, mChannelToUpdate);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to update notification channel", e);
+ }
+ }
+ }
+
+ @Retention(SOURCE)
+ @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE})
+ private @interface AlertingBehavior {}
+ private static final int BEHAVIOR_ALERTING = 0;
+ private static final int BEHAVIOR_SILENT = 1;
+ private static final int BEHAVIOR_BUBBLE = 2;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 779a224..6789c81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -23,7 +23,9 @@
import android.app.NotificationChannel;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.ShortcutManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -228,6 +230,9 @@
initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
} else if (gutsView instanceof NotificationInfo) {
initializeNotificationInfo(row, (NotificationInfo) gutsView);
+ } else if (gutsView instanceof NotificationConversationInfo) {
+ initializeConversationNotificationInfo(
+ row, (NotificationConversationInfo) gutsView);
}
return true;
} catch (Exception e) {
@@ -339,6 +344,66 @@
}
/**
+ * Sets up the {@link NotificationConversationInfo} inside the notification row's guts.
+ * @param row view to set up the guts for
+ * @param notificationInfoView view to set up/bind within {@code row}
+ */
+ @VisibleForTesting
+ void initializeConversationNotificationInfo(
+ final ExpandableNotificationRow row,
+ NotificationConversationInfo notificationInfoView) throws Exception {
+ NotificationGuts guts = row.getGuts();
+ StatusBarNotification sbn = row.getEntry().getSbn();
+ String packageName = sbn.getPackageName();
+ // Settings link is only valid for notifications that specify a non-system user
+ NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
+ UserHandle userHandle = sbn.getUser();
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(
+ mContext, userHandle.getIdentifier());
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
+ INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ final NotificationConversationInfo.OnAppSettingsClickListener onAppSettingsClick =
+ (View v, Intent intent) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+ guts.resetFalsingCheck();
+ mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(),
+ row);
+ };
+
+ final NotificationConversationInfo.OnSnoozeClickListener onSnoozeClickListener =
+ (View v, int hours) -> {
+ mListContainer.getSwipeActionHelper().snooze(sbn, hours);
+ };
+
+ if (!userHandle.equals(UserHandle.ALL)
+ || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+ onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ mOnSettingsClickListener.onSettingsClick(sbn.getKey());
+ startAppNotificationSettingsActivity(packageName, appUid, channel, row);
+ };
+ }
+
+ notificationInfoView.bindNotification(
+ shortcutManager,
+ launcherApps,
+ pmUser,
+ iNotificationManager,
+ mVisualStabilityManager,
+ packageName,
+ row.getEntry().getChannel(),
+ row.getEntry(),
+ onSettingsClick,
+ onAppSettingsClick,
+ onSnoozeClickListener,
+ mDeviceProvisionedController.isDeviceProvisioned());
+
+ }
+
+ /**
* Closes guts or notification menus that might be visible and saves any changes.
*
* @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index edfd1b4..212cba6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -83,7 +83,6 @@
private OnMenuEventListener mMenuListener;
private boolean mDismissRtl;
private boolean mIsForeground;
- private final boolean mIsUsingBidirectionalSwipe;
private ValueAnimator mFadeAnimator;
private boolean mAnimating;
@@ -116,19 +115,11 @@
private boolean mIsUserTouching;
public NotificationMenuRow(Context context) {
- //TODO: (b/131242807) not using bidirectional swipe for now
- this(context, false);
- }
-
- // Only needed for testing until we want to turn bidirectional swipe back on
- @VisibleForTesting
- NotificationMenuRow(Context context, boolean isUsingBidirectionalSwipe) {
mContext = context;
mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
mHandler = new Handler(Looper.getMainLooper());
mLeftMenuItems = new ArrayList<>();
mRightMenuItems = new ArrayList<>();
- mIsUsingBidirectionalSwipe = isUsingBidirectionalSwipe;
}
@Override
@@ -269,24 +260,18 @@
mSnoozeItem = createSnoozeItem(mContext);
}
mAppOpsItem = createAppOpsItem(mContext);
- if (mIsUsingBidirectionalSwipe) {
- mInfoItem = createInfoItem(mContext,
- mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT);
+ if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) {
+ mInfoItem = createConversationItem(mContext);
} else {
mInfoItem = createInfoItem(mContext);
}
- if (!mIsUsingBidirectionalSwipe) {
- if (!isForeground && showSnooze) {
- mRightMenuItems.add(mSnoozeItem);
- }
- mRightMenuItems.add(mInfoItem);
- mRightMenuItems.add(mAppOpsItem);
- mLeftMenuItems.addAll(mRightMenuItems);
- } else {
- ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems;
- menuItems.add(mInfoItem);
+ if (!isForeground && showSnooze) {
+ mRightMenuItems.add(mSnoozeItem);
}
+ mRightMenuItems.add(mInfoItem);
+ mRightMenuItems.add(mAppOpsItem);
+ mLeftMenuItems.addAll(mRightMenuItems);
populateMenuViews();
if (resetState) {
@@ -633,12 +618,12 @@
@Override
public boolean shouldShowGutsOnSnapOpen() {
- return mIsUsingBidirectionalSwipe;
+ return false;
}
@Override
public MenuItem menuItemToExposeOnSnap() {
- return mIsUsingBidirectionalSwipe ? mInfoItem : null;
+ return null;
}
@Override
@@ -664,6 +649,16 @@
return snooze;
}
+ static NotificationMenuItem createConversationItem(Context context) {
+ Resources res = context.getResources();
+ String infoDescription = res.getString(R.string.notification_menu_gear_description);
+ NotificationConversationInfo infoContent =
+ (NotificationConversationInfo) LayoutInflater.from(context).inflate(
+ R.layout.notification_conversation_info, null, false);
+ return new NotificationMenuItem(context, infoDescription, infoContent,
+ R.drawable.ic_settings);
+ }
+
static NotificationMenuItem createInfoItem(Context context) {
Resources res = context.getResources();
String infoDescription = res.getString(R.string.notification_menu_gear_description);
@@ -673,17 +668,6 @@
R.drawable.ic_settings);
}
- static NotificationMenuItem createInfoItem(Context context, boolean isCurrentlySilent) {
- Resources res = context.getResources();
- String infoDescription = res.getString(R.string.notification_menu_gear_description);
- NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
- R.layout.notification_info, null, false);
- int iconResId = isCurrentlySilent
- ? R.drawable.ic_notifications_silence
- : R.drawable.ic_notifications_alert;
- return new NotificationMenuItem(context, infoDescription, infoContent, iconResId);
- }
-
static MenuItem createAppOpsItem(Context context) {
AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
R.layout.app_ops_info, null, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 823dd5a..dc2d99c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -6207,6 +6207,11 @@
}
@Override
+ public void onSnooze(StatusBarNotification sbn, int hours) {
+ mStatusBar.setNotificationSnoozed(sbn, hours);
+ }
+
+ @Override
public boolean shouldDismissQuickly() {
return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 4845ea1..6c0655e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -282,6 +282,11 @@
mCallback.onSnooze(sbn, snoozeOption);
}
+ @Override
+ public void snooze(StatusBarNotification sbn, int hours) {
+ mCallback.onSnooze(sbn, hours);
+ }
+
@VisibleForTesting
protected void handleMenuCoveredOrDismissed() {
View exposedMenuView = getExposedMenuView();
@@ -447,6 +452,8 @@
void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
+ void onSnooze(StatusBarNotification sbn, int hours);
+
void onDismiss();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index a6842ba..4f56f56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -157,7 +157,6 @@
private int mNavigationIconHints = 0;
private @TransitionMode int mNavigationBarMode;
private AccessibilityManager mAccessibilityManager;
- private MagnificationContentObserver mMagnificationObserver;
private ContentResolver mContentResolver;
private boolean mAssistantAvailable;
@@ -303,11 +302,6 @@
mWindowManager = getContext().getSystemService(WindowManager.class);
mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
mContentResolver = getContext().getContentResolver();
- mMagnificationObserver = new MagnificationContentObserver(
- getContext().getMainThreadHandler());
- mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
- mMagnificationObserver, UserHandle.USER_ALL);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
@@ -329,7 +323,6 @@
super.onDestroy();
mNavigationModeController.removeListener(this);
mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
- mContentResolver.unregisterContentObserver(mMagnificationObserver);
mContentResolver.unregisterContentObserver(mAssistContentObserver);
}
@@ -969,28 +962,18 @@
* @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled.
*/
public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) {
- int requestingServices = 0;
- try {
- if (Settings.Secure.getIntForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
- UserHandle.USER_CURRENT) == 1) {
- requestingServices++;
- }
- } catch (Settings.SettingNotFoundException e) {
- }
-
boolean feedbackEnabled = false;
// AccessibilityManagerService resolves services for the current user since the local
// AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
final List<AccessibilityServiceInfo> services =
mAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ final List<String> a11yButtonTargets =
+ mAccessibilityManager.getAccessibilityShortcutTargets(
+ AccessibilityManager.ACCESSIBILITY_BUTTON);
+ final int requestingServices = a11yButtonTargets.size();
for (int i = services.size() - 1; i >= 0; --i) {
AccessibilityServiceInfo info = services.get(i);
- if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
- requestingServices++;
- }
-
if (info.feedbackType != 0 && info.feedbackType !=
AccessibilityServiceInfo.FEEDBACK_GENERIC) {
feedbackEnabled = true;
@@ -1114,18 +1097,6 @@
private final AccessibilityServicesStateChangeListener mAccessibilityListener =
this::updateAccessibilityServicesState;
- private class MagnificationContentObserver extends ContentObserver {
-
- public MagnificationContentObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager);
- }
- }
-
private final Consumer<Integer> mRotationWatcher = rotation -> {
if (mNavigationBarView != null
&& mNavigationBarView.needsReorient(rotation)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index e11fc1b..8c947ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -116,7 +116,13 @@
*/
private void onEntryRemovedInternal(NotificationEntry removed,
final StatusBarNotification sbn) {
- String groupKey = getGroupKey(sbn);
+ onEntryRemovedInternal(removed, sbn.getGroupKey(), sbn.isGroup(),
+ sbn.getNotification().isGroupSummary());
+ }
+
+ private void onEntryRemovedInternal(NotificationEntry removed, String notifGroupKey, boolean
+ isGroup, boolean isGroupSummary) {
+ String groupKey = getGroupKey(removed.getKey(), notifGroupKey);
final NotificationGroup group = mGroupMap.get(groupKey);
if (group == null) {
// When an app posts 2 different notifications as summary of the same group, then a
@@ -125,7 +131,7 @@
// the close future. See b/23676310 for reference.
return;
}
- if (isGroupChild(sbn)) {
+ if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) {
group.children.remove(removed.getKey());
} else {
group.summary = null;
@@ -229,7 +235,7 @@
private int getNumberOfIsolatedChildren(String groupKey) {
int count = 0;
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
- if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+ if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
count++;
}
}
@@ -238,31 +244,47 @@
private NotificationEntry getIsolatedChild(String groupKey) {
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
- if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+ if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
return mGroupMap.get(sbn.getKey()).summary;
}
}
return null;
}
- public void onEntryUpdated(NotificationEntry entry,
- StatusBarNotification oldNotification) {
- String oldKey = oldNotification.getGroupKey();
- String newKey = entry.getSbn().getGroupKey();
- boolean groupKeysChanged = !oldKey.equals(newKey);
- boolean wasGroupChild = isGroupChild(oldNotification);
+ /**
+ * Update an entry's group information
+ * @param entry notification entry to update
+ * @param oldNotification previous notification info before this update
+ */
+ public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
+ onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
+ oldNotification.getNotification().isGroupSummary());
+ }
+
+ /**
+ * Updates an entry's group information
+ * @param entry notification entry to update
+ * @param oldGroupKey the notification's previous group key before this update
+ * @param oldIsGroup whether this notification was a group before this update
+ * @param oldIsGroupSummary whether this notification was a group summary before this update
+ */
+ public void onEntryUpdated(NotificationEntry entry, String oldGroupKey, boolean oldIsGroup,
+ boolean oldIsGroupSummary) {
+ String newGroupKey = entry.getSbn().getGroupKey();
+ boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey);
+ boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary);
boolean isGroupChild = isGroupChild(entry.getSbn());
mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild;
- if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
- onEntryRemovedInternal(entry, oldNotification);
+ if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) {
+ onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary);
}
onEntryAdded(entry);
mIsUpdatingUnchangedGroup = false;
- if (isIsolated(entry.getSbn())) {
+ if (isIsolated(entry.getSbn().getKey())) {
mIsolatedEntries.put(entry.getKey(), entry.getSbn());
if (groupKeysChanged) {
- updateSuppression(mGroupMap.get(oldKey));
- updateSuppression(mGroupMap.get(newKey));
+ updateSuppression(mGroupMap.get(oldGroupKey));
+ updateSuppression(mGroupMap.get(newGroupKey));
}
} else if (!wasGroupChild && isGroupChild) {
onEntryBecomingChild(entry);
@@ -418,10 +440,14 @@
* @return the key of the notification
*/
public String getGroupKey(StatusBarNotification sbn) {
- if (isIsolated(sbn)) {
- return sbn.getKey();
+ return getGroupKey(sbn.getKey(), sbn.getGroupKey());
+ }
+
+ private String getGroupKey(String key, String groupKey) {
+ if (isIsolated(key)) {
+ return key;
}
- return sbn.getGroupKey();
+ return groupKey;
}
/** @return group expansion state after toggling. */
@@ -434,8 +460,8 @@
return group.expanded;
}
- private boolean isIsolated(StatusBarNotification sbn) {
- return mIsolatedEntries.containsKey(sbn.getKey());
+ private boolean isIsolated(String sbnKey) {
+ return mIsolatedEntries.containsKey(sbnKey);
}
/**
@@ -445,7 +471,7 @@
* @return true if it is visually a group summary
*/
public boolean isGroupSummary(StatusBarNotification sbn) {
- if (isIsolated(sbn)) {
+ if (isIsolated(sbn.getKey())) {
return true;
}
return sbn.getNotification().isGroupSummary();
@@ -458,10 +484,14 @@
* @return true if it is visually a group child
*/
public boolean isGroupChild(StatusBarNotification sbn) {
- if (isIsolated(sbn)) {
+ return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary());
+ }
+
+ private boolean isGroupChild(String key, boolean isGroup, boolean isGroupSummary) {
+ if (isIsolated(key)) {
return false;
}
- return sbn.isGroup() && !sbn.getNotification().isGroupSummary();
+ return isGroup && !isGroupSummary;
}
@Override
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 ba70cf4..dc9cf77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -4026,6 +4026,11 @@
}
}
+ public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) {
+ mNotificationListener.snoozeNotification(sbn.getKey(),
+ hoursToSnooze * 60 * 60 * 1000);
+ }
+
@Override
public void toggleSplitScreen() {
toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 5916180..f05b585 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -15,6 +15,9 @@
*/
package com.android.systemui.statusbar.policy;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
@@ -704,7 +707,11 @@
}
mServiceState = state;
if (mServiceState != null) {
- updateDataNetType(mServiceState.getDataNetworkType());
+ NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+ DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+ if (regInfo != null) {
+ updateDataNetType(regInfo.getAccessNetworkTechnology());
+ }
}
updateTelephony();
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
new file mode 100644
index 0000000..e4b7a20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.app.Activity
+import android.os.Bundle
+import android.os.PersistableBundle
+import androidx.lifecycle.LifecycleOwner
+import com.android.settingslib.core.lifecycle.Lifecycle
+
+open class LifecycleActivity : Activity(), LifecycleOwner {
+
+ private val lifecycle = Lifecycle(this)
+
+ override fun getLifecycle() = lifecycle
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ lifecycle.onAttach(this)
+ lifecycle.onCreate(savedInstanceState)
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onCreate(
+ savedInstanceState: Bundle?,
+ persistentState: PersistableBundle?
+ ) {
+ lifecycle.onAttach(this)
+ lifecycle.onCreate(savedInstanceState)
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
+ super.onCreate(savedInstanceState, persistentState)
+ }
+
+ override fun onStart() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_START)
+ super.onStart()
+ }
+
+ override fun onResume() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_RESUME)
+ super.onResume()
+ }
+
+ override fun onPause() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_PAUSE)
+ super.onPause()
+ }
+
+ override fun onStop() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_STOP)
+ super.onStop()
+ }
+
+ override fun onDestroy() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_DESTROY)
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index cca76bd..8a1759d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -27,6 +27,9 @@
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
import java.util.WeakHashMap
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
/**
* Extension function for all objects which will return a PhysicsAnimator instance for that object.
@@ -35,6 +38,15 @@
private const val TAG = "PhysicsAnimator"
+private val UNSET = -Float.MAX_VALUE
+
+/**
+ * [FlingAnimation] multiplies the friction set via [FlingAnimation.setFriction] by 4.2f, which is
+ * where this number comes from. We use it in [PhysicsAnimator.flingThenSpring] to calculate the
+ * minimum velocity for a fling to reach a certain value, given the fling's friction.
+ */
+private const val FLING_FRICTION_SCALAR_MULTIPLIER = 4.2f
+
typealias EndAction = () -> Unit
/** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
@@ -236,6 +248,71 @@
}
/**
+ * Flings a property using the given start velocity. If the fling animation reaches the min/max
+ * bounds (from the [flingConfig]) with velocity remaining, it'll overshoot it and spring back.
+ *
+ * If the object is already out of the fling bounds, it will immediately spring back within
+ * bounds.
+ *
+ * This is useful for animating objects that are bounded by constraints such as screen edges,
+ * since otherwise the fling animation would end abruptly upon reaching the min/max bounds.
+ *
+ * @param property The property to animate.
+ * @param startVelocity The velocity, in pixels/second, with which to start the fling. If the
+ * object is already outside the fling bounds, this velocity will be used as the start velocity
+ * of the spring that will spring it back within bounds.
+ * @param flingMustReachMinOrMax If true, the fling animation is guaranteed to reach either its
+ * minimum bound (if [startVelocity] is negative) or maximum bound (if it's positive). The
+ * animator will use startVelocity if it's sufficient, or add more velocity if necessary. This
+ * is useful when fling's deceleration-based physics are preferable to the acceleration-based
+ * forces used by springs - typically, when you're allowing the user to move an object somewhere
+ * on the screen, but it needs to be along an edge.
+ * @param flingConfig The configuration to use for the fling portion of the animation.
+ * @param springConfig The configuration to use for the spring portion of the animation.
+ */
+ @JvmOverloads
+ fun flingThenSpring(
+ property: FloatPropertyCompat<in T>,
+ startVelocity: Float,
+ flingConfig: FlingConfig,
+ springConfig: SpringConfig,
+ flingMustReachMinOrMax: Boolean = false
+ ): PhysicsAnimator<T> {
+ val flingConfigCopy = flingConfig.copy()
+ val springConfigCopy = springConfig.copy()
+ val toAtLeast = if (startVelocity < 0) flingConfig.min else flingConfig.max
+
+ // If the fling needs to reach min/max, calculate the velocity required to do so and use
+ // that if the provided start velocity is not sufficient.
+ if (flingMustReachMinOrMax &&
+ toAtLeast != -Float.MAX_VALUE && toAtLeast != Float.MAX_VALUE) {
+ val distanceToDestination = toAtLeast - property.getValue(target)
+
+ // The minimum velocity required for the fling to end up at the given destination,
+ // taking the provided fling friction value.
+ val velocityToReachDestination = distanceToDestination *
+ (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
+
+ // Try to use the provided start velocity, but use the required velocity to reach the
+ // destination if the provided velocity is insufficient.
+ val sufficientVelocity =
+ if (distanceToDestination < 0)
+ min(velocityToReachDestination, startVelocity)
+ else
+ max(velocityToReachDestination, startVelocity)
+
+ flingConfigCopy.startVelocity = sufficientVelocity
+ springConfigCopy.finalPosition = toAtLeast
+ } else {
+ flingConfigCopy.startVelocity = startVelocity
+ }
+
+ flingConfigs[property] = flingConfigCopy
+ springConfigs[property] = springConfigCopy
+ return this
+ }
+
+ /**
* Adds a listener that will be called whenever any property on the animated object is updated.
* This will be called on every animation frame, with the current value of the animated object
* and the new property values.
@@ -246,7 +323,7 @@
}
/**
- * Adds a listener that will be called whenever a property's animation ends. This is useful if
+ * Adds a listener that will be called when a property stops animating. This is useful if
* you care about a specific property ending, or want to use the end value/end velocity from a
* particular property's animation. If you just want to run an action when all property
* animations have ended, use [withEndActions].
@@ -311,6 +388,114 @@
"your test setup.")
}
+ // Functions that will actually start the animations. These are run after we build and add
+ // the InternalListener, since some animations might update/end immediately and we don't
+ // want to miss those updates.
+ val animationStartActions = ArrayList<() -> Unit>()
+
+ for (animatedProperty in getAnimatedProperties()) {
+ val flingConfig = flingConfigs[animatedProperty]
+ val springConfig = springConfigs[animatedProperty]
+
+ // The property's current value on the object.
+ val currentValue = animatedProperty.getValue(target)
+
+ // Start by checking for a fling configuration. If one is present, we're either flinging
+ // or flinging-then-springing. Either way, we'll want to start the fling first.
+ if (flingConfig != null) {
+ animationStartActions.add {
+ // When the animation is starting, adjust the min/max bounds to include the
+ // current value of the property, if necessary. This is required to allow a
+ // fling to bring an out-of-bounds object back into bounds. For example, if an
+ // object was dragged halfway off the left side of the screen, but then flung to
+ // the right, we don't want the animation to end instantly just because the
+ // object started out of bounds. If the fling is in the direction that would
+ // take it farther out of bounds, it will end instantly as expected.
+ flingConfig.apply {
+ min = min(currentValue, this.min)
+ max = max(currentValue, this.max)
+ }
+
+ // Apply the configuration and start the animation.
+ getFlingAnimation(animatedProperty)
+ .also { flingConfig.applyToAnimation(it) }
+ .start()
+ }
+ }
+
+ // Check for a spring configuration. If one is present, we're either springing, or
+ // flinging-then-springing.
+ if (springConfig != null) {
+
+ // If there is no corresponding fling config, we're only springing.
+ if (flingConfig == null) {
+ // Apply the configuration and start the animation.
+ val springAnim = getSpringAnimation(animatedProperty)
+ springConfig.applyToAnimation(springAnim)
+ animationStartActions.add(springAnim::start)
+ } else {
+ // If there's a corresponding fling config, we're flinging-then-springing. Save
+ // the fling's original bounds so we can spring to them when the fling ends.
+ val flingMin = flingConfig.min
+ val flingMax = flingConfig.max
+
+ // Add an end listener that will start the spring when the fling ends.
+ endListeners.add(0, object : EndListener<T> {
+ override fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ ) {
+ // If this isn't the relevant property, it wasn't a fling, or the fling
+ // was explicitly cancelled, don't spring.
+ if (property != animatedProperty || !wasFling || canceled) {
+ return
+ }
+
+ val endedWithVelocity = abs(finalVelocity) > 0
+
+ // If the object was out of bounds when the fling animation started, it
+ // will immediately end. In that case, we'll spring it back in bounds.
+ val endedOutOfBounds = finalValue !in flingMin..flingMax
+
+ // If the fling ended either out of bounds or with remaining velocity,
+ // it's time to spring.
+ if (endedWithVelocity || endedOutOfBounds) {
+ springConfig.startVelocity = finalVelocity
+
+ // If the spring's final position isn't set, this is a
+ // flingThenSpring where flingMustReachMinOrMax was false. We'll
+ // need to set the spring's final position here.
+ if (springConfig.finalPosition == UNSET) {
+ if (endedWithVelocity) {
+ // If the fling ended with negative velocity, that means it
+ // hit the min bound, so spring to that bound (and vice
+ // versa).
+ springConfig.finalPosition =
+ if (finalVelocity < 0) flingMin else flingMax
+ } else if (endedOutOfBounds) {
+ // If the fling ended out of bounds, spring it to the
+ // nearest bound.
+ springConfig.finalPosition =
+ if (finalValue < flingMin) flingMin else flingMax
+ }
+ }
+
+ // Apply the configuration and start the spring animation.
+ getSpringAnimation(animatedProperty)
+ .also { springConfig.applyToAnimation(it) }
+ .start()
+ }
+ }
+ })
+ }
+ }
+ }
+
// Add an internal listener that will dispatch animation events to the provided listeners.
internalListeners.add(InternalListener(
getAnimatedProperties(),
@@ -318,24 +503,10 @@
ArrayList(endListeners),
ArrayList(endActions)))
- for ((property, config) in flingConfigs) {
- val currentValue = property.getValue(target)
-
- // If the fling is already out of bounds, don't start it.
- if (currentValue <= config.min || currentValue >= config.max) {
- continue
- }
-
- val flingAnim = getFlingAnimation(property)
- config.applyToAnimation(flingAnim)
- flingAnim.start()
- }
-
- for ((property, config) in springConfigs) {
- val springAnim = getSpringAnimation(property)
- config.applyToAnimation(springAnim)
- springAnim.start()
- }
+ // Actually start the DynamicAnimations. This is delayed until after the InternalListener is
+ // constructed and added so that we don't miss the end listener firing for any animations
+ // that immediately end.
+ animationStartActions.forEach { it.invoke() }
clearAnimator()
}
@@ -381,7 +552,10 @@
}
anim.addEndListener { _, canceled, value, velocity ->
internalListeners.removeAll {
- it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+ it.onInternalAnimationEnd(
+ property, canceled, value, velocity, anim is FlingAnimation)
+ }
+ }
return anim
}
@@ -434,7 +608,8 @@
property: FloatPropertyCompat<in T>,
canceled: Boolean,
finalValue: Float,
- finalVelocity: Float
+ finalVelocity: Float,
+ isFling: Boolean
): Boolean {
// If this property animation isn't relevant to this listener, ignore it.
@@ -461,7 +636,15 @@
val allEnded = !arePropertiesAnimating(properties)
endListeners.forEach {
- it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+ it.onAnimationEnd(
+ target, property, isFling, canceled, finalValue, finalVelocity,
+ allEnded)
+
+ // Check that the end listener didn't restart this property's animation.
+ if (isPropertyAnimating(property)) {
+ return false
+ }
+ }
// If all of the animations that this listener cares about have ended, run the end
// actions unless the animation was canceled.
@@ -495,7 +678,8 @@
/** Returns whether the given property is animating. */
fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
- return springAnimations[property]?.isRunning ?: false
+ return springAnimations[property]?.isRunning ?: false ||
+ flingAnimations[property]?.isRunning ?: false
}
/** Returns whether any of the given properties are animating. */
@@ -523,15 +707,15 @@
data class SpringConfig internal constructor(
internal var stiffness: Float,
internal var dampingRatio: Float,
- internal var startVel: Float = 0f,
- internal var finalPosition: Float = -Float.MAX_VALUE
+ internal var startVelocity: Float = 0f,
+ internal var finalPosition: Float = UNSET
) {
constructor() :
this(defaultSpring.stiffness, defaultSpring.dampingRatio)
constructor(stiffness: Float, dampingRatio: Float) :
- this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+ this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
/** Apply these configuration settings to the given SpringAnimation. */
internal fun applyToAnimation(anim: SpringAnimation) {
@@ -542,7 +726,7 @@
finalPosition = this@SpringConfig.finalPosition
}
- if (startVel != 0f) anim.setStartVelocity(startVel)
+ if (startVelocity != 0f) anim.setStartVelocity(startVelocity)
}
}
@@ -555,7 +739,7 @@
internal var friction: Float,
internal var min: Float,
internal var max: Float,
- internal var startVel: Float
+ internal var startVelocity: Float
) {
constructor() : this(defaultFling.friction)
@@ -564,7 +748,7 @@
this(friction, defaultFling.min, defaultFling.max)
constructor(friction: Float, min: Float, max: Float) :
- this(friction, min, max, startVel = 0f)
+ this(friction, min, max, startVelocity = 0f)
/** Apply these configuration settings to the given FlingAnimation. */
internal fun applyToAnimation(anim: FlingAnimation) {
@@ -572,7 +756,7 @@
friction = this@FlingConfig.friction
setMinValue(min)
setMaxValue(max)
- setStartVelocity(startVel)
+ setStartVelocity(startVelocity)
}
}
}
@@ -625,6 +809,10 @@
*
* @param target The animated object itself.
* @param property The property whose animation has just ended.
+ * @param wasFling Whether this property ended after a fling animation (as opposed to a
+ * spring animation). If this property was animated via [flingThenSpring], this will be true
+ * if the fling animation did not reach the min/max bounds, decelerating to a stop
+ * naturally. It will be false if it hit the bounds and was sprung back.
* @param canceled Whether the animation was explicitly canceled before it naturally ended.
* @param finalValue The final value of the animated property.
* @param finalVelocity The final velocity (in pixels per second) of the ended animation.
@@ -662,6 +850,7 @@
fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
index e86970c..965decd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -19,6 +19,7 @@
import android.os.Looper
import android.util.ArrayMap
import androidx.dynamicanimation.animation.FloatPropertyCompat
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.prepareForTest
import java.util.ArrayDeque
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -119,6 +120,7 @@
override fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
@@ -389,8 +391,6 @@
val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
animationThreadHandler.post {
- val animatedProperties = animator.getAnimatedProperties()
-
// Add an update listener that dispatches to any test update listeners added by
// tests.
animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
@@ -398,6 +398,10 @@
target: T,
values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
) {
+ values.forEach { (property, value) ->
+ allUpdates.getOrPut(property, { ArrayList() }).add(value)
+ }
+
for (listener in testUpdateListeners) {
listener.onAnimationUpdateForProperty(target, values)
}
@@ -410,6 +414,7 @@
override fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
@@ -417,7 +422,7 @@
) {
for (listener in testEndListeners) {
listener.onAnimationEnd(
- target, property, canceled, finalValue, finalVelocity,
+ target, property, wasFling, canceled, finalValue, finalVelocity,
allRelevantPropertyAnimsEnded)
}
@@ -432,31 +437,6 @@
}
})
- val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also {
- it.add(object : PhysicsAnimator.UpdateListener<T> {
- override fun onAnimationUpdateForProperty(
- target: T,
- values: ArrayMap<FloatPropertyCompat<in T>,
- PhysicsAnimator.AnimationUpdate>
- ) {
- values.forEach { (property, value) ->
- allUpdates.getOrPut(property, { ArrayList() }).add(value)
- }
- }
- })
- }
-
- /**
- * Add an internal listener at the head of the list that captures update values
- * directly from DynamicAnimation. We use this to build a list of all updates so we
- * can verify that InternalListener dispatches to the real listeners properly.
- */
- animator.internalListeners.add(0, animator.InternalListener(
- animatedProperties,
- updateListeners,
- ArrayList(),
- ArrayList()))
-
animator.startInternal()
unblockLatch.countDown()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
new file mode 100644
index 0000000..7c8c7c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.Binder
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import dagger.Lazy
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsBindingControllerTest : SysuiTestCase() {
+
+ companion object {
+ fun <T> any(): T = Mockito.any<T>()
+ private val TEST_COMPONENT_NAME_1 = ComponentName("TEST_PKG", "TEST_CLS_1")
+ private val TEST_COMPONENT_NAME_2 = ComponentName("TEST_PKG", "TEST_CLS_2")
+ private val TEST_COMPONENT_NAME_3 = ComponentName("TEST_PKG", "TEST_CLS_3")
+ }
+
+ @Mock
+ private lateinit var mockControlsController: ControlsController
+
+ private val executor = FakeExecutor(FakeSystemClock())
+ private lateinit var controller: ControlsBindingController
+ private val providers = TestableControlsBindingControllerImpl.providers
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ controller = TestableControlsBindingControllerImpl(
+ mContext, executor, Lazy { mockControlsController })
+ }
+
+ @After
+ fun tearDown() {
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ providers.clear()
+ }
+
+ @Test
+ fun testBindAndLoad() {
+ val callback: (List<Control>) -> Unit = {}
+ controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
+
+ assertEquals(1, providers.size)
+ val provider = providers.first()
+ verify(provider).maybeBindAndLoad(callback)
+ }
+
+ @Test
+ fun testBindServices() {
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+ executor.runAllReady()
+
+ assertEquals(2, providers.size)
+ assertEquals(setOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2),
+ providers.map { it.componentName }.toSet())
+ providers.forEach {
+ verify(it).bindPermanently()
+ }
+ }
+
+ @Test
+ fun testSubscribe() {
+ val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_3))
+
+ controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+ executor.runAllReady()
+
+ assertEquals(3, providers.size)
+ val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 }
+ val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 }
+ val provider3 = providers.first { it.componentName == TEST_COMPONENT_NAME_3 }
+
+ verify(provider1).maybeBindAndSubscribe(listOf(controlInfo1.controlId))
+ verify(provider2).maybeBindAndSubscribe(listOf(controlInfo2.controlId))
+ verify(provider3, never()).maybeBindAndSubscribe(any())
+ verify(provider3).unbindService() // Not needed services will be unbound
+ }
+
+ @Test
+ fun testUnsubscribe_notRefreshing() {
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+ controller.unsubscribe()
+
+ executor.runAllReady()
+
+ providers.forEach {
+ verify(it, never()).unsubscribe()
+ }
+ }
+
+ @Test
+ fun testUnsubscribe_refreshing() {
+ val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+
+ controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+ controller.unsubscribe()
+
+ executor.runAllReady()
+
+ providers.forEach {
+ verify(it).unsubscribe()
+ }
+ }
+}
+
+class TestableControlsBindingControllerImpl(
+ context: Context,
+ executor: DelayableExecutor,
+ lazyController: Lazy<ControlsController>
+) : ControlsBindingControllerImpl(context, executor, lazyController) {
+
+ companion object {
+ val providers = mutableSetOf<ControlsProviderLifecycleManager>()
+ }
+
+ override fun createProviderManager(component: ComponentName):
+ ControlsProviderLifecycleManager {
+ val provider = mock(ControlsProviderLifecycleManager::class.java)
+ val token = Binder()
+ `when`(provider.componentName).thenReturn(component)
+ `when`(provider.token).thenReturn(token)
+ providers.add(provider)
+ return provider
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..a19c299
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -0,0 +1,360 @@
+/*
+ * 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.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.DumpController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Optional
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsControllerImplTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var uiController: ControlsUiController
+ @Mock
+ private lateinit var bindingController: ControlsBindingController
+ @Mock
+ private lateinit var dumpController: DumpController
+ @Mock
+ private lateinit var pendingIntent: PendingIntent
+ @Mock
+ private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper
+
+ @Captor
+ private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
+ @Captor
+ private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
+
+ private lateinit var delayableExecutor: FakeExecutor
+ private lateinit var controller: ControlsController
+
+ companion object {
+ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+ fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
+ private val TEST_COMPONENT = ComponentName("test.pkg", "test.class")
+ private const val TEST_CONTROL_ID = "control1"
+ private const val TEST_CONTROL_TITLE = "Test"
+ private const val TEST_DEVICE_TYPE = DeviceTypes.TYPE_AC_HEATER
+ private val TEST_CONTROL_INFO = ControlInfo(
+ TEST_COMPONENT, TEST_CONTROL_ID, TEST_CONTROL_TITLE, TEST_DEVICE_TYPE)
+
+ private val TEST_COMPONENT_2 = ComponentName("test.pkg", "test.class.2")
+ private const val TEST_CONTROL_ID_2 = "control2"
+ private const val TEST_CONTROL_TITLE_2 = "Test 2"
+ private const val TEST_DEVICE_TYPE_2 = DeviceTypes.TYPE_CAMERA
+ private val TEST_CONTROL_INFO_2 = ControlInfo(
+ TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2)
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ Settings.Secure.putInt(mContext.contentResolver,
+ ControlsControllerImpl.CONTROLS_AVAILABLE, 1)
+
+ delayableExecutor = FakeExecutor(FakeSystemClock())
+
+ controller = ControlsControllerImpl(
+ mContext,
+ delayableExecutor,
+ uiController,
+ bindingController,
+ Optional.of(persistenceWrapper),
+ dumpController
+ )
+ assertTrue(controller.available)
+ }
+
+ private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder {
+ return Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+ .setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
+ }
+
+ @Test
+ fun testStartWithoutFavorites() {
+ assertTrue(controller.getFavoriteControls().isEmpty())
+ }
+
+ @Test
+ fun testStartWithSavedFavorites() {
+ `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_CONTROL_INFO))
+ val controller_other = ControlsControllerImpl(
+ mContext,
+ delayableExecutor,
+ uiController,
+ bindingController,
+ Optional.of(persistenceWrapper),
+ dumpController
+ )
+ assertEquals(listOf(TEST_CONTROL_INFO), controller_other.getFavoriteControls())
+ }
+
+ @Test
+ fun testAddFavorite() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO in favorites)
+ assertEquals(1, favorites.size)
+ }
+
+ @Test
+ fun testAddMultipleFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO in favorites)
+ assertTrue(TEST_CONTROL_INFO_2 in favorites)
+ assertEquals(2, favorites.size)
+ }
+
+ @Test
+ fun testAddAndRemoveFavorite() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO !in favorites)
+ assertTrue(TEST_CONTROL_INFO_2 in favorites)
+ assertEquals(1, favorites.size)
+ }
+
+ @Test
+ fun testFavoritesSavedOnAdd() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ verify(persistenceWrapper).storeFavorites(listOf(TEST_CONTROL_INFO))
+ }
+
+ @Test
+ fun testFavoritesSavedOnRemove() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ reset(persistenceWrapper)
+
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+ verify(persistenceWrapper).storeFavorites(emptyList())
+ }
+
+ @Test
+ fun testFavoritesSavedOnChange() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {}
+
+ reset(persistenceWrapper)
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ verify(persistenceWrapper).storeFavorites(listOf(newControlInfo))
+ }
+
+ @Test
+ fun testFavoritesNotSavedOnRedundantAdd() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ reset(persistenceWrapper)
+
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+ }
+
+ @Test
+ fun testFavoritesNotSavedOnNotRemove() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+ verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+ }
+
+ @Test
+ fun testOnActionResponse() {
+ controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)
+
+ verify(uiController).onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID,
+ ControlAction.RESPONSE_OK)
+ }
+
+ @Test
+ fun testRefreshStatus() {
+ val list = listOf(Control.StatefulBuilder(TEST_CONTROL_ID, pendingIntent).build())
+ controller.refreshStatus(TEST_COMPONENT, list)
+
+ verify(uiController).onRefreshState(TEST_COMPONENT, list)
+ }
+
+ @Test
+ fun testUnsubscribe() {
+ controller.unsubscribe()
+ verify(bindingController).unsubscribe()
+ }
+
+ @Test
+ fun testSubscribeFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+ controller.subscribeToFavorites()
+
+ verify(bindingController).subscribe(capture(controlInfoListCaptor))
+
+ assertTrue(TEST_CONTROL_INFO in controlInfoListCaptor.value)
+ assertTrue(TEST_CONTROL_INFO_2 in controlInfoListCaptor.value)
+ }
+
+ @Test
+ fun testLoadForComponent_noFavorites() {
+ var loaded = false
+ val control = builderFromInfo(TEST_CONTROL_INFO).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(1, it.size)
+ val controlStatus = it[0]
+ assertEquals(ControlStatus(control, false), controlStatus)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testLoadForComponent_favorites() {
+ var loaded = false
+ val control = builderFromInfo(TEST_CONTROL_INFO).build()
+ val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(2, it.size)
+ val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID }
+ assertEquals(ControlStatus(control, true), controlStatus)
+
+ val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 }
+ assertEquals(ControlStatus(control2, false), controlStatus2)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testLoadForComponent_removed() {
+ var loaded = false
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(1, it.size)
+ val controlStatus = it[0]
+ assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
+ assertTrue(controlStatus.favorite)
+ assertTrue(controlStatus.removed)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(emptyList())
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testFavoriteInformationModifiedOnLoad() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {}
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ val favorites = controller.getFavoriteControls()
+ assertEquals(1, favorites.size)
+ assertEquals(newControlInfo, favorites[0])
+ }
+
+ @Test
+ fun testFavoriteInformationModifiedOnRefresh() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.refreshStatus(TEST_COMPONENT, listOf(control))
+
+ delayableExecutor.runAllReady()
+
+ val favorites = controller.getFavoriteControls()
+ assertEquals(1, favorites.size)
+ assertEquals(newControlInfo, favorites[0])
+ }
+
+ @Test
+ fun testClearFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ assertEquals(1, controller.getFavoriteControls().size)
+
+ controller.clearFavorites()
+ assertTrue(controller.getFavoriteControls().isEmpty())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
new file mode 100644
index 0000000..c145c1f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsFavoritePersistenceWrapperTest : SysuiTestCase() {
+
+ private lateinit var file: File
+
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var wrapper: ControlsFavoritePersistenceWrapper
+
+ @Before
+ fun setUp() {
+ file = File.createTempFile("controls_favorites", ".temp")
+ wrapper = ControlsFavoritePersistenceWrapper(file, executor)
+ }
+
+ @After
+ fun tearDown() {
+ if (file.exists() ?: false) {
+ file.delete()
+ }
+ }
+
+ @Test
+ fun testSaveAndRestore() {
+ val controlInfo1 = ControlInfo(
+ ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_1")!!,
+ "id1", "name_1", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(
+ ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_2")!!,
+ "id2", "name_2", DeviceTypes.TYPE_GENERIC_ON_OFF)
+ val list = listOf(controlInfo1, controlInfo2)
+
+ wrapper.storeFavorites(list)
+
+ executor.runAllReady()
+
+ assertEquals(list, wrapper.readFavorites())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
new file mode 100644
index 0000000..556bb40
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var serviceCallback: IControlsProviderCallback.Stub
+ @Mock
+ private lateinit var service: IControlsProvider.Stub
+
+ private val componentName = ComponentName("test.pkg", "test.cls")
+ private lateinit var manager: ControlsProviderLifecycleManager
+ private lateinit var executor: DelayableExecutor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mContext.addMockService(componentName, service)
+ executor = FakeExecutor(FakeSystemClock())
+ `when`(service.asBinder()).thenCallRealMethod()
+ `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service)
+
+ manager = ControlsProviderLifecycleManager(
+ context,
+ executor,
+ serviceCallback,
+ componentName
+ )
+ }
+
+ @After
+ fun tearDown() {
+ manager.unbindService()
+ }
+
+ @Test
+ fun testBindService() {
+ manager.bindPermanently()
+ assertTrue(mContext.isBound(componentName))
+ }
+
+ @Test
+ fun testUnbindService() {
+ manager.bindPermanently()
+ manager.unbindService()
+ assertFalse(mContext.isBound(componentName))
+ }
+
+ @Test
+ fun testMaybeBindAndLoad() {
+ val callback: (List<Control>) -> Unit = {}
+ manager.maybeBindAndLoad(callback)
+
+ verify(service).load()
+
+ assertTrue(mContext.isBound(componentName))
+ assertEquals(callback, manager.lastLoadCallback)
+ }
+
+ @Test
+ fun testMaybeUnbind_bindingAndCallback() {
+ manager.maybeBindAndLoad {}
+
+ manager.maybeUnbindAndRemoveCallback()
+ assertFalse(mContext.isBound(componentName))
+ assertNull(manager.lastLoadCallback)
+ }
+
+ @Test
+ fun testUnsubscribe() {
+ manager.bindPermanently()
+ manager.unsubscribe()
+
+ verify(service).unsubscribe()
+ }
+
+ @Test
+ fun testMaybeBindAndSubscribe() {
+ val list = listOf("TEST_ID")
+ manager.maybeBindAndSubscribe(list)
+
+ assertTrue(mContext.isBound(componentName))
+ verify(service).subscribe(list)
+ }
+
+ @Test
+ fun testMaybeBindAndAction() {
+ val controlId = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+ manager.maybeBindAndSendAction(controlId, action)
+
+ assertTrue(mContext.isBound(componentName))
+ verify(service).onAction(controlId, action)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
new file mode 100644
index 0000000..d6993c0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.controller
+
+import android.os.RemoteException
+import android.service.controls.IControlsProvider
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderServiceWrapperTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var service: IControlsProvider
+
+ private val exception = RemoteException()
+
+ private lateinit var wrapper: ControlsProviderServiceWrapper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ wrapper = ControlsProviderServiceWrapper(service)
+ }
+
+ @Test
+ fun testLoad_happyPath() {
+ val result = wrapper.load()
+
+ assertTrue(result)
+ verify(service).load()
+ }
+
+ @Test
+ fun testLoad_error() {
+ `when`(service.load()).thenThrow(exception)
+ val result = wrapper.load()
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testSubscribe_happyPath() {
+ val list = listOf("TEST_ID")
+ val result = wrapper.subscribe(list)
+
+ assertTrue(result)
+ verify(service).subscribe(list)
+ }
+
+ @Test
+ fun testSubscribe_error() {
+ `when`(service.subscribe(any())).thenThrow(exception)
+
+ val list = listOf("TEST_ID")
+ val result = wrapper.subscribe(list)
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testUnsubscribe_happyPath() {
+ val result = wrapper.unsubscribe()
+
+ assertTrue(result)
+ verify(service).unsubscribe()
+ }
+
+ @Test
+ fun testUnsubscribe_error() {
+ `when`(service.unsubscribe()).thenThrow(exception)
+ val result = wrapper.unsubscribe()
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testOnAction_happyPath() {
+ val id = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+
+ val result = wrapper.onAction(id, action)
+
+ assertTrue(result)
+ verify(service).onAction(id, action)
+ }
+
+ @Test
+ fun testOnAction_error() {
+ `when`(service.onAction(any(), any())).thenThrow(exception)
+
+ val id = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+
+ val result = wrapper.onAction(id, action)
+
+ assertFalse(result)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
new file mode 100644
index 0000000..f09aab9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -0,0 +1,188 @@
+/*
+ * 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.content.pm.ServiceInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsListingControllerImplTest : SysuiTestCase() {
+
+ companion object {
+ private const val TEST_LABEL = "TEST_LABEL"
+ private const val TEST_PERMISSION = "permission"
+ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+ fun <T> any(): T = Mockito.any<T>()
+ }
+
+ @Mock
+ private lateinit var mockSL: ServiceListing
+ @Mock
+ private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
+ @Mock
+ private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
+ @Mock
+ private lateinit var serviceInfo: ServiceInfo
+ @Mock
+ private lateinit var componentName: ComponentName
+
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var controller: ControlsListingControllerImpl
+
+ private var serviceListingCallbackCaptor =
+ ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(serviceInfo.componentName).thenReturn(componentName)
+
+ controller = ControlsListingControllerImpl(mContext, executor, mockSL)
+ verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
+ }
+
+ @After
+ fun tearDown() {
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ }
+
+ @Test
+ fun testNoServices_notListening() {
+ assertTrue(controller.getCurrentServices().isEmpty())
+ }
+
+ @Test
+ fun testStartListening_onFirstCallback() {
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+
+ verify(mockSL).setListening(true)
+ }
+
+ @Test
+ fun testStartListening_onlyOnce() {
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ executor.runAllReady()
+
+ verify(mockSL).setListening(true)
+ }
+
+ @Test
+ fun testStopListening_callbackRemoved() {
+ controller.addCallback(mockCallback)
+
+ executor.runAllReady()
+
+ controller.removeCallback(mockCallback)
+
+ executor.runAllReady()
+
+ verify(mockSL).setListening(false)
+ }
+
+ @Test
+ fun testStopListening_notWhileRemainingCallbacks() {
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ executor.runAllReady()
+
+ controller.removeCallback(mockCallback)
+
+ executor.runAllReady()
+
+ verify(mockSL, never()).setListening(false)
+ }
+
+ @Test
+ fun testReloadOnFirstCallbackAdded() {
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+
+ verify(mockSL).reload()
+ }
+
+ @Test
+ fun testCallbackCalledWhenAdded() {
+ `when`(mockSL.reload()).then {
+ serviceListingCallbackCaptor.value.onServicesReloaded(emptyList())
+ }
+
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+ verify(mockCallback).onServicesUpdated(any())
+ reset(mockCallback)
+
+ controller.addCallback(mockCallbackOther)
+ executor.runAllReady()
+ verify(mockCallbackOther).onServicesUpdated(any())
+ verify(mockCallback, never()).onServicesUpdated(any())
+ }
+
+ @Test
+ fun testCallbackGetsList() {
+ val list = listOf(serviceInfo)
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ @Suppress("unchecked_cast")
+ val captor: ArgumentCaptor<List<CandidateInfo>> =
+ ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>>
+
+ executor.runAllReady()
+ reset(mockCallback)
+ reset(mockCallbackOther)
+
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+ verify(mockCallback).onServicesUpdated(capture(captor))
+ assertEquals(1, captor.value.size)
+ assertEquals(componentName.flattenToString(), captor.value[0].key)
+
+ verify(mockCallbackOther).onServicesUpdated(capture(captor))
+ assertEquals(1, captor.value.size)
+ assertEquals(componentName.flattenToString(), captor.value[0].key)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 09cc5ba..fe8d769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -29,6 +29,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -48,6 +49,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.DumpController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
@@ -103,7 +105,7 @@
MockitoAnnotations.initMocks(this);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mCollection = new NotifCollection(mStatusBarService);
+ mCollection = new NotifCollection(mStatusBarService, mock(DumpController.class));
mCollection.attach(mGroupCoalescer);
mCollection.addCollectionListener(mCollectionListener);
mCollection.setBuildListener(mBuildListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index be06748..e915be3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -16,9 +16,10 @@
package com.android.systemui.statusbar.notification.collection;
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
+import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
@@ -27,6 +28,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -37,6 +39,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.DumpController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -45,7 +48,7 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.util.Assert;
@@ -100,7 +103,7 @@
MockitoAnnotations.initMocks(this);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog);
+ mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog, mock(DumpController.class));
mListBuilder.setOnRenderListListener(mOnRenderListListener);
mListBuilder.attach(mNotifCollection);
@@ -448,6 +451,29 @@
}
@Test
+ public void testPreRenderNotifsFilteredBreakupGroups() {
+ final String filterTag = "FILTER_ME";
+ // GIVEN a NotifFilter that filters out notifications with a tag
+ NotifFilter filter1 = spy(new NotifFilterWithTag(filterTag));
+ mListBuilder.addPreRenderFilter(filter1);
+
+ // WHEN the pipeline is kicked off on a list of notifs
+ addGroupChildWithTag(0, PACKAGE_2, GROUP_1, filterTag);
+ addGroupChild(1, PACKAGE_2, GROUP_1);
+ addGroupSummary(2, PACKAGE_2, GROUP_1);
+ dispatchBuild();
+
+ // THEN the final list doesn't contain any filtered-out notifs
+ // and groups that are too small are broken up
+ verifyBuiltList(
+ notif(1)
+ );
+
+ // THEN each filtered notif records the filter that did it
+ assertEquals(filter1, mEntrySet.get(0).mExcludingFilter);
+ }
+
+ @Test
public void testNotifFiltersCanBePreempted() {
// GIVEN two notif filters
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
@@ -550,12 +576,17 @@
}
@Test
- public void testNotifsAreSectioned() {
- // GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides
+ public void testNotifSections() {
+ // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide
// notifs based on package name
mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
- final SectionsProvider sectionsProvider = spy(new PackageSectioner());
- mListBuilder.setSectionsProvider(sectionsProvider);
+ final NotifSection pkg1Section = spy(new PackageSection(PACKAGE_1));
+ final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+ // NOTE: no package 3 section explicitly added, so notifs with package 3 will get set by
+ // ShadeListBuilder's sDefaultSection which will demote it to the last section
+ final NotifSection pkg4Section = spy(new PackageSection(PACKAGE_4));
+ final NotifSection pkg5Section = spy(new PackageSection(PACKAGE_5));
+ mListBuilder.setSections(Arrays.asList(pkg1Section, pkg2Section, pkg4Section, pkg5Section));
// WHEN we build a list with different packages
addNotif(0, PACKAGE_4);
@@ -582,19 +613,93 @@
child(6)
),
notif(8),
- notif(3),
- notif(9)
+ notif(9),
+ notif(3)
);
- // THEN the sections provider is called on all top level elements (but no children and no
- // entries that were filtered out)
- verify(sectionsProvider).getSection(mEntrySet.get(1));
- verify(sectionsProvider).getSection(mEntrySet.get(2));
- verify(sectionsProvider).getSection(mEntrySet.get(3));
- verify(sectionsProvider).getSection(mEntrySet.get(7));
- verify(sectionsProvider).getSection(mEntrySet.get(8));
- verify(sectionsProvider).getSection(mEntrySet.get(9));
- verify(sectionsProvider).getSection(mBuiltList.get(3));
+ // THEN the first section (pkg1Section) is called on all top level elements (but
+ // no children and no entries that were filtered out)
+ verify(pkg1Section).isInSection(mEntrySet.get(1));
+ verify(pkg1Section).isInSection(mEntrySet.get(2));
+ verify(pkg1Section).isInSection(mEntrySet.get(3));
+ verify(pkg1Section).isInSection(mEntrySet.get(7));
+ verify(pkg1Section).isInSection(mEntrySet.get(8));
+ verify(pkg1Section).isInSection(mEntrySet.get(9));
+ verify(pkg1Section).isInSection(mBuiltList.get(3));
+
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(0));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(4));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(5));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(6));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(10));
+
+ // THEN the last section (pkg5Section) is not called on any of the entries that were
+ // filtered or already in a section
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(0));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(1));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(2));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(4));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(5));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(6));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(7));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(8));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(10));
+
+ verify(pkg5Section).isInSection(mEntrySet.get(3));
+ verify(pkg5Section).isInSection(mEntrySet.get(9));
+
+ // THEN the correct section is assigned for entries in pkg1Section
+ assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection);
+ assertEquals(0, mEntrySet.get(2).getSection());
+ assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection);
+ assertEquals(0, mEntrySet.get(7).getSection());
+
+ // THEN the correct section is assigned for entries in pkg2Section
+ assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection);
+ assertEquals(1, mEntrySet.get(1).getSection());
+ assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection);
+ assertEquals(1, mEntrySet.get(8).getSection());
+ assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection);
+ assertEquals(1, mBuiltList.get(3).getSection());
+
+ // THEN no section was assigned to entries in pkg4Section (since they were filtered)
+ assertEquals(null, mEntrySet.get(0).mNotifSection);
+ assertEquals(-1, mEntrySet.get(0).getSection());
+ assertEquals(null, mEntrySet.get(10).mNotifSection);
+ assertEquals(-1, mEntrySet.get(10).getSection());
+
+
+ // THEN the correct section is assigned for entries in pkg5Section
+ assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection);
+ assertEquals(3, mEntrySet.get(9).getSection());
+
+ // THEN the children entries are assigned the same section as its parent
+ assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection);
+ assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection());
+ assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection);
+ assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection());
+ }
+
+ @Test
+ public void testNotifUsesDefaultSection() {
+ // GIVEN a Section for Package2
+ final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+ mListBuilder.setSections(Arrays.asList(pkg2Section));
+
+ // WHEN we build a list with pkg1 and pkg2 packages
+ addNotif(0, PACKAGE_1);
+ addNotif(1, PACKAGE_2);
+ dispatchBuild();
+
+ // THEN the list is sorted according to section
+ verifyBuiltList(
+ notif(1),
+ notif(0)
+ );
+
+ // THEN the entry that didn't have an explicit section gets assigned the DefaultSection
+ assertEquals(1, notif(0).entry.getSection());
+ assertNotNull(notif(0).entry.mNotifSection);
}
@Test
@@ -629,7 +734,7 @@
// GIVEN a bunch of registered listeners and pluggables
NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
NotifPromoter promoter = spy(new IdPromoter(3));
- PackageSectioner sectioner = spy(new PackageSectioner());
+ NotifSection section = spy(new PackageSection(PACKAGE_1));
NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
mListBuilder.addPreGroupFilter(preGroupFilter);
@@ -637,7 +742,7 @@
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
mListBuilder.setComparators(Collections.singletonList(comparator));
- mListBuilder.setSectionsProvider(sectioner);
+ mListBuilder.setSections(Arrays.asList(section));
mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
mListBuilder.addPreRenderFilter(preRenderFilter);
@@ -657,7 +762,7 @@
mOnBeforeTransformGroupsListener,
promoter,
mOnBeforeSortListener,
- sectioner,
+ section,
comparator,
preRenderFilter,
mOnBeforeRenderListListener,
@@ -670,7 +775,7 @@
inOrder.verify(promoter, atLeastOnce())
.shouldPromoteToTopLevel(any(NotificationEntry.class));
inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
- inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class));
+ inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class));
inOrder.verify(comparator, atLeastOnce())
.compare(any(ListEntry.class), any(ListEntry.class));
inOrder.verify(preRenderFilter, atLeastOnce())
@@ -684,12 +789,12 @@
// GIVEN a variety of pluggables
NotifFilter packageFilter = new PackageFilter(PACKAGE_1);
NotifPromoter idPromoter = new IdPromoter(4);
- SectionsProvider sectionsProvider = new PackageSectioner();
+ NotifSection section = new PackageSection(PACKAGE_1);
NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
mListBuilder.addPreGroupFilter(packageFilter);
mListBuilder.addPromoter(idPromoter);
- mListBuilder.setSectionsProvider(sectionsProvider);
+ mListBuilder.setSections(Arrays.asList(section));
mListBuilder.setComparators(Collections.singletonList(hypeComparator));
// GIVEN a set of random notifs
@@ -709,7 +814,7 @@
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
- sectionsProvider.invalidateList();
+ section.invalidateList();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
@@ -982,9 +1087,10 @@
return builder;
}
- /** Same behavior as {@link #addNotif(int, String)}. */
- private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
+ private NotificationEntryBuilder addGroupChildWithTag(int index, String packageId,
+ String groupId, String tag) {
final NotificationEntryBuilder builder = new NotificationEntryBuilder()
+ .setTag(tag)
.setPkg(packageId)
.setId(nextId(packageId))
.setRank(nextRank());
@@ -999,6 +1105,11 @@
return builder;
}
+ /** Same behavior as {@link #addNotif(int, String)}. */
+ private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
+ return addGroupChildWithTag(index, packageId, groupId, null);
+ }
+
private int nextId(String packageName) {
Integer nextId = mNextIdMap.get(packageName);
if (nextId == null) {
@@ -1081,7 +1192,8 @@
}
} catch (AssertionError err) {
throw new AssertionError(
- "List under test failed verification:\n" + dumpList(mBuiltList), err);
+ "List under test failed verification:\n" + dumpTree(mBuiltList,
+ true, ""), err);
}
}
@@ -1166,6 +1278,21 @@
}
}
+ /** Filters out notifications with a particular tag */
+ private static class NotifFilterWithTag extends NotifFilter {
+ private final String mTag;
+
+ NotifFilterWithTag(String tag) {
+ super("NotifFilterWithTag_" + tag);
+ mTag = tag;
+ }
+
+ @Override
+ public boolean shouldFilterOut(NotificationEntry entry, long now) {
+ return Objects.equals(entry.getSbn().getTag(), mTag);
+ }
+ }
+
/** Promotes notifs with particular IDs */
private static class IdPromoter extends NotifPromoter {
private final List<Integer> mIds;
@@ -1202,25 +1329,18 @@
}
}
- /** Sorts notifs into sections based on their package name */
- private static class PackageSectioner extends SectionsProvider {
+ /** Represents a section for the passed pkg */
+ private static class PackageSection extends NotifSection {
+ private final String mPackage;
- PackageSectioner() {
- super("PackageSectioner");
+ PackageSection(String pkg) {
+ super("PackageSection_" + pkg);
+ mPackage = pkg;
}
@Override
- public int getSection(ListEntry entry) {
- switch (entry.getRepresentativeEntry().getSbn().getPackageName()) {
- case PACKAGE_1:
- return 1;
- case PACKAGE_2:
- return 2;
- case PACKAGE_3:
- return 3;
- default:
- return 4;
- }
+ public boolean isInSection(ListEntry entry) {
+ return entry.getRepresentativeEntry().getSbn().getPackageName().equals(mPackage);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
new file mode 100644
index 0000000..0bf458c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.BubblesTestActivity;
+import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationConversationInfoTest extends SysuiTestCase {
+ private static final String TEST_PACKAGE_NAME = "test_package";
+ private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME;
+ private static final int TEST_UID = 1;
+ private static final String TEST_CHANNEL = "test_channel";
+ private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
+ private static final String CONVERSATION_ID = "convo";
+
+ private TestableLooper mTestableLooper;
+ private NotificationConversationInfo mNotificationInfo;
+ private NotificationChannel mNotificationChannel;
+ private NotificationChannel mConversationChannel;
+ private StatusBarNotification mSbn;
+ private NotificationEntry mEntry;
+ private StatusBarNotification mBubbleSbn;
+ private NotificationEntry mBubbleEntry;
+ @Mock
+ private ShortcutInfo mShortcutInfo;
+ private Drawable mImage;
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private INotificationManager mMockINotificationManager;
+ @Mock
+ private PackageManager mMockPackageManager;
+ @Mock
+ private VisualStabilityManager mVisualStabilityManager;
+ @Mock
+ private BubbleController mBubbleController;
+ @Mock
+ private LauncherApps mLauncherApps;
+ @Mock
+ private ShortcutManager mShortcutManager;
+ @Mock
+ private NotificationGuts mNotificationGuts;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestableLooper = TestableLooper.get(this);
+
+ mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+ mDependency.injectTestDependency(BubbleController.class, mBubbleController);
+ // Inflate the layout
+ final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate(
+ R.layout.notification_conversation_info,
+ null);
+ mNotificationInfo.mShowHomeScreen = true;
+ mNotificationInfo.setGutsParent(mNotificationGuts);
+ doAnswer((Answer<Object>) invocation -> {
+ mNotificationInfo.handleCloseControls(true, false);
+ return null;
+ }).when(mNotificationGuts).closeControls(anyInt(), anyInt(), eq(true), eq(false));
+ // Our view is never attached to a window so the View#post methods in NotificationInfo never
+ // get called. Setting this will skip the post and do the action immediately.
+ mNotificationInfo.mSkipPost = true;
+
+ // PackageManager must return a packageInfo and applicationInfo.
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = TEST_PACKAGE_NAME;
+ when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt()))
+ .thenReturn(packageInfo);
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = TEST_UID; // non-zero
+ when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
+ applicationInfo);
+ final PackageInfo systemPackageInfo = new PackageInfo();
+ systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
+ when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
+ .thenReturn(systemPackageInfo);
+ when(mMockPackageManager.getPackageInfo(eq("android"), anyInt()))
+ .thenReturn(packageInfo);
+
+ when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
+ List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
+ mImage = mContext.getDrawable(R.drawable.ic_star);
+ when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
+ anyInt())).thenReturn(mImage);
+
+ mNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+
+ Notification notification = new Notification.Builder(mContext, mNotificationChannel.getId())
+ .setShortcutId(CONVERSATION_ID)
+ .setStyle(new Notification.MessagingStyle(new Person.Builder().setName("m").build())
+ .addMessage(new Notification.MessagingStyle.Message(
+ "hello!", 1000, new Person.Builder().setName("other").build())))
+ .build();
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
+ notification, UserHandle.CURRENT, null, 0);
+ mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
+
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0,
+ new Intent(mContext, BubblesTestActivity.class), 0);
+ mBubbleSbn = new SbnBuilder(mSbn).setBubbleMetadata(
+ new Notification.BubbleMetadata.Builder()
+ .setIntent(bubbleIntent)
+ .setIcon(Icon.createWithResource(mContext, R.drawable.android)).build())
+ .build();
+ mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build();
+
+ mConversationChannel = new NotificationChannel(
+ TEST_CHANNEL + " : " + CONVERSATION_ID, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+ mConversationChannel.setConversationId(TEST_CHANNEL, CONVERSATION_ID);
+ when(mMockINotificationManager.getConversationNotificationChannel(anyString(), anyInt(),
+ anyString(), eq(TEST_CHANNEL), eq(false), eq(CONVERSATION_ID)))
+ .thenReturn(mConversationChannel);
+ }
+
+ @Test
+ public void testBindNotification_SetsTextShortcutName() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.name);
+ assertEquals(mShortcutInfo.getShortLabel(), textView.getText().toString());
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsShortcutIcon() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
+ assertEquals(mImage, view.getDrawable());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextApplicationName() {
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
+ assertTrue(textView.getText().toString().contains("App Name"));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextChannelName() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.parent_channel_name);
+ assertTrue(textView.getText().toString().contains(mNotificationChannel.getName()));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextGroupName() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ when(mMockINotificationManager.getNotificationChannelGroupForPackage(
+ anyString(), anyString(), anyInt())).thenReturn(group);
+ mNotificationChannel.setGroup(group.getId());
+ mConversationChannel.setGroup(group.getId());
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+ assertTrue(textView.getText().toString().contains(group.getName()));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ assertEquals(VISIBLE, textView.getVisibility());
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_GroupNameHiddenIfNoGroup() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ assertEquals(GONE, textView.getVisibility());
+ assertEquals(GONE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_noDelegate() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+ assertEquals(GONE, nameView.getVisibility());
+ final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+ assertEquals(GONE, dividerView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_delegate() throws Exception {
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0,
+ mSbn.getNotification(), UserHandle.CURRENT, null, 0);
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = 7; // non-zero
+ when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn(
+ applicationInfo);
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other");
+
+ NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build();
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ entry,
+ null,
+ null,
+ null,
+ true);
+ final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+ assertEquals(VISIBLE, nameView.getVisibility());
+ assertTrue(nameView.getText().toString().contains("Proxied"));
+ final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+ assertEquals(VISIBLE, dividerView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsOnClickListenerForSettings() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(mConversationChannel, c);
+ latch.countDown();
+ },
+ null,
+ null,
+ true);
+
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ settingsButton.performClick();
+ // Verify that listener was triggered.
+ assertEquals(0, latch.getCount());
+ }
+
+ @Test
+ public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(mNotificationChannel, c);
+ latch.countDown();
+ },
+ null,
+ null,
+ false);
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ public void testBindNotification_bubbleActionVisibleWhenCanBubble() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.VISIBLE, bubbleView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_bubbleActionVisibleWhenCannotBubble() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.GONE, bubbleView.getVisibility());
+ }
+
+ @Test
+ public void testAddToHome() throws Exception {
+ when(mShortcutManager.isRequestPinShortcutSupported()).thenReturn(true);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.home).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null);
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ public void testSnooze() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ (View v, int hours) -> {
+ latch.countDown();
+ },
+ true);
+
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.snooze).performClick();
+ mTestableLooper.processAllMessages();
+
+ assertEquals(0, latch.getCount());
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ public void testBubble_promotesBubble() throws Exception {
+ mNotificationChannel.setAllowBubbles(false);
+ mConversationChannel.setAllowBubbles(false);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ assertFalse(mBubbleEntry.isBubble());
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.bubble).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mBubbleController, times(1)).onUserCreatedBubbleFromNotification(mBubbleEntry);
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertTrue(captor.getValue().canBubble());
+ }
+
+ @Test
+ public void testBubble_demotesBubble() throws Exception {
+ mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ assertTrue(mBubbleEntry.isBubble());
+
+ // Demote it
+ mNotificationInfo.findViewById(R.id.bubble).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry);
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertFalse(captor.getValue().canBubble());
+ }
+
+ @Test
+ public void testFavorite_favorite() throws Exception {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ Button fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_favorite),
+ fave.getText().toString());
+
+ fave.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertTrue(captor.getValue().canBypassDnd());
+ }
+
+ @Test
+ public void testFavorite_unfavorite() throws Exception {
+ mNotificationChannel.setBypassDnd(true);
+ mConversationChannel.setBypassDnd(true);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ Button fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
+ fave.getText().toString());
+
+ fave.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertFalse(captor.getValue().canBypassDnd());
+ }
+
+ @Test
+ public void testMute_mute() throws Exception {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ Button mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_mute),
+ mute.getText().toString());
+
+ mute.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance());
+ }
+
+ @Test
+ public void testMute_unmute() throws Exception {
+ mNotificationChannel.setImportance(IMPORTANCE_LOW);
+ mNotificationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportance(IMPORTANCE_LOW);
+ mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ Button mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_unmute),
+ mute.getText().toString());
+
+ mute.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance());
+ }
+
+ @Test
+ public void testBindNotification_createsNewChannel() throws Exception {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
+ anyString(), anyInt(), any(), eq(CONVERSATION_ID));
+ }
+
+ @Test
+ public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
+ mNotificationChannel.setConversationId("", CONVERSATION_ID);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
+ anyString(), anyInt(), any(), eq(CONVERSATION_ID));
+ }
+
+ @Test
+ public void testAdjustImportanceTemporarilyAllowsReordering() {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ mNotificationInfo.findViewById(R.id.mute).performClick();
+
+ verify(mVisualStabilityManager).temporarilyAllowReordering();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index f48c40c..b33d26f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -132,16 +132,6 @@
}
@Test
- public void testNoAppOpsInSlowSwipe_biDirectionalSwipe() {
- NotificationMenuRow row = new NotificationMenuRow(mContext, true);
- row.createMenu(mRow, null);
-
- ViewGroup container = (ViewGroup) row.getMenuView();
- // in the new interruption model there is only the blocking item
- assertEquals(1, container.getChildCount());
- }
-
- @Test
public void testIsSnappedAndOnSameSide() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 9cb06a5..13f3a5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.policy;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -39,6 +42,7 @@
import android.os.Handler;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -358,7 +362,13 @@
}
public void updateDataConnectionState(int dataState, int dataNetType) {
- when(mServiceState.getDataNetworkType()).thenReturn(dataNetType);
+ NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setDomain(DOMAIN_PS)
+ .setAccessNetworkTechnology(dataNetType)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+ .thenReturn(fakeRegInfo);
mPhoneStateListener.onDataConnectionStateChanged(dataState, dataNetType);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 5a5ef8b..f6c750d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,5 +1,8 @@
package com.android.systemui.statusbar.policy;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyInt;
@@ -418,7 +421,13 @@
// State from NR_5G to NONE NR_STATE_RESTRICTED, showing corresponding icon
doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
- doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+ NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setDomain(DOMAIN_PS)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .build();
+ doReturn(fakeRegInfo).when(mServiceState)
+ .getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
TelephonyManager.NETWORK_TYPE_LTE);
@@ -480,8 +489,13 @@
verifyDataIndicators(TelephonyIcons.ICON_LTE);
- when(mServiceState.getDataNetworkType())
- .thenReturn(TelephonyManager.NETWORK_TYPE_HSPA);
+ NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setDomain(DOMAIN_PS)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_HSPA)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+ .thenReturn(fakeRegInfo);
updateServiceState();
verifyDataIndicators(TelephonyIcons.ICON_H);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
index 709a1a8..a785d4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -19,6 +19,7 @@
import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -154,6 +155,7 @@
verify(mockEndListener).onAnimationEnd(
testView,
DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
canceled = false,
finalValue = 10f,
finalVelocity = 0f,
@@ -175,6 +177,7 @@
verify(mockEndListener).onAnimationEnd(
testView,
DynamicAnimation.TRANSLATION_Y,
+ wasFling = false,
canceled = false,
finalValue = 500f,
finalVelocity = 0f,
@@ -214,8 +217,8 @@
verifyUpdateListenerCalls(animator, mockUpdateListener)
verify(mockEndListener, times(1)).onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
- eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(false), anyFloat(),
+ anyFloat(), eq(true))
verify(mockEndAction, times(1)).run()
animator
@@ -329,13 +332,24 @@
// The original end listener should also have been called, with allEnded = true since it was
// provided to an animator that animated only TRANSLATION_X.
verify(mockEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true)
+ .onAnimationEnd(
+ testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 200f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
verifyNoMoreInteractions(mockEndListener)
// The second end listener should have been called, but with allEnded = false since it was
// provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y.
verify(secondEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false)
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 200f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = false)
verifyNoMoreInteractions(secondEndListener)
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
@@ -345,7 +359,12 @@
verifyNoMoreInteractions(mockEndListener)
verify(secondEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y,
+ wasFling = false,
+ canceled = false,
+ finalValue = 4000f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
verifyNoMoreInteractions(secondEndListener)
}
@@ -364,8 +383,8 @@
assertEquals(10f, testView.translationX, 1f)
verify(mockEndListener, times(1))
.onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
- anyFloat(), eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+ eq(10f), anyFloat(), eq(true))
animator
.fling(
@@ -381,8 +400,39 @@
assertEquals(-5f, testView.translationX, 1f)
verify(mockEndListener, times(1))
.onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
- anyFloat(), eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+ eq(-5f), anyFloat(), eq(true))
+ }
+
+ @Test
+ fun testIsPropertyAnimating() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ testView.physicsAnimator
+ .spring(DynamicAnimation.TRANSLATION_X, 500f, springConfig)
+ .fling(DynamicAnimation.TRANSLATION_Y, 10f, flingConfig)
+ .spring(DynamicAnimation.TRANSLATION_Z, 1000f, springConfig)
+ .start()
+
+ // All of the properties we just started should be animating.
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+ // Block until x and y end.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(testView.physicsAnimator,
+ DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)
+
+ // Verify that x and y are no longer animating, but that Z is (it's springing to 1000f).
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Z)
+
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
}
@Test
@@ -395,6 +445,118 @@
assertEquals(200f, testView.translationX, 1f)
}
+ @Test
+ fun testFlingThenSpring() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ // Start at 500f and fling hard to the left. We should quickly reach the 250f minimum, fly
+ // past it since there's so much velocity remaining, then spring back to 250f.
+ testView.translationX = 500f
+ animator
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -5000f,
+ flingConfig.apply { min = 250f },
+ springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // Block until we pass the minimum.
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+ animator) { v -> v.translationX <= 250f }
+
+ // Double check that the view is there.
+ assertTrue(testView.translationX <= 250f)
+
+ // The update listener should have been called with a value < 500f, and then a value less
+ // than or equal to the 250f minimum.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < 500f },
+ { u -> u.value <= 250f })
+
+ // Despite the fact that the fling has ended, the end listener shouldn't have been called
+ // since we're about to begin springing the same property.
+ verifyNoMoreInteractions(mockEndListener)
+ verifyNoMoreInteractions(mockEndAction)
+
+ // Wait for the spring to finish.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_X)
+
+ // Make sure we continued past 250f since the spring should have been started with some
+ // remaining negative velocity from the fling.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < 250f })
+
+ // At this point, the animation end listener should have been called once, and only once,
+ // when the spring ended at 250f.
+ verify(mockEndListener).onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 250f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // The end action should also have been called once.
+ verify(mockEndAction, times(1)).run()
+ verifyNoMoreInteractions(mockEndAction)
+
+ assertEquals(250f, testView.translationX)
+ }
+
+ @Test
+ fun testFlingThenSpring_objectOutsideFlingBounds() {
+ // Start the view at x = -500, well outside the fling bounds of min = 0f, with strong
+ // negative velocity.
+ testView.translationX = -500f
+ animator
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -5000f,
+ flingConfig.apply { min = 0f },
+ springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // The initial -5000f velocity should result in frames to the left of -500f before the view
+ // springs back towards 0f.
+ verifyAnimationUpdateFrames(
+ animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < -500f },
+ { u -> u.value > -500f })
+
+ // We should end up at the fling min.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
+ @Test
+ fun testFlingToMinMaxThenSpring() {
+ // Start at x = 500f.
+ testView.translationX = 500f
+
+ // Fling to the left at the very sad rate of -1 pixels per second. That won't get us much of
+ // anywhere, and certainly not to the 0f min.
+ animator
+ // Good thing we have flingToMinMaxThenSpring!
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -10000f,
+ flingConfig.apply { min = 0f },
+ springConfig,
+ flingMustReachMinOrMax = true)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // Thanks, flingToMinMaxThenSpring, for adding enough velocity to get us here.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
/**
* Verifies that the calls to the mock update listener match the animation update frames
* reported by the test internal listener, in order.
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index d297f3f..e441fb5 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -16,7 +16,7 @@
java_defaults {
name: "TetheringAndroidLibraryDefaults",
- platform_apis: true,
+ sdk_version: "system_current",
srcs: [
"src/**/*.java",
":framework-tethering-shared-srcs",
@@ -29,6 +29,7 @@
"netlink-client",
"networkstack-aidl-interfaces-unstable-java",
"android.hardware.tetheroffload.control-V1.0-java",
+ "net-utils-framework-common",
],
libs: [
"framework-tethering",
@@ -80,7 +81,7 @@
// Common defaults for compiling the actual APK.
java_defaults {
name: "TetheringAppDefaults",
- platform_apis: true,
+ sdk_version: "system_current",
privileged: true,
// Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
// explicitly.
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index 5a71eb2..c71d0d7 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -36,6 +36,7 @@
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
<uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<application
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index ca290c6..19e8d69 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -38,8 +38,9 @@
<!-- List of regexpressions describing the interface (if any) that represent tetherable
Wifi P2P interfaces. If the device doesn't want to support tethering over Wifi P2p this
- should be empty. An example would be "p2p-p2p.*" -->
+ should be empty. An example would be "p2p-p2p\\d-.*" -->
<string-array translatable="false" name="config_tether_wifi_p2p_regexs">
+ <item>"p2p-p2p\\d-.*"</item>
</string-array>
<!-- List of regexpressions describing the interface (if any) that represent tetherable
@@ -100,7 +101,7 @@
<!-- If the mobile hotspot feature requires provisioning, a package name and class name
can be provided to launch a supported application that provisions the devices.
- EntitlementManager will send an inent to Settings with the specified package name and
+ EntitlementManager will send an intent to Settings with the specified package name and
class name in extras to launch provision app.
TODO: note what extras here.
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 6ac467e..4306cf0b 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -26,7 +26,6 @@
import android.net.INetd;
import android.net.INetworkStackStatusCallback;
-import android.net.INetworkStatsService;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -176,7 +175,6 @@
private final SharedLog mLog;
private final INetd mNetd;
- private final INetworkStatsService mStatsService;
private final Callback mCallback;
private final InterfaceController mInterfaceCtrl;
@@ -208,12 +206,10 @@
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
- INetd netd, INetworkStatsService statsService, Callback callback,
- boolean usingLegacyDhcp, Dependencies deps) {
+ INetd netd, Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
super(ifaceName, looper);
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
- mStatsService = statsService;
mCallback = callback;
mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
mIfaceName = ifaceName;
@@ -882,12 +878,6 @@
// to remove their rules, which generates errors.
// Just do the best we can.
try {
- // About to tear down NAT; gather remaining statistics.
- mStatsService.forceUpdate();
- } catch (Exception e) {
- mLog.e("Exception in forceUpdate: " + e.toString());
- }
- try {
mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
} catch (RemoteException | ServiceSpecificException e) {
mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString());
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index 4e2a2c1..1cabc8d 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -27,8 +27,6 @@
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
-import static com.android.internal.R.string.config_wifi_tether_enable;
-
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -36,7 +34,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.res.Resources;
import android.net.util.SharedLog;
import android.os.Bundle;
import android.os.Handler;
@@ -54,6 +51,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.R;
import java.io.PrintWriter;
@@ -75,9 +73,7 @@
"com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM";
private static final String EXTRA_SUBID = "subId";
- // {@link ComponentName} of the Service used to run tether provisioning.
- private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(
- Resources.getSystem().getString(config_wifi_tether_enable));
+ private final ComponentName mSilentProvisioningService;
private static final int MS_PER_HOUR = 60 * 60 * 1000;
private static final int EVENT_START_PROVISIONING = 0;
private static final int EVENT_STOP_PROVISIONING = 1;
@@ -122,6 +118,8 @@
mHandler = new EntitlementHandler(masterHandler.getLooper());
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM),
null, mHandler);
+ mSilentProvisioningService = ComponentName.unflattenFromString(
+ mContext.getResources().getString(R.string.config_wifi_tether_enable));
}
public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) {
@@ -377,7 +375,7 @@
intent.putExtra(EXTRA_RUN_PROVISION, true);
intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
intent.putExtra(EXTRA_SUBID, subId);
- intent.setComponent(TETHER_SERVICE);
+ intent.setComponent(mSilentProvisioningService);
// Only admin user can change tethering and SilentTetherProvisioning don't need to
// show UI, it is fine to always start setting's background service as system user.
mContext.startService(intent);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
index ce7c2a6..cc36f4a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
@@ -16,35 +16,40 @@
package com.android.server.connectivity.tethering;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_UID;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
-import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.NetworkStats.UID_TETHERING;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.usage.NetworkStatsManager;
import android.content.ContentResolver;
-import android.net.ITetheringStatsProvider;
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
import android.net.RouteInfo;
import android.net.netlink.ConntrackMessage;
import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkSocket;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
import android.net.util.SharedLog;
import android.os.Handler;
-import android.os.INetworkManagementService;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.SystemClock;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.text.TextUtils;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
@@ -73,13 +78,19 @@
private static final String ANYIP = "0.0.0.0";
private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
+ @VisibleForTesting
+ enum StatsType {
+ STATS_PER_IFACE,
+ STATS_PER_UID,
+ }
+
private enum UpdateType { IF_NEEDED, FORCE };
private final Handler mHandler;
private final OffloadHardwareInterface mHwInterface;
private final ContentResolver mContentResolver;
- private final INetworkManagementService mNms;
- private final ITetheringStatsProvider mStatsProvider;
+ private final @NonNull OffloadTetheringStatsProvider mStatsProvider;
+ private final @Nullable NetworkStatsProviderCallback mStatsProviderCb;
private final SharedLog mLog;
private final HashMap<String, LinkProperties> mDownstreams;
private boolean mConfigInitialized;
@@ -109,22 +120,23 @@
private int mNatUpdateNetlinkErrors;
public OffloadController(Handler h, OffloadHardwareInterface hwi,
- ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
+ ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log) {
mHandler = h;
mHwInterface = hwi;
mContentResolver = contentResolver;
- mNms = nms;
mStatsProvider = new OffloadTetheringStatsProvider();
mLog = log.forSubComponent(TAG);
mDownstreams = new HashMap<>();
mExemptPrefixes = new HashSet<>();
mLastLocalPrefixStrs = new HashSet<>();
-
+ NetworkStatsProviderCallback providerCallback = null;
try {
- mNms.registerTetheringStatsProvider(mStatsProvider, getClass().getSimpleName());
- } catch (RemoteException e) {
- mLog.e("Cannot register offload stats provider: " + e);
+ providerCallback = nsm.registerNetworkStatsProvider(
+ getClass().getSimpleName(), mStatsProvider);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Cannot register offload stats provider: " + e);
}
+ mStatsProviderCb = providerCallback;
}
/** Start hardware offload. */
@@ -173,7 +185,7 @@
// and we need to synchronize stats and limits between
// software and hardware forwarding.
updateStatsForAllUpstreams();
- forceTetherStatsPoll();
+ mStatsProvider.pushTetherStats();
}
@Override
@@ -186,7 +198,7 @@
// limits set take into account any software tethering
// traffic that has been happening in the meantime.
updateStatsForAllUpstreams();
- forceTetherStatsPoll();
+ mStatsProvider.pushTetherStats();
// [2] (Re)Push all state.
computeAndPushLocalPrefixes(UpdateType.FORCE);
pushAllDownstreamState();
@@ -204,14 +216,11 @@
// the HAL queued the callback.
// TODO: rev the HAL so that it provides an interface name.
- // Fetch current stats, so that when our notification reaches
- // NetworkStatsService and triggers a poll, we will respond with
- // current data (which will be above the limit that was reached).
- // Note that if we just changed upstream, this is unnecessary but harmless.
- // The stats for the previous upstream were already updated on this thread
- // just after the upstream was changed, so they are also up-to-date.
updateStatsForCurrentUpstream();
- forceTetherStatsPoll();
+ mStatsProvider.pushTetherStats();
+ // Push stats to service does not cause the service react to it immediately.
+ // Inform the service about limit reached.
+ if (mStatsProviderCb != null) mStatsProviderCb.onLimitReached();
}
@Override
@@ -253,42 +262,37 @@
return mConfigInitialized && mControlInitialized;
}
- private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
- @Override
- public NetworkStats getTetherStats(int how) {
- // getTetherStats() is the only function in OffloadController that can be called from
- // a different thread. Do not attempt to update stats by querying the offload HAL
- // synchronously from a different thread than our Handler thread. http://b/64771555.
- Runnable updateStats = () -> {
- updateStatsForCurrentUpstream();
- };
- if (Looper.myLooper() == mHandler.getLooper()) {
- updateStats.run();
- } else {
- mHandler.post(updateStats);
- }
+ @VisibleForTesting
+ class OffloadTetheringStatsProvider extends AbstractNetworkStatsProvider {
+ // These stats must only ever be touched on the handler thread.
+ @NonNull
+ private NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+ @NonNull
+ private NetworkStats mUidStats = new NetworkStats(0L, 0);
- NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
- NetworkStats.Entry entry = new NetworkStats.Entry();
- entry.set = SET_DEFAULT;
- entry.tag = TAG_NONE;
- entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
+ @VisibleForTesting
+ @NonNull
+ NetworkStats getTetherStats(@NonNull StatsType how) {
+ NetworkStats stats = new NetworkStats(0L, 0);
+ final int uid = (how == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL;
- for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
- ForwardedStats value = kv.getValue();
- entry.iface = kv.getKey();
- entry.rxBytes = value.rxBytes;
- entry.txBytes = value.txBytes;
- stats.addEntry(entry);
+ for (final Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
+ final ForwardedStats value = kv.getValue();
+ final Entry entry = new Entry(kv.getKey(), uid, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, value.rxBytes, 0L, value.txBytes, 0L, 0L);
+ stats = stats.addValues(entry);
}
return stats;
}
@Override
- public void setInterfaceQuota(String iface, long quotaBytes) {
+ public void setLimit(String iface, long quotaBytes) {
+ mLog.i("setLimit: " + iface + "," + quotaBytes);
+ // Listen for all iface is necessary since upstream might be changed after limit
+ // is set.
mHandler.post(() -> {
- if (quotaBytes == ITetheringStatsProvider.QUOTA_UNLIMITED) {
+ if (quotaBytes == QUOTA_UNLIMITED) {
mInterfaceQuotas.remove(iface);
} else {
mInterfaceQuotas.put(iface, quotaBytes);
@@ -296,6 +300,42 @@
maybeUpdateDataLimit(iface);
});
}
+
+ /**
+ * Push stats to service, but does not cause a force polling. Note that this can only be
+ * called on the handler thread.
+ */
+ public void pushTetherStats() {
+ // TODO: remove the accumulated stats and report the diff from HAL directly.
+ if (null == mStatsProviderCb) return;
+ final NetworkStats ifaceDiff =
+ getTetherStats(StatsType.STATS_PER_IFACE).subtract(mIfaceStats);
+ final NetworkStats uidDiff =
+ getTetherStats(StatsType.STATS_PER_UID).subtract(mUidStats);
+ try {
+ mStatsProviderCb.onStatsUpdated(0 /* token */, ifaceDiff, uidDiff);
+ mIfaceStats = mIfaceStats.add(ifaceDiff);
+ mUidStats = mUidStats.add(uidDiff);
+ } catch (RuntimeException e) {
+ mLog.e("Cannot report network stats: ", e);
+ }
+ }
+
+ @Override
+ public void requestStatsUpdate(int token) {
+ mLog.i("requestStatsUpdate: " + token);
+ // Do not attempt to update stats by querying the offload HAL
+ // synchronously from a different thread than the Handler thread. http://b/64771555.
+ mHandler.post(() -> {
+ updateStatsForCurrentUpstream();
+ pushTetherStats();
+ });
+ }
+
+ @Override
+ public void setAlert(long quotaBytes) {
+ // TODO: Ask offload HAL to notify alert without stopping traffic.
+ }
}
private String currentUpstreamInterface() {
@@ -353,14 +393,6 @@
}
}
- private void forceTetherStatsPoll() {
- try {
- mNms.tetherLimitReached(mStatsProvider);
- } catch (RemoteException e) {
- mLog.e("Cannot report data limit reached: " + e);
- }
- }
-
/** Set current tethering upstream LinkProperties. */
public void setUpstreamLinkProperties(LinkProperties lp) {
if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 4a8ef1f..90b9d3f 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -29,6 +29,8 @@
import android.os.RemoteException;
import android.system.OsConstants;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
@@ -91,6 +93,12 @@
txBytes = 0;
}
+ @VisibleForTesting
+ public ForwardedStats(long rxBytes, long txBytes) {
+ this.rxBytes = rxBytes;
+ this.txBytes = txBytes;
+ }
+
/** Add Tx/Rx bytes. */
public void add(ForwardedStats other) {
rxBytes += other.rxBytes;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 038d7ae..37e0cc1 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -20,6 +20,7 @@
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
@@ -53,6 +54,7 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
@@ -63,9 +65,8 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.ITetheringEventCallback;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -176,8 +177,6 @@
private final Context mContext;
private final ArrayMap<String, TetherState> mTetherStates;
private final BroadcastReceiver mStateReceiver;
- private final INetworkStatsService mStatsService;
- private final INetworkPolicyManager mPolicyManager;
private final Looper mLooper;
private final StateMachine mTetherMasterSM;
private final OffloadController mOffloadController;
@@ -207,13 +206,12 @@
private boolean mWifiTetherRequested;
private Network mTetherUpstream;
private TetherStatesParcel mTetherStatesParcel;
+ private boolean mDataSaverEnabled = false;
public Tethering(TetheringDependencies deps) {
mLog.mark("Tethering.constructed");
mDeps = deps;
mContext = mDeps.getContext();
- mStatsService = mDeps.getINetworkStatsService();
- mPolicyManager = mDeps.getINetworkPolicyManager();
mNetd = mDeps.getINetd(mContext);
mLooper = mDeps.getTetheringLooper();
@@ -224,10 +222,12 @@
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
mTetherMasterSM.start();
+ final NetworkStatsManager statsManager =
+ (NetworkStatsManager) mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
mHandler = mTetherMasterSM.getHandler();
mOffloadController = new OffloadController(mHandler,
mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(),
- mDeps.getINetworkManagementService(), mLog);
+ statsManager, mLog);
mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
mForwardedDownstreams = new HashSet<>();
@@ -264,7 +264,7 @@
}
final UserManager userManager = (UserManager) mContext.getSystemService(
- Context.USER_SERVICE);
+ Context.USER_SERVICE);
mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler);
mActiveDataSubIdListener = new ActiveDataSubIdListener(executor);
@@ -288,6 +288,7 @@
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
+ filter.addAction(ACTION_RESTRICT_BACKGROUND_CHANGED);
mContext.registerReceiver(mStateReceiver, filter, null, handler);
}
@@ -484,7 +485,7 @@
}
private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
- final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ final BluetoothAdapter adapter = mDeps.getBluetoothAdapter();
if (adapter == null || !adapter.isEnabled()) {
Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: "
+ (adapter == null));
@@ -740,8 +741,7 @@
.setContentIntent(pi);
mLastNotificationId = id;
- notificationManager.notify(null, mLastNotificationId,
- mTetheredNotificationBuilder.buildInto(new Notification()));
+ notificationManager.notify(null, mLastNotificationId, mTetheredNotificationBuilder.build());
}
@VisibleForTesting
@@ -775,6 +775,9 @@
} else if (action.equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)) {
mLog.log("OBSERVED user restrictions changed");
handleUserRestrictionAction();
+ } else if (action.equals(ACTION_RESTRICT_BACKGROUND_CHANGED)) {
+ mLog.log("OBSERVED data saver changed");
+ handleDataSaverChanged();
}
}
@@ -885,6 +888,20 @@
private void handleUserRestrictionAction() {
mTetheringRestriction.onUserRestrictionsChanged();
}
+
+ private void handleDataSaverChanged() {
+ final ConnectivityManager connMgr = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ final boolean isDataSaverEnabled = connMgr.getRestrictBackgroundStatus()
+ != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+
+ if (mDataSaverEnabled == isDataSaverEnabled) return;
+
+ mDataSaverEnabled = isDataSaverEnabled;
+ if (mDataSaverEnabled) {
+ untetherAll();
+ }
+ }
}
@VisibleForTesting
@@ -1982,15 +1999,6 @@
mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, error));
- try {
- // Notify that we're tethering (or not) this interface.
- // This is how data saver for instance knows if the user explicitly
- // turned on tethering (thus keeping us from being in data saver mode).
- mPolicyManager.onTetheringChanged(iface, state == IpServer.STATE_TETHERED);
- } catch (RemoteException e) {
- // Not really very much we can do here.
- }
-
// If TetherMasterSM is in ErrorState, TetherMasterSM stays there.
// Thus we give a chance for TetherMasterSM to recover to InitialState
// by sending CMD_CLEAR_ERROR
@@ -2054,7 +2062,7 @@
mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
final TetherState tetherState = new TetherState(
- new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mStatsService,
+ new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
makeControlCallback(), mConfig.enableLegacyDhcpServer,
mDeps.getIpServerDependencies()));
mTetherStates.put(iface, tetherState);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index dbe7892..068c346 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -23,18 +23,6 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
-import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
-import static com.android.internal.R.array.config_tether_bluetooth_regexs;
-import static com.android.internal.R.array.config_tether_dhcp_range;
-import static com.android.internal.R.array.config_tether_upstream_types;
-import static com.android.internal.R.array.config_tether_usb_regexs;
-import static com.android.internal.R.array.config_tether_wifi_p2p_regexs;
-import static com.android.internal.R.array.config_tether_wifi_regexs;
-import static com.android.internal.R.bool.config_tether_upstream_automatic;
-import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
-import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
-import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
-
import android.content.Context;
import android.content.res.Resources;
import android.net.TetheringConfigurationParcel;
@@ -45,6 +33,7 @@
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.tethering.R;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -113,27 +102,30 @@
activeDataSubId = id;
Resources res = getResources(ctx, activeDataSubId);
- tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs);
+ tetherableUsbRegexs = getResourceStringArray(res, R.array.config_tether_usb_regexs);
// TODO: Evaluate deleting this altogether now that Wi-Fi always passes
// us an interface name. Careful consideration needs to be given to
// implications for Settings and for provisioning checks.
- tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs);
- tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs);
- tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
+ tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs);
+ tetherableWifiP2pRegexs = getResourceStringArray(
+ res, R.array.config_tether_wifi_p2p_regexs);
+ tetherableBluetoothRegexs = getResourceStringArray(
+ res, R.array.config_tether_bluetooth_regexs);
isDunRequired = checkDunRequired(ctx);
- chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
+ chooseUpstreamAutomatically = getResourceBoolean(
+ res, R.bool.config_tether_upstream_automatic);
preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
legacyDhcpRanges = getLegacyDhcpRanges(res);
defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
- provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
+ provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app);
provisioningAppNoUi = getProvisioningAppNoUi(res);
provisioningCheckPeriod = getResourceInteger(res,
- config_mobile_hotspot_provision_check_period,
+ R.integer.config_mobile_hotspot_provision_check_period,
0 /* No periodic re-check */);
configLog.log(toString());
@@ -248,7 +240,7 @@
}
private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
- final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types);
+ final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types);
final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
for (int i : ifaceTypes) {
switch (i) {
@@ -298,7 +290,7 @@
}
private static String[] getLegacyDhcpRanges(Resources res) {
- final String[] fromResource = getResourceStringArray(res, config_tether_dhcp_range);
+ final String[] fromResource = getResourceStringArray(res, R.array.config_tether_dhcp_range);
if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
return fromResource;
}
@@ -307,7 +299,7 @@
private static String getProvisioningAppNoUi(Resources res) {
try {
- return res.getString(config_mobile_hotspot_provision_app_no_ui);
+ return res.getString(R.string.config_mobile_hotspot_provision_app_no_ui);
} catch (Resources.NotFoundException e) {
return "";
}
@@ -339,7 +331,7 @@
}
private boolean getEnableLegacyDhcpServer(final Resources res) {
- return getResourceBoolean(res, config_tether_enable_legacy_dhcp_server)
+ return getResourceBoolean(res, R.bool.config_tether_enable_legacy_dhcp_server)
|| getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER);
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
index b16b329..e019c3a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -16,18 +16,15 @@
package com.android.server.connectivity.tethering;
+import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.NetworkRequest;
import android.net.ip.IpServer;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.IBinder;
-import android.os.INetworkManagementService;
import android.os.Looper;
-import android.os.ServiceManager;
import com.android.internal.util.StateMachine;
@@ -97,33 +94,6 @@
}
/**
- * Get a reference to INetworkManagementService to registerTetheringStatsProvider from
- * OffloadController. Note: This should be removed soon by Usage refactor work in R
- * development cycle.
- */
- public INetworkManagementService getINetworkManagementService() {
- return INetworkManagementService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
- }
-
- /**
- * Get a reference to INetworkStatsService to force update tethering usage.
- * Note: This should be removed in R development cycle.
- */
- public INetworkStatsService getINetworkStatsService() {
- return INetworkStatsService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
- }
-
- /**
- * Get a reference to INetworkPolicyManager to be used by tethering.
- */
- public INetworkPolicyManager getINetworkPolicyManager() {
- return INetworkPolicyManager.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
- }
-
- /**
* Get a reference to INetd to be used by tethering.
*/
public INetd getINetd(Context context) {
@@ -140,4 +110,9 @@
* Get Context of TetheringSerice.
*/
public abstract Context getContext();
+
+ /**
+ * Get a reference to BluetoothAdapter to be used by tethering.
+ */
+ public abstract BluetoothAdapter getBluetoothAdapter();
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
index d5cdd8a..4dd6830 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
@@ -22,6 +22,8 @@
import android.net.RouteInfo;
import android.net.util.InterfaceSet;
+import com.android.net.module.util.NetUtils;
+
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -85,7 +87,7 @@
private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) {
final RouteInfo ri = (lp != null)
- ? RouteInfo.selectBestRoute(lp.getAllRoutes(), dst)
+ ? NetUtils.selectBestRoute(lp.getAllRoutes(), dst)
: null;
return (ri != null) ? ri.getInterface() : null;
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
index e4e4a09..cb7d392 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -24,6 +24,7 @@
import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.net.IIntResultListener;
@@ -42,7 +43,6 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.Settings;
@@ -363,7 +363,7 @@
IBinder connector;
try {
final long before = System.currentTimeMillis();
- while ((connector = ServiceManager.getService(
+ while ((connector = (IBinder) mContext.getSystemService(
Context.NETWORK_STACK_SERVICE)) == null) {
if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector");
@@ -377,6 +377,11 @@
}
return INetworkStackConnector.Stub.asInterface(connector);
}
+
+ @Override
+ public BluetoothAdapter getBluetoothAdapter() {
+ return BluetoothAdapter.getDefaultAdapter();
+ }
};
}
return mDeps;
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 65a0ac1..1f50b6b 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -52,7 +52,6 @@
import static org.mockito.Mockito.when;
import android.net.INetd;
-import android.net.INetworkStatsService;
import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -99,7 +98,6 @@
private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
@Mock private INetd mNetd;
- @Mock private INetworkStatsService mStatsService;
@Mock private IpServer.Callback mCallback;
@Mock private SharedLog mSharedLog;
@Mock private IDhcpServer mDhcpServer;
@@ -139,13 +137,13 @@
mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
}
mIpServer = new IpServer(
- IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mStatsService,
+ IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
mCallback, usingLegacyDhcp, mDependencies);
mIpServer.start();
// Starting the state machine always puts us in a consistent state and notifies
// the rest of the world that we've changed from an unknown to available state.
mLooper.dispatchAll();
- reset(mNetd, mStatsService, mCallback);
+ reset(mNetd, mCallback);
when(mRaDaemon.start()).thenReturn(true);
}
@@ -162,7 +160,7 @@
if (upstreamIface != null) {
dispatchTetherConnectionChanged(upstreamIface);
}
- reset(mNetd, mStatsService, mCallback);
+ reset(mNetd, mCallback);
}
@Before public void setUp() throws Exception {
@@ -173,13 +171,13 @@
@Test
public void startsOutAvailable() {
mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
- mNetd, mStatsService, mCallback, false /* usingLegacyDhcp */, mDependencies);
+ mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies);
mIpServer.start();
mLooper.dispatchAll();
verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mCallback, mNetd, mStatsService);
+ verifyNoMoreInteractions(mCallback, mNetd);
}
@Test
@@ -198,7 +196,7 @@
// None of these commands should trigger us to request action from
// the rest of the system.
dispatchCommand(command);
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
}
@@ -210,7 +208,7 @@
verify(mCallback).updateInterfaceState(
mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -228,7 +226,7 @@
mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -236,7 +234,7 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, null);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNetd, mStatsService, mCallback);
+ InOrder inOrder = inOrder(mNetd, mCallback);
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
@@ -245,7 +243,7 @@
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -265,7 +263,7 @@
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -285,7 +283,7 @@
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -298,7 +296,7 @@
InOrder inOrder = inOrder(mNetd);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -306,13 +304,12 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mStatsService);
- inOrder.verify(mStatsService).forceUpdate();
+ InOrder inOrder = inOrder(mNetd);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -322,12 +319,10 @@
doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mStatsService);
- inOrder.verify(mStatsService).forceUpdate();
+ InOrder inOrder = inOrder(mNetd);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mStatsService).forceUpdate();
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@@ -340,13 +335,11 @@
IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNetd, mStatsService);
- inOrder.verify(mStatsService).forceUpdate();
+ InOrder inOrder = inOrder(mNetd);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mStatsService).forceUpdate();
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@@ -356,8 +349,7 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNetd, mStatsService, mCallback);
- inOrder.verify(mStatsService).forceUpdate();
+ InOrder inOrder = inOrder(mNetd, mCallback);
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
@@ -368,7 +360,7 @@
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -435,11 +427,11 @@
public void ignoresDuplicateUpstreamNotifications() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
for (int i = 0; i < 5; i++) {
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 79bba7f..4f07461 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -27,7 +27,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -56,10 +55,10 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.R;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.networkstack.tethering.R;
import org.junit.After;
import org.junit.Before;
@@ -157,16 +156,18 @@
eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
when(mResources.getStringArray(R.array.config_tether_dhcp_range))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getIntArray(R.array.config_tether_upstream_types))
- .thenReturn(new int[0]);
- when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+ .thenReturn(new int[0]);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
+ when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
mMockContext = new MockContext(mContext);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 7886ca6..7e62e5a 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -16,21 +16,26 @@
package com.android.server.connectivity.tethering;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_IFACE;
-import static android.net.NetworkStats.STATS_PER_UID;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStats.UID_TETHERING;
import static android.net.RouteInfo.RTN_UNICAST;
-import static android.net.TrafficStats.UID_TETHERING;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_IFACE;
+import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_UID;
import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
import static com.android.testutils.MiscAssertsKt.assertContainsAll;
import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.NetworkStatsUtilsKt.orderInsensitiveEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
@@ -39,11 +44,14 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
+import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.ITetheringStatsProvider;
@@ -51,10 +59,12 @@
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
import android.net.RouteInfo;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
import android.net.util.SharedLog;
import android.os.Handler;
-import android.os.INetworkManagementService;
import android.os.Looper;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -97,11 +107,13 @@
@Mock private OffloadHardwareInterface mHardware;
@Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
- @Mock private INetworkManagementService mNMService;
+ @Mock private NetworkStatsManager mStatsManager;
+ @Mock private NetworkStatsProviderCallback mTetherStatsProviderCb;
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
ArgumentCaptor.forClass(ArrayList.class);
- private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
- ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
+ private final ArgumentCaptor<OffloadController.OffloadTetheringStatsProvider>
+ mTetherStatsProviderCaptor =
+ ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class);
private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
private MockContentResolver mContentResolver;
@@ -114,6 +126,8 @@
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mContext.getContentResolver()).thenReturn(mContentResolver);
FakeSettingsProvider.clearSettingsProvider();
+ when(mStatsManager.registerNetworkStatsProvider(anyString(), any()))
+ .thenReturn(mTetherStatsProviderCb);
}
@After public void tearDown() throws Exception {
@@ -139,9 +153,9 @@
private OffloadController makeOffloadController() throws Exception {
OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
- mHardware, mContentResolver, mNMService, new SharedLog("test"));
- verify(mNMService).registerTetheringStatsProvider(
- mTetherStatsProviderCaptor.capture(), anyString());
+ mHardware, mContentResolver, mStatsManager, new SharedLog("test"));
+ verify(mStatsManager).registerNetworkStatsProvider(anyString(),
+ mTetherStatsProviderCaptor.capture());
return offload;
}
@@ -384,12 +398,11 @@
inOrder.verifyNoMoreInteractions();
}
- private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) {
- assertEquals(iface, entry.iface);
- assertEquals(stats.rxBytes, entry.rxBytes);
- assertEquals(stats.txBytes, entry.txBytes);
- assertEquals(SET_DEFAULT, entry.set);
- assertEquals(TAG_NONE, entry.tag);
+ private static @NonNull Entry buildTestEntry(@NonNull OffloadController.StatsType how,
+ @NonNull String iface, long rxBytes, long txBytes) {
+ return new Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING, SET_DEFAULT,
+ TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, 0L,
+ txBytes, 0L, 0L);
}
@Test
@@ -400,19 +413,16 @@
final OffloadController offload = makeOffloadController();
offload.start();
+ final OffloadController.OffloadTetheringStatsProvider provider =
+ mTetherStatsProviderCaptor.getValue();
+
final String ethernetIface = "eth1";
final String mobileIface = "rmnet_data0";
- ForwardedStats ethernetStats = new ForwardedStats();
- ethernetStats.rxBytes = 12345;
- ethernetStats.txBytes = 54321;
-
- ForwardedStats mobileStats = new ForwardedStats();
- mobileStats.rxBytes = 999;
- mobileStats.txBytes = 99999;
-
- when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
- when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
+ when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
+ new ForwardedStats(12345, 54321));
+ when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(
+ new ForwardedStats(999, 99999));
InOrder inOrder = inOrder(mHardware);
@@ -432,10 +442,35 @@
// Expect that we fetch stats from the previous upstream.
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(mobileIface));
- ethernetStats = new ForwardedStats();
- ethernetStats.rxBytes = 100000;
- ethernetStats.txBytes = 100000;
- when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
+ // Verify that the fetched stats are stored.
+ final NetworkStats ifaceStats = provider.getTetherStats(STATS_PER_IFACE);
+ final NetworkStats uidStats = provider.getTetherStats(STATS_PER_UID);
+ final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 12345, 54321));
+
+ final NetworkStats expectedUidStats = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 12345, 54321));
+
+ assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStats));
+ assertTrue(orderInsensitiveEquals(expectedUidStats, uidStats));
+
+ final ArgumentCaptor<NetworkStats> ifaceStatsCaptor = ArgumentCaptor.forClass(
+ NetworkStats.class);
+ final ArgumentCaptor<NetworkStats> uidStatsCaptor = ArgumentCaptor.forClass(
+ NetworkStats.class);
+
+ // Force pushing stats update to verify the stats reported.
+ provider.pushTetherStats();
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(),
+ ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+ assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStatsCaptor.getValue()));
+ assertTrue(orderInsensitiveEquals(expectedUidStats, uidStatsCaptor.getValue()));
+
+
+ when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
+ new ForwardedStats(100000, 100000));
offload.setUpstreamLinkProperties(null);
// Expect that we first clear the HAL's upstream parameters.
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
@@ -443,37 +478,38 @@
// Expect that we fetch stats from the previous upstream.
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));
- ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
- NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE);
- NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID);
- waitForIdle();
// There is no current upstream, so no stats are fetched.
inOrder.verify(mHardware, never()).getForwardedStats(any());
inOrder.verifyNoMoreInteractions();
- assertEquals(2, stats.size());
- assertEquals(2, perUidStats.size());
+ // Verify that the stored stats is accumulated.
+ final NetworkStats ifaceStatsAccu = provider.getTetherStats(STATS_PER_IFACE);
+ final NetworkStats uidStatsAccu = provider.getTetherStats(STATS_PER_UID);
+ final NetworkStats expectedIfaceStatsAccu = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 112345, 154321));
- NetworkStats.Entry entry = null;
- for (int i = 0; i < stats.size(); i++) {
- assertEquals(UID_ALL, stats.getValues(i, entry).uid);
- assertEquals(UID_TETHERING, perUidStats.getValues(i, entry).uid);
- }
+ final NetworkStats expectedUidStatsAccu = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 112345, 154321));
- int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
- int mobilePosition = 1 - ethernetPosition;
+ assertTrue(orderInsensitiveEquals(expectedIfaceStatsAccu, ifaceStatsAccu));
+ assertTrue(orderInsensitiveEquals(expectedUidStatsAccu, uidStatsAccu));
- entry = stats.getValues(mobilePosition, entry);
- assertNetworkStats(mobileIface, mobileStats, entry);
- entry = perUidStats.getValues(mobilePosition, entry);
- assertNetworkStats(mobileIface, mobileStats, entry);
+ // Verify that only diff of stats is reported.
+ reset(mTetherStatsProviderCb);
+ provider.pushTetherStats();
+ final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 0, 0))
+ .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 100000, 100000));
- ethernetStats.rxBytes = 12345 + 100000;
- ethernetStats.txBytes = 54321 + 100000;
- entry = stats.getValues(ethernetPosition, entry);
- assertNetworkStats(ethernetIface, ethernetStats, entry);
- entry = perUidStats.getValues(ethernetPosition, entry);
- assertNetworkStats(ethernetIface, ethernetStats, entry);
+ final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 0, 0))
+ .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 100000, 100000));
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(),
+ ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+ assertTrue(orderInsensitiveEquals(expectedIfaceStatsDiff, ifaceStatsCaptor.getValue()));
+ assertTrue(orderInsensitiveEquals(expectedUidStatsDiff, uidStatsCaptor.getValue()));
}
@Test
@@ -493,19 +529,19 @@
lp.setInterfaceName(ethernetIface);
offload.setUpstreamLinkProperties(lp);
- ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
+ AbstractNetworkStatsProvider provider = mTetherStatsProviderCaptor.getValue();
final InOrder inOrder = inOrder(mHardware);
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
// Applying an interface quota to the current upstream immediately sends it to the hardware.
- provider.setInterfaceQuota(ethernetIface, ethernetLimit);
+ provider.setLimit(ethernetIface, ethernetLimit);
waitForIdle();
inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit);
inOrder.verifyNoMoreInteractions();
// Applying an interface quota to another upstream does not take any immediate action.
- provider.setInterfaceQuota(mobileIface, mobileLimit);
+ provider.setLimit(mobileIface, mobileLimit);
waitForIdle();
inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
@@ -518,7 +554,7 @@
// Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set
// to Long.MAX_VALUE.
- provider.setInterfaceQuota(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
+ provider.setLimit(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
waitForIdle();
inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE);
@@ -526,7 +562,7 @@
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false);
lp.setInterfaceName(ethernetIface);
offload.setUpstreamLinkProperties(lp);
- provider.setInterfaceQuota(mobileIface, mobileLimit);
+ provider.setLimit(mobileIface, mobileLimit);
waitForIdle();
inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
@@ -535,7 +571,7 @@
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false);
lp.setInterfaceName(mobileIface);
offload.setUpstreamLinkProperties(lp);
- provider.setInterfaceQuota(mobileIface, mobileLimit);
+ provider.setLimit(mobileIface, mobileLimit);
waitForIdle();
inOrder.verify(mHardware).getForwardedStats(ethernetIface);
inOrder.verify(mHardware).stopOffloadControl();
@@ -551,7 +587,7 @@
OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
callback.onStoppedLimitReached();
- verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
}
@Test
@@ -654,9 +690,10 @@
// Verify forwarded stats behaviour.
verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
+ // TODO: verify the exact stats reported.
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
+ verifyNoMoreInteractions(mTetherStatsProviderCb);
verifyNoMoreInteractions(mHardware);
- verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
- verifyNoMoreInteractions(mNMService);
}
@Test
@@ -719,8 +756,8 @@
// Verify forwarded stats behaviour.
verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
- verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
- verifyNoMoreInteractions(mNMService);
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
+ verifyNoMoreInteractions(mTetherStatsProviderCb);
// TODO: verify local prefixes and downstreams are also pushed to the HAL.
verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index ef97ad4..3635964 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -26,13 +26,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
-import static com.android.internal.R.array.config_tether_bluetooth_regexs;
-import static com.android.internal.R.array.config_tether_dhcp_range;
-import static com.android.internal.R.array.config_tether_upstream_types;
-import static com.android.internal.R.array.config_tether_usb_regexs;
-import static com.android.internal.R.array.config_tether_wifi_regexs;
-import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -51,6 +44,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.networkstack.tethering.R;
import org.junit.After;
import org.junit.Before;
@@ -120,15 +114,18 @@
() -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
- when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]);
- when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]);
- when(mResources.getStringArray(config_tether_wifi_regexs))
+ when(mResources.getStringArray(R.array.config_tether_dhcp_range)).thenReturn(
+ new String[0]);
+ when(mResources.getStringArray(R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
+ when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[]{ "test_wlan\\d" });
- when(mResources.getStringArray(config_tether_bluetooth_regexs)).thenReturn(new String[0]);
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
- when(mResources.getStringArray(config_mobile_hotspot_provision_app))
+ when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)).thenReturn(
+ new String[0]);
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+ when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
.thenReturn(new String[0]);
- when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
mHasTelephonyManager = true;
mMockContext = new MockContext(mContext);
mEnableLegacyDhcpServer = false;
@@ -140,7 +137,7 @@
}
private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
legacyTetherUpstreamTypes);
return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
}
@@ -224,7 +221,7 @@
@Test
public void testNoDefinedUpstreamTypesAddsEthernet() {
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{});
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[]{});
when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
final TetheringConfiguration cfg = new TetheringConfiguration(
@@ -246,7 +243,7 @@
@Test
public void testDefinedUpstreamTypesSansEthernetAddsEthernet() {
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
@@ -264,7 +261,7 @@
@Test
public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() {
- when(mResources.getIntArray(config_tether_upstream_types))
+ when(mResources.getIntArray(R.array.config_tether_upstream_types))
.thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI});
when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
@@ -282,7 +279,8 @@
@Test
public void testNewDhcpServerDisabled() {
- when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ true);
doReturn(false).when(
() -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
@@ -291,7 +289,8 @@
new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
assertTrue(enableByRes.enableLegacyDhcpServer);
- when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
doReturn(true).when(
() -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
@@ -303,7 +302,8 @@
@Test
public void testNewDhcpServerEnabled() {
- when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
doReturn(false).when(
() -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
@@ -329,16 +329,17 @@
private void setUpResourceForSubId() {
when(mResourcesForSubId.getStringArray(
- config_tether_dhcp_range)).thenReturn(new String[0]);
+ R.array.config_tether_dhcp_range)).thenReturn(new String[0]);
when(mResourcesForSubId.getStringArray(
- config_tether_usb_regexs)).thenReturn(new String[0]);
+ R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
when(mResourcesForSubId.getStringArray(
- config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
+ R.array.config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
when(mResourcesForSubId.getStringArray(
- config_tether_bluetooth_regexs)).thenReturn(new String[0]);
- when(mResourcesForSubId.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
+ R.array.config_tether_bluetooth_regexs)).thenReturn(new String[0]);
+ when(mResourcesForSubId.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
+ new int[0]);
when(mResourcesForSubId.getStringArray(
- config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
+ R.array.config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
}
}
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 900c671..5910624 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
@@ -19,6 +19,9 @@
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
@@ -37,8 +40,6 @@
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
-
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -53,6 +54,7 @@
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -60,6 +62,8 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.app.usage.NetworkStatsManager;
+import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -68,9 +72,8 @@
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.ITetheringEventCallback;
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
@@ -99,7 +102,6 @@
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Bundle;
import android.os.Handler;
-import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.RemoteException;
@@ -120,6 +122,7 @@
import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
import org.junit.After;
import org.junit.Before;
@@ -133,6 +136,7 @@
import java.net.Inet6Address;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Vector;
@RunWith(AndroidJUnit4.class)
@@ -151,9 +155,7 @@
@Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
- @Mock private INetworkManagementService mNMService;
- @Mock private INetworkStatsService mStatsService;
- @Mock private INetworkPolicyManager mPolicyManager;
+ @Mock private NetworkStatsManager mStatsManager;
@Mock private OffloadHardwareInterface mOffloadHardwareInterface;
@Mock private Resources mResources;
@Mock private TelephonyManager mTelephonyManager;
@@ -167,6 +169,7 @@
@Mock private INetd mNetd;
@Mock private UserManager mUserManager;
@Mock private NetworkRequest mNetworkRequest;
+ @Mock private ConnectivityManager mCm;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -217,6 +220,8 @@
if (Context.USB_SERVICE.equals(name)) return mUsbManager;
if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
if (Context.USER_SERVICE.equals(name)) return mUserManager;
+ if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager;
+ if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
return super.getSystemService(name);
}
@@ -334,21 +339,6 @@
}
@Override
- public INetworkManagementService getINetworkManagementService() {
- return mNMService;
- }
-
- @Override
- public INetworkStatsService getINetworkStatsService() {
- return mStatsService;
- }
-
- @Override
- public INetworkPolicyManager getINetworkPolicyManager() {
- return mPolicyManager;
- }
-
- @Override
public INetd getINetd(Context context) {
return mNetd;
}
@@ -362,6 +352,12 @@
public Context getContext() {
return mServiceContext;
}
+
+ @Override
+ public BluetoothAdapter getBluetoothAdapter() {
+ // TODO: add test for bluetooth tethering.
+ return null;
+ }
}
private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
@@ -420,24 +416,24 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
+ when(mResources.getStringArray(R.array.config_tether_dhcp_range))
.thenReturn(new String[0]);
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs))
+ when(mResources.getStringArray(R.array.config_tether_usb_regexs))
.thenReturn(new String[] { "test_rndis\\d" });
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
+ when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[]{ "test_wlan\\d" });
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+ when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[]{ "test_p2p-p2p\\d-.*" });
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
+ when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
.thenReturn(new String[0]);
- when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
- .thenReturn(new int[0]);
- when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
- .thenReturn(false);
- when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+ when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(false);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
when(mNetd.interfaceGetList())
.thenReturn(new String[] {
TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME});
+ when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
mInterfaceConfiguration = new InterfaceConfigurationParcel();
mInterfaceConfiguration.flags = new String[0];
when(mRouterAdvertisementDaemon.start())
@@ -457,7 +453,7 @@
mServiceContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(ACTION_TETHER_STATE_CHANGED));
mTethering = makeTethering();
- verify(mNMService).registerTetheringStatsProvider(any(), anyString());
+ verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
verify(mNetd).registerUnsolicitedEventListener(any());
final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
ArgumentCaptor.forClass(PhoneStateListener.class);
@@ -700,7 +696,8 @@
@Test
public void workingMobileUsbTethering_IPv4LegacyDhcp() {
- when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ true);
sendConfigurationChanged();
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
@@ -788,8 +785,7 @@
@Test
public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception {
- when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
- .thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
sendConfigurationChanged();
// Setup IPv6
@@ -1317,7 +1313,7 @@
private void workingWifiP2pGroupOwnerLegacyMode(
boolean emulateInterfaceStatusChanged) throws Exception {
// change to legacy mode and update tethering information by chaning SIM
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+ when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[]{});
final int fakeSubId = 1234;
mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
@@ -1355,6 +1351,50 @@
workingWifiP2pGroupClient(false);
}
+ private void setDataSaverEnabled(boolean enabled) {
+ final Intent intent = new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED);
+ mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+ final int status = enabled ? RESTRICT_BACKGROUND_STATUS_ENABLED
+ : RESTRICT_BACKGROUND_STATUS_DISABLED;
+ when(mCm.getRestrictBackgroundStatus()).thenReturn(status);
+ mLooper.dispatchAll();
+ }
+
+ @Test
+ public void testDataSaverChanged() {
+ // Start Tethering.
+ final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
+ runUsbTethering(upstreamState);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ // Data saver is ON.
+ setDataSaverEnabled(true);
+ // Verify that tethering should be disabled.
+ verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ mTethering.interfaceRemoved(TEST_USB_IFNAME);
+ mLooper.dispatchAll();
+ assertEquals(mTethering.getTetheredIfaces(), new String[0]);
+ reset(mUsbManager);
+
+ runUsbTethering(upstreamState);
+ // Verify that user can start tethering again without turning OFF data saver.
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+
+ // If data saver is keep ON with change event, tethering should not be OFF this time.
+ setDataSaverEnabled(true);
+ verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+
+ // If data saver is turned OFF, it should not change tethering.
+ setDataSaverEnabled(false);
+ verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ }
+
+ private static <T> void assertContains(Collection<T> collection, T element) {
+ assertTrue(element + " not found in " + collection, collection.contains(element));
+ }
+
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 5eaa80a..c3965c4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -36,8 +36,12 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -54,6 +58,7 @@
import android.view.Display;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowInfo;
import android.view.accessibility.AccessibilityCache;
@@ -90,6 +95,7 @@
private static final String LOG_TAG = "AbstractAccessibilityServiceConnection";
private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000;
+ protected static final String TAKE_SCREENSHOT = "takeScreenshot";
protected final Context mContext;
protected final SystemSupport mSystemSupport;
protected final WindowManagerInternal mWindowManagerService;
@@ -934,6 +940,54 @@
mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
}
+ @Nullable
+ @Override
+ public Bitmap takeScreenshot(int displayId) {
+ synchronized (mLock) {
+ if (!hasRightsToCurrentUserLocked()) {
+ return null;
+ }
+
+ if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
+ return null;
+ }
+ }
+
+ if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+ return null;
+ }
+
+ final Display display = DisplayManagerGlobal.getInstance()
+ .getRealDisplay(displayId);
+ if (display == null) {
+ return null;
+ }
+ final Point displaySize = new Point();
+ display.getRealSize(displaySize);
+
+ final int rotation = display.getRotation();
+ Bitmap screenShot = null;
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+ // TODO (b/145893483): calling new API with the display as a parameter
+ // when surface control supported.
+ screenShot = SurfaceControl.screenshot(crop, displaySize.x, displaySize.y,
+ rotation);
+ if (screenShot != null) {
+ // Optimization for telling the bitmap that all of the pixels are known to be
+ // opaque (false). This is meant as a drawing hint, as in some cases a bitmap
+ // that is known to be opaque can take a faster drawing case than one that may
+ // have non-opaque per-pixel alpha values.
+ screenShot.setHasAlpha(false);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return screenShot;
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -1018,6 +1072,20 @@
}
}
+ /**
+ * Gets windowId of given token.
+ *
+ * @param token The token
+ * @return window id
+ */
+ @Override
+ public int getWindowIdForLeashToken(@NonNull IBinder token) {
+ synchronized (mLock) {
+ // TODO: Add a method to lookup window ID by given leash token.
+ return -1;
+ }
+ }
+
public void resetLocked() {
mSystemSupport.getKeyEventDispatcher().flush(this);
try {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 58d3489..36a9e0eb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -411,6 +411,7 @@
if (reboundAService) {
onUserStateChangedLocked(userState);
}
+ migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName);
}
}
@@ -811,9 +812,7 @@
userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
userState.setDisplayMagnificationEnabledLocked(false);
- userState.setNavBarMagnificationEnabledLocked(false);
userState.disableShortcutMagnificationLocked();
-
userState.setAutoclickEnabledLocked(false);
userState.mEnabledServices.clear();
userState.mEnabledServices.add(service);
@@ -853,17 +852,20 @@
* navigation area has been clicked.
*
* @param displayId The logical display id.
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
*/
@Override
- public void notifyAccessibilityButtonClicked(int displayId) {
+ public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller does not hold permission "
+ android.Manifest.permission.STATUS_BAR_SERVICE);
}
- synchronized (mLock) {
- notifyAccessibilityButtonClickedLocked(displayId);
- }
+ mMainHandler.sendMessage(obtainMessage(
+ AccessibilityManagerService::performAccessibilityShortcutInternal, this,
+ displayId, ACCESSIBILITY_BUTTON, targetName));
}
/**
@@ -1023,6 +1025,7 @@
// the state since the context in which the current user
// state was used has changed since it was inactive.
onUserStateChangedLocked(userState);
+ migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null);
if (announceNewUser) {
// Schedule announcement of the current user if needed.
@@ -1132,66 +1135,6 @@
}
}
- // TODO(a11y shortcut): Remove this function and Use #performAccessibilityShortcutInternal(
- // ACCESSIBILITY_BUTTON) instead, after the new Settings shortcut Ui merged.
- private void notifyAccessibilityButtonClickedLocked(int displayId) {
- final AccessibilityUserState state = getCurrentUserStateLocked();
-
- int potentialTargets = state.isNavBarMagnificationEnabledLocked() ? 1 : 0;
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- potentialTargets++;
- }
- }
-
- if (potentialTargets == 0) {
- return;
- }
- if (potentialTargets == 1) {
- if (state.isNavBarMagnificationEnabledLocked()) {
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
- displayId));
- return;
- } else {
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- service.notifyAccessibilityButtonClickedLocked(displayId);
- return;
- }
- }
- }
- } else {
- if (state.getServiceAssignedToAccessibilityButtonLocked() == null
- && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityTargetsSelection, this,
- displayId, ACCESSIBILITY_BUTTON));
- } else if (state.isNavBarMagnificationEnabledLocked()
- && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
- displayId));
- return;
- } else {
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
- state.getServiceAssignedToAccessibilityButtonLocked()))) {
- service.notifyAccessibilityButtonClickedLocked(displayId);
- return;
- }
- }
- }
- // The user may have turned off the assigned service or feature
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityTargetsSelection, this,
- displayId, ACCESSIBILITY_BUTTON));
- }
- }
-
private void sendAccessibilityButtonToInputFilter(int displayId) {
synchronized (mLock) {
if (mHasInputFilter && mInputFilter != null) {
@@ -1635,8 +1578,7 @@
if (userState.isDisplayMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
}
- if (userState.isNavBarMagnificationEnabledLocked()
- || userState.isShortcutKeyMagnificationEnabledLocked()) {
+ if (userState.isShortcutMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
}
if (userHasMagnificationServicesLocked(userState)) {
@@ -1886,14 +1828,8 @@
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
0, userState.mUserId) == 1;
- final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
- 0, userState.mUserId) == 1;
- if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())
- || (navBarMagnificationEnabled != userState.isNavBarMagnificationEnabledLocked())) {
+ if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) {
userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
- userState.setNavBarMagnificationEnabledLocked(navBarMagnificationEnabled);
return true;
}
return false;
@@ -2088,8 +2024,7 @@
// displays in one display. It's not a real display and there's no input events for it.
final ArrayList<Display> displays = getValidDisplayList();
if (userState.isDisplayMagnificationEnabledLocked()
- || userState.isNavBarMagnificationEnabledLocked()
- || userState.isShortcutKeyMagnificationEnabledLocked()) {
+ || userState.isShortcutMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
getMagnificationController().register(display.getDisplayId());
@@ -2208,6 +2143,85 @@
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
}
+ /**
+ * 1) Check if the service assigned to accessibility button target sdk version > Q.
+ * If it isn't, remove it from the list and associated setting.
+ * (It happens when an accessibility service package is downgraded.)
+ * 2) Check if an enabled service targeting sdk version > Q and requesting a11y button is
+ * assigned to a shortcut. If it isn't, assigns it to the accessibility button.
+ * (It happens when an enabled accessibility service package is upgraded.)
+ *
+ * @param packageName The package name to check, or {@code null} to check all services.
+ */
+ private void migrateAccessibilityButtonSettingsIfNecessaryLocked(
+ AccessibilityUserState userState, @Nullable String packageName) {
+ final Set<String> buttonTargets =
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+ int lastSize = buttonTargets.size();
+ buttonTargets.removeIf(name -> {
+ if (packageName != null && name != null && !name.contains(packageName)) {
+ return false;
+ }
+ final ComponentName componentName = ComponentName.unflattenFromString(name);
+ if (componentName == null) {
+ return false;
+ }
+ final AccessibilityServiceInfo serviceInfo =
+ userState.getInstalledServiceInfoLocked(componentName);
+ if (serviceInfo == null) {
+ return false;
+ }
+ if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+ .targetSdkVersion > Build.VERSION_CODES.Q) {
+ return false;
+ }
+ // A11y services targeting sdk version <= Q should not be in the list.
+ return true;
+ });
+ boolean changed = (lastSize != buttonTargets.size());
+ lastSize = buttonTargets.size();
+
+ final Set<String> shortcutKeyTargets =
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+ userState.mEnabledServices.forEach(componentName -> {
+ if (packageName != null && componentName != null
+ && !packageName.equals(componentName.getPackageName())) {
+ return;
+ }
+ final AccessibilityServiceInfo serviceInfo =
+ userState.getInstalledServiceInfoLocked(componentName);
+ if (serviceInfo == null) {
+ return;
+ }
+ final boolean requestA11yButton = (serviceInfo.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ if (!(serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+ .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton)) {
+ return;
+ }
+ final String serviceName = serviceInfo.getComponentName().flattenToString();
+ if (TextUtils.isEmpty(serviceName)) {
+ return;
+ }
+ if (shortcutKeyTargets.contains(serviceName) || buttonTargets.contains(serviceName)) {
+ return;
+ }
+ // For enabled a11y services targeting sdk version > Q and requesting a11y button should
+ // be assigned to a shortcut.
+ buttonTargets.add(serviceName);
+ });
+ changed |= (lastSize != buttonTargets.size());
+ if (!changed) {
+ return;
+ }
+
+ // Update setting key with new value.
+ persistColonDelimitedSetToSettingLocked(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ userState.mUserId, buttonTargets, str -> str);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ }
+
private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked();
int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked();
@@ -2269,9 +2283,13 @@
* AIDL-exposed method to be called when the accessibility shortcut key is enabled. Requires
* permission to write secure settings, since someone with that permission can enable
* accessibility services themselves.
+ *
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
*/
@Override
- public void performAccessibilityShortcut() {
+ public void performAccessibilityShortcut(String targetName) {
if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
&& (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
!= PackageManager.PERMISSION_GRANTED)) {
@@ -2280,7 +2298,7 @@
}
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::performAccessibilityShortcutInternal, this,
- Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY));
+ Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName));
}
/**
@@ -2288,20 +2306,31 @@
*
* @param shortcutType The shortcut type.
* @param displayId The display id of the accessibility button.
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
*/
private void performAccessibilityShortcutInternal(int displayId,
- @ShortcutType int shortcutType) {
+ @ShortcutType int shortcutType, @Nullable String targetName) {
final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
if (shortcutTargets.isEmpty()) {
Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
return;
}
- // In case there are many targets assigned to the given shortcut.
- if (shortcutTargets.size() > 1) {
- showAccessibilityTargetsSelection(displayId, shortcutType);
- return;
+ // In case the caller specified a target name
+ if (targetName != null) {
+ if (!shortcutTargets.contains(targetName)) {
+ Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
+ return;
+ }
+ } else {
+ // In case there are many targets assigned to the given shortcut.
+ if (shortcutTargets.size() > 1) {
+ showAccessibilityTargetsSelection(displayId, shortcutType);
+ return;
+ }
+ targetName = shortcutTargets.get(0);
}
- final String targetName = shortcutTargets.get(0);
// In case user assigned magnification to the given shortcut.
if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) {
sendAccessibilityButtonToInputFilter(displayId);
@@ -2844,11 +2873,6 @@
private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
- // TODO(a11y shortcut): Remove this setting key, and have a migrate function in
- // Setting provider after new shortcut UI merged.
- private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
-
private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
@@ -2888,8 +2912,6 @@
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri,
false, this, UserHandle.USER_ALL);
- contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri,
- false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mAutoclickEnabledUri,
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri,
@@ -2924,8 +2946,7 @@
if (readTouchExplorationEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
- } else if (mDisplayMagnificationEnabledUri.equals(uri)
- || mNavBarMagnificationEnabledUri.equals(uri)) {
+ } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
if (readMagnificationEnabledSettingsLocked(userState)) {
onUserStateChangedLocked(userState);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 2032109..7dbec7c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -326,6 +326,19 @@
}
/**
+ * Checks if a service can take screenshot.
+ *
+ * @param service The service requesting access
+ *
+ * @return Whether ot not the service may take screenshot
+ */
+ public boolean canTakeScreenshotLocked(
+ @NonNull AbstractAccessibilityServiceConnection service) {
+ return (service.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT) != 0;
+ }
+
+ /**
* Returns the parent userId of the profile according to the specified userId.
*
* @param userId The userId to check
@@ -426,6 +439,7 @@
return false;
}
}
+ // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy.
if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) {
return true;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 37ac3ec..25911a7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT;
+
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.Manifest;
@@ -25,16 +27,20 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.view.Display;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -313,48 +319,16 @@
}
}
- // TODO(a11y shortcut): Refactoring the logic here, after the new Settings shortcut Ui merged.
public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) {
// If the service does not request the accessibility button, it isn't available
if (!mRequestAccessibilityButton) {
return false;
}
-
// If the accessibility button isn't currently shown, it cannot be available to services
if (!mSystemSupport.isAccessibilityButtonShown()) {
return false;
}
-
- // If magnification is on and assigned to the accessibility button, services cannot be
- if (userState.isNavBarMagnificationEnabledLocked()
- && userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- return false;
- }
-
- int requestingServices = 0;
- for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- requestingServices++;
- }
- }
-
- if (requestingServices == 1) {
- // If only a single service is requesting, it must be this service, and the
- // accessibility button is available to it
- return true;
- } else {
- // With more than one active service, we derive the target from the user's settings
- if (userState.getServiceAssignedToAccessibilityButtonLocked() == null) {
- // If the user has not made an assignment, we treat the button as available to
- // all services until the user interacts with the button to make an assignment
- return true;
- } else {
- // If an assignment was made, it defines availability
- return mComponentName.equals(
- userState.getServiceAssignedToAccessibilityButtonLocked());
- }
- }
+ return true;
}
@Override
@@ -419,4 +393,15 @@
}
}
}
+
+ @Override
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {
+ mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
+ final Bitmap screenshot = super.takeScreenshot(displayId);
+ // Send back the result.
+ final Bundle payload = new Bundle();
+ payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT, screenshot);
+ callback.sendResult(payload);
+ }, null).recycleOnUse());
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index a163f74..ebe2af6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -100,7 +100,6 @@
private boolean mIsAutoclickEnabled;
private boolean mIsDisplayMagnificationEnabled;
private boolean mIsFilterKeyEventsEnabled;
- private boolean mIsNavBarMagnificationEnabled;
private boolean mIsPerformGesturesEnabled;
private boolean mIsTextHighContrastEnabled;
private boolean mIsTouchExplorationEnabled;
@@ -153,7 +152,6 @@
mAccessibilityButtonTargets.clear();
mIsTouchExplorationEnabled = false;
mIsDisplayMagnificationEnabled = false;
- mIsNavBarMagnificationEnabled = false;
mIsAutoclickEnabled = false;
mUserNonInteractiveUiTimeout = 0;
mUserInteractiveUiTimeout = 0;
@@ -435,8 +433,6 @@
pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled));
pw.append(", displayMagnificationEnabled=").append(String.valueOf(
mIsDisplayMagnificationEnabled));
- pw.append(", navBarMagnificationEnabled=").append(String.valueOf(
- mIsNavBarMagnificationEnabled));
pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
@@ -553,8 +549,12 @@
mLastSentClientState = state;
}
- public boolean isShortcutKeyMagnificationEnabledLocked() {
- return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+ /**
+ * Returns true if navibar magnification or shortcut key magnification is enabled.
+ */
+ public boolean isShortcutMagnificationEnabledLocked() {
+ return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME)
+ || mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
}
/**
@@ -690,28 +690,4 @@
public void setUserNonInteractiveUiTimeoutLocked(int timeout) {
mUserNonInteractiveUiTimeout = timeout;
}
-
- // TODO(a11y shortcut): These functions aren't necessary, after the new Settings shortcut Ui
- // is merged.
- boolean isNavBarMagnificationEnabledLocked() {
- return mIsNavBarMagnificationEnabled;
- }
-
- void setNavBarMagnificationEnabledLocked(boolean enabled) {
- mIsNavBarMagnificationEnabled = enabled;
- }
-
- boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
- return mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
- }
-
- ComponentName getServiceAssignedToAccessibilityButtonLocked() {
- final String targetName = mAccessibilityButtonTargets.isEmpty() ? null
- : mAccessibilityButtonTargets.valueAt(0);
- if (targetName == null) {
- return null;
- }
- return ComponentName.unflattenFromString(targetName);
- }
- // TODO(a11y shortcut): End
}
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 7a8a112..5d9af26 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -25,6 +25,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Slog;
import android.view.Display;
@@ -325,5 +326,8 @@
@Override
public void onFingerprintGesture(int gesture) {}
+
+ @Override
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
index 2891c6c..4c9e590 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -82,7 +82,7 @@
mCurrentTaps++;
if (mCurrentTaps == mTargetTaps) {
// Done.
- completeAfterTapTimeout(event, rawEvent, policyFlags);
+ completeGesture(event, rawEvent, policyFlags);
return;
}
// Needs more taps.
diff --git a/services/api/current.txt b/services/api/current.txt
index d802177..18e38b1 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -1 +1,31 @@
// Signature format: 2.0
+package com.android.server {
+
+ public abstract class SystemService {
+ ctor public SystemService(@NonNull android.content.Context);
+ method @NonNull public final android.content.Context getContext();
+ method public boolean isSupportedUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onBootPhase(int);
+ method public void onCleanupUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public abstract void onStart();
+ method public void onStartUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onStopUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onSwitchUser(@Nullable com.android.server.SystemService.TargetUser, @NonNull com.android.server.SystemService.TargetUser);
+ method public void onUnlockUser(@NonNull com.android.server.SystemService.TargetUser);
+ method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder);
+ method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder, boolean);
+ field public static final int PHASE_ACTIVITY_MANAGER_READY = 550; // 0x226
+ field public static final int PHASE_BOOT_COMPLETED = 1000; // 0x3e8
+ field public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520; // 0x208
+ field public static final int PHASE_LOCK_SETTINGS_READY = 480; // 0x1e0
+ field public static final int PHASE_SYSTEM_SERVICES_READY = 500; // 0x1f4
+ field public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; // 0x258
+ field public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // 0x64
+ }
+
+ public static final class SystemService.TargetUser {
+ method @NonNull public android.os.UserHandle getUserHandle();
+ }
+
+}
+
diff --git a/services/art-profile b/services/art-profile
index a0338d5..4e113c8 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -3066,18 +3066,18 @@
HSPLcom/android/server/am/AppBindRecord;->dumpInIntentBind(Ljava/io/PrintWriter;Ljava/lang/String;)V
PLcom/android/server/am/AppBindRecord;->toString()Ljava/lang/String;
PLcom/android/server/am/AppBindRecord;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
-PLcom/android/server/am/AppCompactor$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLcom/android/server/am/AppCompactor$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V
-HSPLcom/android/server/am/AppCompactor;-><init>(Lcom/android/server/am/ActivityManagerService;)V
-HSPLcom/android/server/am/AppCompactor;->access$1000(Lcom/android/server/am/AppCompactor;)V
-HSPLcom/android/server/am/AppCompactor;->access$700(Lcom/android/server/am/AppCompactor;)Lcom/android/server/am/ActivityManagerService;
-HSPLcom/android/server/am/AppCompactor;->access$800(Lcom/android/server/am/AppCompactor;)Ljava/util/ArrayList;
-HSPLcom/android/server/am/AppCompactor;->access$900(Lcom/android/server/am/AppCompactor;)Ljava/util/Random;
-PLcom/android/server/am/AppCompactor;->dump(Ljava/io/PrintWriter;)V
-HSPLcom/android/server/am/AppCompactor;->init()V
-HSPLcom/android/server/am/AppCompactor;->updateCompactionThrottles()V
-HSPLcom/android/server/am/AppCompactor;->updateUseCompaction()V
-HSPLcom/android/server/am/AppCompactor;->useCompaction()Z
+PLcom/android/server/am/CachedAppOptimizer$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+HSPLcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V
+HSPLcom/android/server/am/CachedAppOptimizer;-><init>(Lcom/android/server/am/ActivityManagerService;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->access$1000(Lcom/android/server/am/CachedAppOptimizer;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->access$700(Lcom/android/server/am/CachedAppOptimizer;)Lcom/android/server/am/ActivityManagerService;
+HSPLcom/android/server/am/CachedAppOptimizer;->access$800(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/ArrayList;
+HSPLcom/android/server/am/CachedAppOptimizer;->access$900(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/Random;
+PLcom/android/server/am/CachedAppOptimizer;->dump(Ljava/io/PrintWriter;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->init()V
+HSPLcom/android/server/am/CachedAppOptimizer;->updateCompactionThrottles()V
+HSPLcom/android/server/am/CachedAppOptimizer;->updateUseCompaction()V
+HSPLcom/android/server/am/CachedAppOptimizer;->useCompaction()Z
PLcom/android/server/am/AppErrorDialog$1;->handleMessage(Landroid/os/Message;)V
PLcom/android/server/am/AppErrorDialog;-><init>(Landroid/content/Context;Lcom/android/server/am/ActivityManagerService;Lcom/android/server/am/AppErrorDialog$Data;)V
PLcom/android/server/am/AppErrorDialog;->onClick(Landroid/view/View;)V
@@ -18632,9 +18632,9 @@
Lcom/android/server/am/ActivityManagerService$UidObserverRegistration;
Lcom/android/server/am/ActivityManagerService;
Lcom/android/server/am/AppBindRecord;
-Lcom/android/server/am/AppCompactor$1;
-Lcom/android/server/am/AppCompactor$MemCompactionHandler;
-Lcom/android/server/am/AppCompactor;
+Lcom/android/server/am/CachedAppOptimizer$1;
+Lcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;
+Lcom/android/server/am/CachedAppOptimizer;
Lcom/android/server/am/AppErrorDialog$Data;
Lcom/android/server/am/AppErrorResult;
Lcom/android/server/am/AppErrors$BadProcessInfo;
diff --git a/services/art-profile-boot b/services/art-profile-boot
index e09424b..fe4178a 100644
--- a/services/art-profile-boot
+++ b/services/art-profile-boot
@@ -538,7 +538,7 @@
Lcom/android/server/am/ActivityManagerService;->updateLowMemStateLocked(III)Z
Lcom/android/server/wm/ConfigurationContainer;->getWindowConfiguration()Landroid/app/WindowConfiguration;
Lcom/android/server/am/OomAdjuster;->applyOomAdjLocked(Lcom/android/server/am/ProcessRecord;ZJJ)Z
-Lcom/android/server/am/AppCompactor;->useCompaction()Z
+Lcom/android/server/am/CachedAppOptimizer;->useCompaction()Z
Lcom/android/server/am/ProcessList;->procStatesDifferForMem(II)Z
Lcom/android/server/am/ActivityManagerService;->dispatchUidsChanged()V
Lcom/android/server/audio/AudioService$VolumeStreamState;->setIndex(IILjava/lang/String;)Z
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 7b95ab5..2c229b4 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -2538,7 +2538,6 @@
KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
} else {
if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
- synchronized (mQueueLock) {
// Fire the intent that kicks off the whole shebang...
try {
mRunBackupIntent.send();
@@ -2546,10 +2545,8 @@
// should never happen
Slog.e(TAG, "run-backup intent cancelled!");
}
-
// ...and cancel any pending scheduled job, because we've just superseded it
KeyValueBackupJob.cancel(mUserId, mContext);
- }
}
} finally {
Binder.restoreCallingIdentity(oldId);
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 2f8c506..f3647602 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -23,8 +23,6 @@
import android.os.UserHandle;
import android.os.UserManager;
-import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
-
import java.util.List;
import java.util.Set;
@@ -198,6 +196,12 @@
long beginTime, long endTime, boolean obfuscateInstantApps);
/**
+ * Returns the events for the user in the given time period.
+ */
+ public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime,
+ long endTime, boolean shouldObfuscateInstantApps);
+
+ /**
* Used to persist the last time a job was run for this app, in order to make decisions later
* whether a job should be deferred until later. The time passed in should be in elapsed
* realtime since boot.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5435cba..c1e23e4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -79,7 +79,6 @@
import android.net.IpMemoryStore;
import android.net.IpPrefix;
import android.net.LinkProperties;
-import android.net.LinkProperties.CompareResult;
import android.net.MatchAllNetworkSpecifier;
import android.net.NattSocketKeepalive;
import android.net.Network;
@@ -87,7 +86,6 @@
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkMonitorManager;
@@ -114,6 +112,7 @@
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
import android.net.shared.PrivateDnsConfig;
+import android.net.util.LinkPropertiesUtils.CompareResult;
import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
import android.os.Binder;
@@ -2843,26 +2842,11 @@
return true;
}
- // TODO: delete when direct use of registerNetworkFactory is no longer supported.
- private boolean maybeHandleNetworkFactoryMessage(Message msg) {
- switch (msg.what) {
- default:
- return false;
- case NetworkFactory.EVENT_UNFULFILLABLE_REQUEST: {
- handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.sendingUid,
- /* callOnUnavailable */ true);
- break;
- }
- }
- return true;
- }
-
@Override
public void handleMessage(Message msg) {
if (!maybeHandleAsyncChannelMessage(msg)
&& !maybeHandleNetworkMonitorMessage(msg)
- && !maybeHandleNetworkAgentInfoMessage(msg)
- && !maybeHandleNetworkFactoryMessage(msg)) {
+ && !maybeHandleNetworkAgentInfoMessage(msg)) {
maybeHandleNetworkAgentMessage(msg);
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index db54214..396b977 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2855,8 +2855,10 @@
*/
@Override
public void startCheckpoint(int numTries) throws RemoteException {
- // Only the system process is permitted to start checkpoints
- if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+ // Only the root, system_server and shell processes are permitted to start checkpoints
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID
+ && callingUid != Process.SHELL_UID) {
throw new SecurityException("no permission to start filesystem checkpoint");
}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index f46b9ae..b1584fe 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -21,6 +21,9 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.annotation.SystemApi.Process;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.content.Context;
@@ -62,7 +65,7 @@
*
* {@hide}
*/
-//@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
public abstract class SystemService {
/** @hide */
@@ -129,7 +132,7 @@
* Class representing user in question in the lifecycle callbacks.
* @hide
*/
- //@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+ @SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
public static final class TargetUser {
@NonNull
private final UserInfo mUserInfo;
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index c0f43a8..e7f7846 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -25,11 +25,14 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManagerInternal;
+import android.util.ArrayMap;
import android.util.Slog;
import com.android.server.SystemService.TargetUser;
import com.android.server.utils.TimingsTraceAndSlog;
+import dalvik.system.PathClassLoader;
+
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -63,6 +66,9 @@
// Services that should receive lifecycle events.
private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
+ // Map of paths to PathClassLoader, so we don't load the same path multiple times.
+ private final ArrayMap<String, PathClassLoader> mLoadedPaths = new ArrayMap<>();
+
private int mCurrentPhase = -1;
private UserManagerInternal mUserManagerInternal;
@@ -76,20 +82,46 @@
*
* @return The service instance.
*/
- @SuppressWarnings("unchecked")
public SystemService startService(String className) {
- final Class<SystemService> serviceClass;
+ final Class<SystemService> serviceClass = loadClassFromLoader(className,
+ this.getClass().getClassLoader());
+ return startService(serviceClass);
+ }
+
+ /**
+ * Starts a service by class name and a path that specifies the jar where the service lives.
+ *
+ * @return The service instance.
+ */
+ public SystemService startServiceFromJar(String className, String path) {
+ PathClassLoader pathClassLoader = mLoadedPaths.get(path);
+ if (pathClassLoader == null) {
+ // NB: the parent class loader should always be the system server class loader.
+ // Changing it has implications that require discussion with the mainline team.
+ pathClassLoader = new PathClassLoader(path, this.getClass().getClassLoader());
+ mLoadedPaths.put(path, pathClassLoader);
+ }
+ final Class<SystemService> serviceClass = loadClassFromLoader(className, pathClassLoader);
+ return startService(serviceClass);
+ }
+
+ /*
+ * Loads and initializes a class from the given classLoader. Returns the class.
+ */
+ @SuppressWarnings("unchecked")
+ private static Class<SystemService> loadClassFromLoader(String className,
+ ClassLoader classLoader) {
try {
- serviceClass = (Class<SystemService>)Class.forName(className);
+ return (Class<SystemService>) Class.forName(className, true, classLoader);
} catch (ClassNotFoundException ex) {
- Slog.i(TAG, "Starting " + className);
throw new RuntimeException("Failed to create service " + className
- + ": service class not found, usually indicates that the caller should "
+ + " from class loader " + classLoader.toString() + ": service class not "
+ + "found, usually indicates that the caller should "
+ "have called PackageManager.hasSystemFeature() to check whether the "
+ "feature is available on this device before trying to start the "
- + "services that implement it", ex);
+ + "services that implement it. Also ensure that the correct path for the "
+ + "classloader is supplied, if applicable.", ex);
}
- return startService(serviceClass);
}
/**
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 27e0d52..5a56a9f 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -61,7 +61,6 @@
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.DebugUtils;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsLog;
@@ -164,8 +163,7 @@
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
- private SparseArray<Pair<VibrationEffect, VibrationAttributes>> mAlwaysOnEffects =
- new SparseArray<>();
+ private SparseArray<Vibration> mAlwaysOnEffects = new SparseArray<>();
static native boolean vibratorExists();
static native void vibratorInit();
@@ -461,6 +459,10 @@
Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY),
true, mSettingObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+ true, mSettingObserver, UserHandle.USER_ALL);
+
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -508,7 +510,8 @@
}
@Override // Binder call
- public boolean setAlwaysOnEffect(int id, VibrationEffect effect, VibrationAttributes attrs) {
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+ VibrationAttributes attrs) {
if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
}
@@ -518,8 +521,8 @@
}
if (effect == null) {
synchronized (mLock) {
- mAlwaysOnEffects.delete(id);
- vibratorAlwaysOnDisable(id);
+ mAlwaysOnEffects.delete(alwaysOnId);
+ vibratorAlwaysOnDisable(alwaysOnId);
}
} else {
if (!verifyVibrationEffect(effect)) {
@@ -529,13 +532,11 @@
Slog.e(TAG, "Only prebaked effects supported for always-on.");
return false;
}
- if (attrs == null) {
- attrs = new VibrationAttributes.Builder()
- .build();
- }
+ attrs = fixupVibrationAttributes(attrs);
synchronized (mLock) {
- mAlwaysOnEffects.put(id, Pair.create(effect, attrs));
- updateAlwaysOnLocked(id, effect, attrs);
+ Vibration vib = new Vibration(null, effect, attrs, uid, opPkg, null);
+ mAlwaysOnEffects.put(alwaysOnId, vib);
+ updateAlwaysOnLocked(alwaysOnId, vib);
}
}
return true;
@@ -575,6 +576,23 @@
return true;
}
+ private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) {
+ if (attrs == null) {
+ attrs = DEFAULT_ATTRIBUTES;
+ }
+ if (shouldBypassDnd(attrs)) {
+ if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
+ final int flags = attrs.getFlags()
+ & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+ attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build();
+ }
+ }
+
+ return attrs;
+ }
+
private static long[] getLongIntArray(Resources r, int resid) {
int[] ar = r.getIntArray(resid);
if (ar == null) {
@@ -604,19 +622,7 @@
return;
}
- if (attrs == null) {
- attrs = DEFAULT_ATTRIBUTES;
- }
-
- if (shouldBypassDnd(attrs)) {
- if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
- final int flags = attrs.getFlags()
- & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
- attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build();
- }
- }
+ attrs = fixupVibrationAttributes(attrs);
// If our current vibration is longer than the new vibration and is the same amplitude,
// then just let the current one finish.
@@ -777,29 +783,8 @@
private void startVibrationLocked(final Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- if (!isAllowedToVibrateLocked(vib)) {
- return;
- }
-
final int intensity = getCurrentIntensityLocked(vib);
- if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
- return;
- }
-
- if (vib.isRingtone() && !shouldVibrateForRingtone()) {
- if (DEBUG) {
- Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
- }
- return;
- }
-
- final int mode = getAppOpMode(vib);
- if (mode != AppOpsManager.MODE_ALLOWED) {
- if (mode == AppOpsManager.MODE_ERRORED) {
- // We might be getting calls from within system_server, so we don't actually
- // want to throw a SecurityException here.
- Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
- }
+ if (!shouldVibrate(vib, intensity)) {
return;
}
applyVibrationIntensityScalingLocked(vib, intensity);
@@ -958,6 +943,35 @@
return mode;
}
+ private boolean shouldVibrate(Vibration vib, int intensity) {
+ if (!isAllowedToVibrateLocked(vib)) {
+ return false;
+ }
+
+ if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ return false;
+ }
+
+ if (vib.isRingtone() && !shouldVibrateForRingtone()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
+ }
+ return false;
+ }
+
+ final int mode = getAppOpMode(vib);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (mode == AppOpsManager.MODE_ERRORED) {
+ // We might be getting calls from within system_server, so we don't actually
+ // want to throw a SecurityException here.
+ Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
@GuardedBy("mLock")
private void reportFinishVibrationLocked() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
@@ -1069,14 +1083,12 @@
mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
}
- private void updateAlwaysOnLocked(int id, VibrationEffect effect, VibrationAttributes attrs) {
- // TODO: Check DND and LowPower settings
- final Vibration vib = new Vibration(null, effect, attrs, 0, null, null);
+ private void updateAlwaysOnLocked(int id, Vibration vib) {
final int intensity = getCurrentIntensityLocked(vib);
- if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ if (!shouldVibrate(vib, intensity)) {
vibratorAlwaysOnDisable(id);
} else {
- final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
final int strength = intensityToEffectStrength(intensity);
vibratorAlwaysOnEnable(id, prebaked.getId(), strength);
}
@@ -1085,8 +1097,8 @@
private void updateAlwaysOnLocked() {
for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
int id = mAlwaysOnEffects.keyAt(i);
- Pair<VibrationEffect, VibrationAttributes> pair = mAlwaysOnEffects.valueAt(i);
- updateAlwaysOnLocked(id, pair.first, pair.second);
+ Vibration vib = mAlwaysOnEffects.valueAt(i);
+ updateAlwaysOnLocked(id, vib);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8f6d981..883e7c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2555,7 +2555,7 @@
Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
Process.THREAD_GROUP_SYSTEM);
Process.setThreadGroupAndCpuset(
- mOomAdjuster.mAppCompact.mCompactionThread.getThreadId(),
+ mOomAdjuster.mCachedAppOptimizer.mCachedAppOptimizerThread.getThreadId(),
Process.THREAD_GROUP_SYSTEM);
} catch (Exception e) {
Slog.w(TAG, "Setting background thread cpuset failed");
@@ -5304,7 +5304,7 @@
String data, Bundle extras, boolean ordered,
boolean sticky, int sendingUser) {
synchronized (ActivityManagerService.this) {
- mOomAdjuster.mAppCompact.compactAllSystem();
+ mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
}
}
@@ -9000,7 +9000,7 @@
final long timeSinceLastIdle = now - mLastIdleTime;
// Compact all non-zygote processes to freshen up the page cache.
- mOomAdjuster.mAppCompact.compactAllSystem();
+ mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
final long lowRamSinceLastIdle = getLowRamTimeSinceIdle(now);
mLastIdleTime = now;
@@ -9108,13 +9108,14 @@
final Resources res = mContext.getResources();
mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
com.android.internal.R.string.config_appsNotReportingCrashes));
- mUserController.mUserSwitchUiEnabled = !res.getBoolean(
+ final boolean userSwitchUiEnabled = !res.getBoolean(
com.android.internal.R.bool.config_customUserSwitchUi);
- mUserController.mMaxRunningUsers = res.getInteger(
+ final int maxRunningUsers = res.getInteger(
com.android.internal.R.integer.config_multiuserMaxRunningUsers);
- mUserController.mDelayUserDataLocking = res.getBoolean(
+ final boolean delayUserDataLocking = res.getBoolean(
com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
-
+ mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers,
+ delayUserDataLocking);
mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
mPssDeferralTime = pssDeferralMs;
}
@@ -10020,7 +10021,7 @@
synchronized(this) {
mConstants.dump(pw);
- mOomAdjuster.dumpAppCompactorSettings(pw);
+ mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
@@ -10425,7 +10426,7 @@
} else if ("settings".equals(cmd)) {
synchronized (this) {
mConstants.dump(pw);
- mOomAdjuster.dumpAppCompactorSettings(pw);
+ mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
}
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
@@ -17942,7 +17943,31 @@
@Override
public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
- return mUserController.stopUser(userId, force, callback, null /* keyEvictedCallback */);
+ return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
+ /* callback= */ callback, /* keyEvictedCallback= */ null);
+ }
+
+ /**
+ * Stops user but allow delayed locking. Delayed locking keeps user unlocked even after
+ * stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true.
+ *
+ * <p>When delayed locking is not enabled through the overlay, this call becomes the same
+ * with {@link #stopUser(int, boolean, IStopUserCallback)} call.
+ *
+ * @param userId User id to stop.
+ * @param force Force stop the user even if the user is related with system user or current
+ * user.
+ * @param callback Callback called when user has stopped.
+ *
+ * @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns
+ * other {@code ActivityManager#USER_OP_*} codes for failure.
+ *
+ */
+ @Override
+ public int stopUserWithDelayedLocking(final int userId, boolean force,
+ final IStopUserCallback callback) {
+ return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true,
+ /* callback= */ callback, /* keyEvictedCallback= */ null);
}
@Override
@@ -18348,7 +18373,7 @@
@Override
public int getMaxRunningUsers() {
- return mUserController.mMaxRunningUsers;
+ return mUserController.getMaxRunningUsers();
}
@Override
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
similarity index 97%
rename from services/core/java/com/android/server/am/AppCompactor.java
rename to services/core/java/com/android/server/am/CachedAppOptimizer.java
index b7e2065..3ca5ebc 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -51,7 +51,7 @@
import java.util.Random;
import java.util.Set;
-public final class AppCompactor {
+public final class CachedAppOptimizer {
// Flags stored in the DeviceConfig API.
@VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
@@ -122,7 +122,7 @@
* that will wipe out the cpuset assignment for system_server threads.
* Accordingly, this is in the AMS constructor.
*/
- final ServiceThread mCompactionThread;
+ final ServiceThread mCachedAppOptimizerThread;
private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
new ArrayList<ProcessRecord>();
@@ -214,15 +214,15 @@
private int mPersistentCompactionCount;
private int mBfgsCompactionCount;
- public AppCompactor(ActivityManagerService am) {
+ public CachedAppOptimizer(ActivityManagerService am) {
mAm = am;
- mCompactionThread = new ServiceThread("CompactionThread",
+ mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
THREAD_PRIORITY_FOREGROUND, true);
mProcStateThrottle = new HashSet<>();
}
@VisibleForTesting
- AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
+ CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
this(am);
mTestCallback = callback;
}
@@ -243,7 +243,7 @@
updateFullDeltaRssThrottle();
updateProcStateThrottle();
}
- Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
+ Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
Process.THREAD_GROUP_SYSTEM);
}
@@ -258,7 +258,7 @@
@GuardedBy("mAm")
void dump(PrintWriter pw) {
- pw.println("AppCompactor settings");
+ pw.println("CachedAppOptimizer settings");
synchronized (mPhenotypeFlagLock) {
pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction);
pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
@@ -300,7 +300,7 @@
app.reqCompactAction = COMPACT_PROCESS_SOME;
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
}
@@ -309,7 +309,7 @@
app.reqCompactAction = COMPACT_PROCESS_FULL;
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
}
@@ -362,8 +362,8 @@
private void updateUseCompaction() {
mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
- if (mUseCompaction && !mCompactionThread.isAlive()) {
- mCompactionThread.start();
+ if (mUseCompaction && !mCachedAppOptimizerThread.isAlive()) {
+ mCachedAppOptimizerThread.start();
mCompactionHandler = new MemCompactionHandler();
}
}
@@ -521,7 +521,7 @@
private final class MemCompactionHandler extends Handler {
private MemCompactionHandler() {
- super(mCompactionThread.getLooper());
+ super(mCachedAppOptimizerThread.getLooper());
}
@Override
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 0fc885a..f86d6a7 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -122,9 +122,9 @@
PowerManagerInternal mLocalPowerManager;
/**
- * Service for compacting background apps.
+ * Service for optimizing resource usage from background apps.
*/
- AppCompactor mAppCompact;
+ CachedAppOptimizer mCachedAppOptimizer;
ActivityManagerConstants mConstants;
@@ -197,7 +197,7 @@
mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
mConstants = mService.mConstants;
- mAppCompact = new AppCompactor(mService);
+ mCachedAppOptimizer = new CachedAppOptimizer(mService);
mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
final int pid = msg.arg1;
@@ -224,7 +224,7 @@
}
void initSettings() {
- mAppCompact.init();
+ mCachedAppOptimizer.init();
}
/**
@@ -1978,7 +1978,7 @@
int changes = 0;
// don't compact during bootup
- if (mAppCompact.useCompaction() && mService.mBooted) {
+ if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
// Cached and prev/home compaction
if (app.curAdj != app.setAdj) {
// Perform a minor compaction when a perceptible app becomes the prev/home app
@@ -1987,26 +1987,26 @@
if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
(app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
app.curAdj == ProcessList.HOME_APP_ADJ)) {
- mAppCompact.compactAppSome(app);
+ mCachedAppOptimizer.compactAppSome(app);
} else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
|| app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
&& app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
&& app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
- mAppCompact.compactAppFull(app);
+ mCachedAppOptimizer.compactAppFull(app);
}
} else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
&& app.setAdj < ProcessList.FOREGROUND_APP_ADJ
// Because these can fire independent of oom_adj/procstate changes, we need
// to throttle the actual dispatch of these requests in addition to the
// processing of the requests. As a result, there is throttling both here
- // and in AppCompactor.
- && mAppCompact.shouldCompactPersistent(app, now)) {
- mAppCompact.compactAppPersistent(app);
+ // and in CachedAppOptimizer.
+ && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
+ mCachedAppOptimizer.compactAppPersistent(app);
} else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
&& app.getCurProcState()
== ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- && mAppCompact.shouldCompactBFGS(app, now)) {
- mAppCompact.compactAppBfgs(app);
+ && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
+ mCachedAppOptimizer.compactAppBfgs(app);
}
}
@@ -2439,7 +2439,7 @@
}
@GuardedBy("mService")
- void dumpAppCompactorSettings(PrintWriter pw) {
- mAppCompact.dump(pw);
+ void dumpCachedAppOptimizerSettings(PrintWriter pw) {
+ mCachedAppOptimizer.dump(pw);
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e11008c..b7f867d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -647,11 +647,10 @@
// Get this after boot, and won't be changed until it's rebooted, as we don't
// want some apps enabled while some apps disabled
mAppDataIsolationEnabled =
- SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+ SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
mAppDataIsolationWhitelistedApps = new ArrayList<>(
SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
-
if (sKillHandler == null) {
sKillThread = new ServiceThread(TAG + ":kill",
THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 8ae18ff6..f3a2e70 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -93,7 +93,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -161,7 +160,8 @@
* <p>Note: Current and system user (and their related profiles) are never stopped when
* switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
*/
- int mMaxRunningUsers;
+ @GuardedBy("mLock")
+ private int mMaxRunningUsers;
// Lock for internal state.
private final Object mLock = new Object();
@@ -213,7 +213,8 @@
private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
= new RemoteCallbackList<>();
- boolean mUserSwitchUiEnabled = true;
+ @GuardedBy("mLock")
+ private boolean mUserSwitchUiEnabled = true;
/**
* Currently active user switch callbacks.
@@ -246,10 +247,11 @@
/**
* In this mode, user is always stopped when switched out but locking of user data is
* postponed until total number of unlocked users in the system reaches mMaxRunningUsers.
- * Once total number of unlocked users reach mMaxRunningUsers, least recentely used user
+ * Once total number of unlocked users reach mMaxRunningUsers, least recently used user
* will be locked.
*/
- boolean mDelayUserDataLocking;
+ @GuardedBy("mLock")
+ private boolean mDelayUserDataLocking;
/**
* Keep track of last active users for mDelayUserDataLocking.
* The latest stopped user is placed in front while the least recently stopped user in back.
@@ -275,6 +277,33 @@
updateStartedUserArrayLU();
}
+ void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
+ boolean delayUserDataLocking) {
+ synchronized (mLock) {
+ mUserSwitchUiEnabled = userSwitchUiEnabled;
+ mMaxRunningUsers = maxRunningUsers;
+ mDelayUserDataLocking = delayUserDataLocking;
+ }
+ }
+
+ private boolean isUserSwitchUiEnabled() {
+ synchronized (mLock) {
+ return mUserSwitchUiEnabled;
+ }
+ }
+
+ int getMaxRunningUsers() {
+ synchronized (mLock) {
+ return mMaxRunningUsers;
+ }
+ }
+
+ private boolean isDelayUserDataLockingEnabled() {
+ synchronized (mLock) {
+ return mDelayUserDataLocking;
+ }
+ }
+
void finishUserSwitch(UserState uss) {
// This call holds the AM lock so we post to the handler.
mHandler.post(() -> {
@@ -321,7 +350,11 @@
// Owner/System user and current user can't be stopped
continue;
}
- if (stopUsersLU(userId, false, null, null) == USER_OP_SUCCESS) {
+ // allowDelayedLocking set here as stopping user is done without any explicit request
+ // from outside.
+ if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
+ /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
+ == USER_OP_SUCCESS) {
iterator.remove();
}
}
@@ -567,8 +600,8 @@
// intialize it; it should be stopped right away as it's not really a "real" user.
// TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
// callback on SystemService instead.
- stopUser(userInfo.id, /* force= */ true, /* stopUserCallback= */ null,
- /* keyEvictedCallback= */ null);
+ stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
+ /* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
return;
}
@@ -611,7 +644,8 @@
}
int restartUser(final int userId, final boolean foreground) {
- return stopUser(userId, /* force */ true, null, new KeyEvictedCallback() {
+ return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
+ /* stopUserCallback= */ null, new KeyEvictedCallback() {
@Override
public void keyEvicted(@UserIdInt int userId) {
// Post to the same handler that this callback is called from to ensure the user
@@ -621,15 +655,16 @@
});
}
- int stopUser(final int userId, final boolean force, final IStopUserCallback stopUserCallback,
- KeyEvictedCallback keyEvictedCallback) {
+ int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
throw new IllegalArgumentException("Can't stop system user " + userId);
}
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
- return stopUsersLU(userId, force, stopUserCallback, keyEvictedCallback);
+ return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
+ keyEvictedCallback);
}
}
@@ -638,7 +673,7 @@
* {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
*/
@GuardedBy("mLock")
- private int stopUsersLU(final int userId, boolean force,
+ private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
if (userId == UserHandle.USER_SYSTEM) {
return USER_OP_ERROR_IS_SYSTEM;
@@ -657,7 +692,8 @@
if (force) {
Slog.i(TAG,
"Force stop user " + userId + ". Related users will not be stopped");
- stopSingleUserLU(userId, stopUserCallback, keyEvictedCallback);
+ stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback,
+ keyEvictedCallback);
return USER_OP_SUCCESS;
}
return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
@@ -665,21 +701,64 @@
}
if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
for (int userIdToStop : usersToStop) {
- stopSingleUserLU(userIdToStop,
+ stopSingleUserLU(userIdToStop, allowDelayedLocking,
userIdToStop == userId ? stopUserCallback : null,
userIdToStop == userId ? keyEvictedCallback : null);
}
return USER_OP_SUCCESS;
}
+ /**
+ * Stops a single User. This can also trigger locking user data out depending on device's
+ * config ({@code mDelayUserDataLocking}) and arguments.
+ * User will be unlocked when
+ * - {@code mDelayUserDataLocking} is not set.
+ * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null.
+ * -
+ *
+ * @param userId User Id to stop and lock the data.
+ * @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
+ * later when number of unlocked users reaches
+ * {@code mMaxRunnngUsers}. Note that this is respected only when
+ * {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is
+ * null. Otherwise the user will be locked.
+ * @param stopUserCallback Callback to notify that user has stopped.
+ * @param keyEvictedCallback Callback to notify that user has been unlocked.
+ */
@GuardedBy("mLock")
- private void stopSingleUserLU(final int userId, final IStopUserCallback stopUserCallback,
+ private void stopSingleUserLU(final int userId, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback,
KeyEvictedCallback keyEvictedCallback) {
if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
final UserState uss = mStartedUsers.get(userId);
- if (uss == null) {
- // User is not started, nothing to do... but we do need to
- // callback if requested.
+ if (uss == null) { // User is not started
+ // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock
+ // the requested user as the client wants to stop and lock the user. On the other hand,
+ // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking
+ // is set as that means client wants to lock the user immediately.
+ // If mDelayUserDataLocking is not set, the user was already locked when it was stopped
+ // and no further action is necessary.
+ if (mDelayUserDataLocking) {
+ if (allowDelayedLocking && keyEvictedCallback != null) {
+ Slog.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it"
+ + " and lock user:" + userId, new RuntimeException());
+ allowDelayedLocking = false;
+ }
+ if (!allowDelayedLocking) {
+ if (mLastActiveUsers.remove(Integer.valueOf(userId))) {
+ // should lock the user, user is already gone
+ final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
+ if (keyEvictedCallback != null) {
+ keyEvictedCallbacks = new ArrayList<>(1);
+ keyEvictedCallbacks.add(keyEvictedCallback);
+ } else {
+ keyEvictedCallbacks = null;
+ }
+ dispatchUserLocking(userId, keyEvictedCallbacks);
+ }
+ }
+ }
+ // We do need to post the stopped callback even though user is already stopped.
if (stopUserCallback != null) {
mHandler.post(() -> {
try {
@@ -704,6 +783,7 @@
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
updateStartedUserArrayLU();
+ final boolean allowDelayyLockingCopied = allowDelayedLocking;
// Post to handler to obtain amLock
mHandler.post(() -> {
// We are going to broadcast ACTION_USER_STOPPING and then
@@ -718,7 +798,8 @@
@Override
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- mHandler.post(() -> finishUserStopping(userId, uss));
+ mHandler.post(() -> finishUserStopping(userId, uss,
+ allowDelayyLockingCopied));
}
};
@@ -734,7 +815,8 @@
}
}
- void finishUserStopping(final int userId, final UserState uss) {
+ void finishUserStopping(final int userId, final UserState uss,
+ final boolean allowDelayedLocking) {
Slog.d(TAG, "UserController event: finishUserStopping(" + userId + ")");
// On to the next.
final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
@@ -746,7 +828,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- finishUserStopped(uss);
+ finishUserStopped(uss, allowDelayedLocking);
}
});
}
@@ -773,7 +855,7 @@
Binder.getCallingPid(), userId);
}
- void finishUserStopped(UserState uss) {
+ void finishUserStopped(UserState uss, boolean allowDelayedLocking) {
final int userId = uss.mHandle.getIdentifier();
Slog.d(TAG, "UserController event: finishUserStopped(" + userId + ")");
final boolean stopped;
@@ -792,7 +874,13 @@
mStartedUsers.remove(userId);
mUserLru.remove(Integer.valueOf(userId));
updateStartedUserArrayLU();
- userIdToLock = updateUserToLockLU(userId);
+ if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
+ Slog.wtf(TAG,
+ "Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
+ + userId + " callbacks:" + keyEvictedCallbacks);
+ allowDelayedLocking = false;
+ }
+ userIdToLock = updateUserToLockLU(userId, allowDelayedLocking);
if (userIdToLock == UserHandle.USER_NULL) {
lockUser = false;
}
@@ -826,31 +914,36 @@
if (!lockUser) {
return;
}
- final int userIdToLockF = userIdToLock;
- // Evict the user's credential encryption key. Performed on FgThread to make it
- // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
- // to prevent data corruption.
- FgThread.getHandler().post(() -> {
- synchronized (mLock) {
- if (mStartedUsers.get(userIdToLockF) != null) {
- Slog.w(TAG, "User was restarted, skipping key eviction");
- return;
- }
- }
- try {
- mInjector.getStorageManager().lockUserKey(userIdToLockF);
- } catch (RemoteException re) {
- throw re.rethrowAsRuntimeException();
- }
- if (userIdToLockF == userId) {
- for (final KeyEvictedCallback callback : keyEvictedCallbacks) {
- callback.keyEvicted(userId);
- }
- }
- });
+ dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
}
}
+ private void dispatchUserLocking(@UserIdInt int userId,
+ @Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
+ // Evict the user's credential encryption key. Performed on FgThread to make it
+ // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
+ // to prevent data corruption.
+ FgThread.getHandler().post(() -> {
+ synchronized (mLock) {
+ if (mStartedUsers.get(userId) != null) {
+ Slog.w(TAG, "User was restarted, skipping key eviction");
+ return;
+ }
+ }
+ try {
+ mInjector.getStorageManager().lockUserKey(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowAsRuntimeException();
+ }
+ if (keyEvictedCallbacks == null) {
+ return;
+ }
+ for (int i = 0; i < keyEvictedCallbacks.size(); i++) {
+ keyEvictedCallbacks.get(i).keyEvicted(userId);
+ }
+ });
+ }
+
/**
* For mDelayUserDataLocking mode, storage once unlocked is kept unlocked.
* Total number of unlocked user storage is limited by mMaxRunningUsers.
@@ -861,9 +954,9 @@
* @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
*/
@GuardedBy("mLock")
- private int updateUserToLockLU(@UserIdInt int userId) {
+ private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
int userIdToLock = userId;
- if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral()
+ if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral()
&& !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
mLastActiveUsers.remove((Integer) userId); // arg should be object, not index
mLastActiveUsers.add(0, userId);
@@ -945,7 +1038,8 @@
if (userInfo.isGuest() || userInfo.isEphemeral()) {
// This is a user to be stopped.
synchronized (mLock) {
- stopUsersLU(oldUserId, true, null, null);
+ stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
+ null, null);
}
}
}
@@ -977,7 +1071,7 @@
}
final int profilesToStartSize = profilesToStart.size();
int i = 0;
- for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) {
+ for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
startUser(profilesToStart.get(i).id, /* foreground= */ false);
}
if (i < profilesToStartSize) {
@@ -1094,7 +1188,7 @@
return false;
}
- if (foreground && mUserSwitchUiEnabled) {
+ if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
mInjector.getWindowManager().startFreezingScreen(
R.anim.screen_user_exit, R.anim.screen_user_enter);
@@ -1142,9 +1236,11 @@
if (foreground) {
// Make sure the old user is no longer considering the display to be on.
mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
+ boolean userSwitchUiEnabled;
synchronized (mLock) {
mCurrentUserId = userId;
mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
+ userSwitchUiEnabled = mUserSwitchUiEnabled;
}
mInjector.updateUserConfiguration();
updateCurrentProfileIds();
@@ -1152,7 +1248,7 @@
mInjector.reportCurWakefulnessUsageEvent();
// Once the internal notion of the active user has switched, we lock the device
// with the option to show the user switcher on the keyguard.
- if (mUserSwitchUiEnabled) {
+ if (userSwitchUiEnabled) {
mInjector.getWindowManager().setSwitchingUser(true);
mInjector.getWindowManager().lockNow(null);
}
@@ -1391,10 +1487,12 @@
Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
return false;
}
+ boolean userSwitchUiEnabled;
synchronized (mLock) {
mTargetUserId = targetUserId;
+ userSwitchUiEnabled = mUserSwitchUiEnabled;
}
- if (mUserSwitchUiEnabled) {
+ if (userSwitchUiEnabled) {
UserInfo currentUserInfo = getUserInfo(currentUserId);
Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
@@ -1458,14 +1556,15 @@
}
// If running in background is disabled or mDelayUserDataLocking mode, stop the user.
boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
- oldUserId) || mDelayUserDataLocking;
+ oldUserId) || isDelayUserDataLockingEnabled();
if (!disallowRunInBg) {
return;
}
synchronized (mLock) {
if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
+ " and related users");
- stopUsersLU(oldUserId, false, null, null);
+ stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
+ null, null);
}
}
@@ -1551,7 +1650,7 @@
void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
- if (mUserSwitchUiEnabled) {
+ if (isUserSwitchUiEnabled()) {
mInjector.getWindowManager().stopFreezingScreen();
}
uss.switching = false;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cd2272a..eedeeea 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2193,8 +2193,8 @@
}
private void enforceModifyAudioRoutingPermission() {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission");
}
@@ -6427,7 +6427,7 @@
return false;
}
boolean suppress = false;
- if (resolvedStream == DEFAULT_VOL_STREAM_NO_PLAYBACK && mController != null) {
+ if (resolvedStream != AudioSystem.STREAM_MUSIC && mController != null) {
final long now = SystemClock.uptimeMillis();
if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
// ui will become visible
diff --git a/services/core/java/com/android/server/backup/PeopleBackupHelper.java b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
new file mode 100644
index 0000000..e58a051
--- /dev/null
+++ b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
@@ -0,0 +1,68 @@
+/*
+ * 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.backup;
+
+import android.app.backup.BlobBackupHelper;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.people.PeopleServiceInternal;
+
+class PeopleBackupHelper extends BlobBackupHelper {
+
+ private static final String TAG = PeopleBackupHelper.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ // Current schema of the backup state blob.
+ private static final int STATE_VERSION = 1;
+
+ // Key under which conversation infos state blob is committed to backup.
+ private static final String KEY_CONVERSATIONS = "people_conversation_infos";
+
+ private final int mUserId;
+
+ PeopleBackupHelper(int userId) {
+ super(STATE_VERSION, KEY_CONVERSATIONS);
+ mUserId = userId;
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ if (!KEY_CONVERSATIONS.equals(key)) {
+ Slog.w(TAG, "Unexpected backup key " + key);
+ return new byte[0];
+ }
+ PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+ if (DEBUG) {
+ Slog.d(TAG, "Handling backup of " + key);
+ }
+ return ps.backupConversationInfos(mUserId);
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ if (!KEY_CONVERSATIONS.equals(key)) {
+ Slog.w(TAG, "Unexpected restore key " + key);
+ return;
+ }
+ PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+ if (DEBUG) {
+ Slog.d(TAG, "Handling restore of " + key);
+ }
+ ps.restoreConversationInfos(mUserId, key, payload);
+ }
+}
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 35e8f56..1f4563b 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -55,6 +55,7 @@
private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
private static final String ACCOUNT_MANAGER_HELPER = "account_manager";
private static final String SLICES_HELPER = "slices";
+ private static final String PEOPLE_HELPER = "people";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -99,6 +100,7 @@
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
addHelper(SLICES_HELPER, new SliceBackupHelper(this));
+ addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index e1a9f3b..8dd3242 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1400,7 +1400,8 @@
if (mCurrentAuthSession.mTokenEscrow != null) {
mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow);
}
- mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+ mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(
+ Utils.getAuthenticationTypeForResult(reason));
break;
case BiometricPrompt.DISMISSED_REASON_NEGATIVE:
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 19f5358..389763b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
@@ -210,4 +211,30 @@
}
return biometricManagerCode;
}
+
+ /**
+ * Converts a {@link BiometricPrompt} dismissal reason to an authentication type at the level of
+ * granularity supported by {@link BiometricPrompt.AuthenticationResult}.
+ *
+ * @param reason The reason that the {@link BiometricPrompt} was dismissed. Must be one of:
+ * {@link BiometricPrompt#DISMISSED_REASON_CREDENTIAL_CONFIRMED},
+ * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRMED}, or
+ * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED}
+ * @return An integer representing the authentication type for {@link
+ * BiometricPrompt.AuthenticationResult}.
+ * @throws IllegalArgumentException if given an invalid dismissal reason.
+ */
+ public static @AuthenticationResultType int getAuthenticationTypeForResult(int reason) {
+ switch (reason) {
+ case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED:
+ return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL;
+
+ case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED:
+ case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED:
+ return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC;
+
+ default:
+ throw new IllegalArgumentException("Unsupported dismissal reason: " + reason);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 1b1c546..5010e46 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -16,6 +16,9 @@
package com.android.server.connectivity;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -24,6 +27,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -91,7 +95,10 @@
boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM
&& hasService()
&& mDataState == TelephonyManager.DATA_CONNECTED;
- int networkType = mServiceState.getDataNetworkType();
+ NetworkRegistrationInfo regInfo =
+ mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+ int networkType = regInfo == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN
+ : regInfo.getAccessNetworkTechnology();
if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible",
networkType, visible ? "" : "not "));
try {
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 6fa999c..04c792a 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -26,6 +26,7 @@
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
@@ -46,19 +47,18 @@
import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.Uri;
import android.os.BestClock;
import android.os.Handler;
import android.os.SystemClock;
-import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.TelephonyManager;
-import android.util.DataUnit;
import android.util.DebugUtils;
-import android.util.Pair;
import android.util.Range;
import android.util.Slog;
@@ -74,7 +74,6 @@
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
-import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -185,7 +184,6 @@
// Track information on mobile networks as they come and go.
class MultipathTracker {
final Network network;
- final int subId;
final String subscriberId;
private long mQuota;
@@ -198,13 +196,14 @@
public MultipathTracker(Network network, NetworkCapabilities nc) {
this.network = network;
this.mNetworkCapabilities = new NetworkCapabilities(nc);
- try {
- subId = Integer.parseInt(
- ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString());
- } catch (ClassCastException | NullPointerException | NumberFormatException e) {
+ NetworkSpecifier specifier = nc.getNetworkSpecifier();
+ int subId = INVALID_SUBSCRIPTION_ID;
+ if (specifier instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+ } else {
throw new IllegalStateException(String.format(
- "Can't get subId from mobile network %s (%s): %s",
- network, nc, e.getMessage()));
+ "Can't get subId from mobile network %s (%s)",
+ network, nc));
}
TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 2179518..2c41557 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -28,7 +28,7 @@
import android.content.Intent;
import android.content.res.Resources;
import android.net.NetworkSpecifier;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.net.wifi.WifiInfo;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
@@ -223,14 +223,8 @@
// name has been added to it
NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier();
int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- if (specifier instanceof StringNetworkSpecifier) {
- try {
- subId = Integer.parseInt(
- ((StringNetworkSpecifier) specifier).specifier);
- } catch (NumberFormatException e) {
- Slog.e(TAG, "NumberFormatException on "
- + ((StringNetworkSpecifier) specifier).specifier);
- }
+ if (specifier instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
}
details = mTelephonyManager.createForSubscriptionId(subId)
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 8498dcb..decb1f9 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -73,7 +73,7 @@
private static final int GLOBAL_ID = -1;
// The tolerance within which we consider something approximately equals.
- private static final float EPSILON = 0.001f;
+ private static final float EPSILON = 0.01f;
private final Object mLock = new Object();
private final Context mContext;
diff --git a/services/core/java/com/android/server/integrity/model/BitOutputStream.java b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
index b8ea041..da778aa 100644
--- a/services/core/java/com/android/server/integrity/model/BitOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
@@ -16,17 +16,25 @@
package com.android.server.integrity.model;
-import java.util.BitSet;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
/** A wrapper class for writing a stream of bits. */
public class BitOutputStream {
- private BitSet mBitSet;
- private int mIndex;
+ private static final int BUFFER_SIZE = 4 * 1024;
+ private static final int BYTE_BITS = 8;
- public BitOutputStream() {
- mBitSet = new BitSet();
- mIndex = 0;
+ private int mNextBitIndex;
+
+ private final OutputStream mOutputStream;
+ private final byte[] mBuffer;
+
+ public BitOutputStream(OutputStream outputStream) {
+ mBuffer = new byte[BUFFER_SIZE];
+ mNextBitIndex = 0;
+ mOutputStream = outputStream;
}
/**
@@ -35,15 +43,17 @@
* @param numOfBits The number of bits used to represent the value.
* @param value The value to convert to bits.
*/
- public void setNext(int numOfBits, int value) {
+ public void setNext(int numOfBits, int value) throws IOException {
if (numOfBits <= 0) {
return;
}
- int offset = 1 << (numOfBits - 1);
+
+ // optional: we can do some clever size checking to "OR" an entire segment of bits instead
+ // of setting bits one by one, but it is probably not worth it.
+ int nextBitMask = 1 << (numOfBits - 1);
while (numOfBits-- > 0) {
- mBitSet.set(mIndex, (value & offset) != 0);
- offset >>>= 1;
- mIndex++;
+ setNext((value & nextBitMask) != 0);
+ nextBitMask >>>= 1;
}
}
@@ -52,35 +62,43 @@
*
* @param value The value to set the bit to.
*/
- public void setNext(boolean value) {
- mBitSet.set(mIndex, value);
- mIndex++;
+ public void setNext(boolean value) throws IOException {
+ int byteToWrite = mNextBitIndex / BYTE_BITS;
+ if (byteToWrite == BUFFER_SIZE) {
+ mOutputStream.write(mBuffer);
+ reset();
+ byteToWrite = 0;
+ }
+ if (value) {
+ mBuffer[byteToWrite] |= 1 << (BYTE_BITS - 1 - (mNextBitIndex % BYTE_BITS));
+ }
+ mNextBitIndex++;
}
/** Set the next bit in the stream to true. */
- public void setNext() {
+ public void setNext() throws IOException {
setNext(/* value= */ true);
}
- /** Convert BitSet in big-endian to ByteArray in big-endian. */
- public byte[] toByteArray() {
- int bitSetSize = mBitSet.length();
- int numOfBytes = bitSetSize / 8;
- if (bitSetSize % 8 != 0) {
- numOfBytes++;
+ /**
+ * Flush the data written to the underlying {@link java.io.OutputStream}. Any unfinished bytes
+ * will be padded with 0.
+ */
+ public void flush() throws IOException {
+ int endByte = mNextBitIndex / BYTE_BITS;
+ if (mNextBitIndex % BYTE_BITS != 0) {
+ // If next bit is not the first bit of a byte, then mNextBitIndex / BYTE_BITS would be
+ // the byte that includes already written bits. We need to increment it so this byte
+ // gets written.
+ endByte++;
}
- byte[] bytes = new byte[numOfBytes];
- for (int i = 0; i < mBitSet.length(); i++) {
- if (mBitSet.get(i)) {
- bytes[i / 8] |= 1 << (7 - (i % 8));
- }
- }
- return bytes;
+ mOutputStream.write(mBuffer, 0, endByte);
+ reset();
}
- /** Clear the stream. */
- public void clear() {
- mBitSet.clear();
- mIndex = 0;
+ /** Reset this output stream to start state. */
+ private void reset() {
+ mNextBitIndex = 0;
+ Arrays.fill(mBuffer, (byte) 0);
}
}
diff --git a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
index f575599..0d6807a 100644
--- a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
@@ -23,31 +23,44 @@
* An output stream that tracks the total number written bytes since construction and allows
* querying this value any time during the execution.
*
- * This class is used for constructing the rule indexing.
+ * <p>This class is used for constructing the rule indexing.
*/
-public class ByteTrackedOutputStream {
+public class ByteTrackedOutputStream extends OutputStream {
- private static int sWrittenBytesCount;
- private static OutputStream sOutputStream;
+ private static final int INT_BYTES = 4;
+
+ private int mWrittenBytesCount;
+ private final OutputStream mOutputStream;
public ByteTrackedOutputStream(OutputStream outputStream) {
- sWrittenBytesCount = 0;
- sOutputStream = outputStream;
+ mWrittenBytesCount = 0;
+ mOutputStream = outputStream;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ mWrittenBytesCount += INT_BYTES;
+ mOutputStream.write(b);
}
/**
- * Writes the given bytes into the output stream provided in constructor and updates the
- * total number of written bytes.
+ * Writes the given bytes into the output stream provided in constructor and updates the total
+ * number of written bytes.
*/
+ @Override
public void write(byte[] bytes) throws IOException {
- sWrittenBytesCount += bytes.length;
- sOutputStream.write(bytes);
+ mWrittenBytesCount += bytes.length;
+ mOutputStream.write(bytes);
}
- /**
- * Returns the total number of bytes written into the output stream at the requested time.
- */
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ mWrittenBytesCount += len;
+ mOutputStream.write(b, off, len);
+ }
+
+ /** Returns the total number of bytes written into the output stream at the requested time. */
public int getWrittenBytesCount() {
- return sWrittenBytesCount;
+ return mWrittenBytesCount;
}
}
diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
index 52df89870..d21febb 100644
--- a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
+++ b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
@@ -18,9 +18,9 @@
/** A helper class containing special indexing file constants. */
public final class IndexingFileConstants {
- // The parsing time seems acceptable for this block size based on the tests in
- // go/ic-rule-file-format.
- public static final int INDEXING_BLOCK_SIZE = 100;
+ // We empirically experimented with different block sizes and identified that 250 is in the
+ // optimal range of efficient computation.
+ public static final int INDEXING_BLOCK_SIZE = 250;
public static final String START_INDEXING_KEY = "START_KEY";
public static final String END_INDEXING_KEY = "END_KEY";
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index 8f53be7..6afadba 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -97,42 +97,38 @@
ruleFileByteTrackedOutputStream);
LinkedHashMap<String, Integer> unindexedRulesIndexes =
serializeRuleList(
- indexedRules.get(NOT_INDEXED),
- ruleFileByteTrackedOutputStream);
+ indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
// Serialize their indexes.
- BitOutputStream indexingBitOutputStream = new BitOutputStream();
- serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */true);
- serializeIndexGroup(appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */
- true);
- serializeIndexGroup(unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */
- false);
- // TODO(b/147609625): This dummy bit is set for fixing the padding issue. Remove when
- // the issue is fixed and correct the tests that does this padding too.
- indexingBitOutputStream.setNext();
- indexingFileOutputStream.write(indexingBitOutputStream.toByteArray());
+ BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
+ serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
+ serializeIndexGroup(
+ appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
+ serializeIndexGroup(
+ unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
+ indexingBitOutputStream.flush();
} catch (Exception e) {
throw new RuleSerializeException(e.getMessage(), e);
}
}
- private void serializeRuleFileMetadata(Optional<Integer> formatVersion,
- ByteTrackedOutputStream outputStream)
+ private void serializeRuleFileMetadata(
+ Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
throws IOException {
int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
- BitOutputStream bitOutputStream = new BitOutputStream();
+ BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
- outputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
}
private LinkedHashMap<String, Integer> serializeRuleList(
Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
throws IOException {
- Preconditions.checkArgument(rulesMap != null,
- "serializeRuleList should never be called with null rule list.");
+ Preconditions.checkArgument(
+ rulesMap != null, "serializeRuleList should never be called with null rule list.");
- BitOutputStream bitOutputStream = new BitOutputStream();
+ BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
@@ -145,9 +141,8 @@
}
for (Rule rule : rulesMap.get(key)) {
- bitOutputStream.clear();
serializeRule(rule, bitOutputStream);
- outputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
indexTracker++;
}
}
@@ -156,7 +151,7 @@
return indexMapping;
}
- private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
+ private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
if (rule == null) {
throw new IllegalArgumentException("Null rule can not be serialized");
}
@@ -171,7 +166,8 @@
bitOutputStream.setNext();
}
- private void serializeFormula(Formula formula, BitOutputStream bitOutputStream) {
+ private void serializeFormula(Formula formula, BitOutputStream bitOutputStream)
+ throws IOException {
if (formula instanceof AtomicFormula) {
serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
} else if (formula instanceof CompoundFormula) {
@@ -183,7 +179,7 @@
}
private void serializeCompoundFormula(
- CompoundFormula compoundFormula, BitOutputStream bitOutputStream) {
+ CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
if (compoundFormula == null) {
throw new IllegalArgumentException("Null compound formula can not be serialized");
}
@@ -197,7 +193,7 @@
}
private void serializeAtomicFormula(
- AtomicFormula atomicFormula, BitOutputStream bitOutputStream) {
+ AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
if (atomicFormula == null) {
throw new IllegalArgumentException("Null atomic formula can not be serialized");
}
@@ -231,11 +227,10 @@
private void serializeIndexGroup(
LinkedHashMap<String, Integer> indexes,
BitOutputStream bitOutputStream,
- boolean isIndexed) {
-
+ boolean isIndexed)
+ throws IOException {
// Output the starting location of this indexing group.
- serializeStringValue(
- START_INDEXING_KEY, /* isHashedValue= */false, bitOutputStream);
+ serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
// If the group is indexed, output the locations of the indexes.
@@ -243,8 +238,8 @@
for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
if (!entry.getKey().equals(START_INDEXING_KEY)
&& !entry.getKey().equals(END_INDEXING_KEY)) {
- serializeStringValue(entry.getKey(), /* isHashedValue= */false,
- bitOutputStream);
+ serializeStringValue(
+ entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
serializeIntValue(entry.getValue(), bitOutputStream);
}
}
@@ -256,7 +251,8 @@
}
private void serializeStringValue(
- String value, boolean isHashedValue, BitOutputStream bitOutputStream) {
+ String value, boolean isHashedValue, BitOutputStream bitOutputStream)
+ throws IOException {
if (value == null) {
throw new IllegalArgumentException("String value can not be null.");
}
@@ -269,11 +265,12 @@
}
}
- private void serializeIntValue(int value, BitOutputStream bitOutputStream) {
+ private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
bitOutputStream.setNext(/* numOfBits= */ 32, value);
}
- private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream) {
+ private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
+ throws IOException {
bitOutputStream.setNext(value);
}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 5191833..b67335a 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -306,14 +306,17 @@
}
} else if (state == BluetoothProfile.STATE_DISCONNECTING
|| state == BluetoothProfile.STATE_DISCONNECTED) {
- btRoute.connectedProfiles.delete(profile);
- if (btRoute.connectedProfiles.size() == 0) {
- mBluetoothRoutes.remove(device.getAddress());
- if (mActiveDevice != null
- && TextUtils.equals(mActiveDevice.getAddress(), device.getAddress())) {
- mActiveDevice = null;
+ if (btRoute != null) {
+ btRoute.connectedProfiles.delete(profile);
+ if (btRoute.connectedProfiles.size() == 0) {
+ mBluetoothRoutes.remove(device.getAddress());
+ if (mActiveDevice != null
+ && TextUtils.equals(mActiveDevice.getAddress(),
+ device.getAddress())) {
+ mActiveDevice = null;
+ }
+ notifyBluetoothRoutesUpdated();
}
- notifyBluetoothRoutesUpdated();
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index df115d0..1905571 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -135,6 +135,7 @@
private int mMaxVolume = 0;
private int mCurrentVolume = 0;
private int mOptimisticVolume = -1;
+ private String mVolumeControlId;
// End volume handling fields
private boolean mIsActive = false;
@@ -714,7 +715,7 @@
if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
- mAudioAttrs);
+ mAudioAttrs, mVolumeControlId);
}
volumeType = mVolumeType;
attributes = mAudioAttrs;
@@ -723,7 +724,7 @@
int max = mAudioManager.getStreamMaxVolume(stream);
int current = mAudioManager.getStreamVolume(stream);
return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
- current, attributes);
+ current, attributes, null);
}
private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@@ -886,6 +887,7 @@
synchronized (mLock) {
typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+ mVolumeControlId = null;
if (attributes != null) {
mAudioAttrs = attributes;
} else {
@@ -904,13 +906,15 @@
}
@Override
- public void setPlaybackToRemote(int control, int max) throws RemoteException {
+ public void setPlaybackToRemote(int control, int max, String controlId)
+ throws RemoteException {
boolean typeChanged;
synchronized (mLock) {
typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
mVolumeControlType = control;
mMaxVolume = max;
+ mVolumeControlId = controlId;
}
if (typeChanged) {
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index d8a4655..c518614 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -161,7 +161,7 @@
import android.net.NetworkState;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.net.TrafficStats;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
@@ -2877,17 +2877,6 @@
}
@Override
- public void onTetheringChanged(String iface, boolean tethering) {
- // No need to enforce permission because setRestrictBackground() will do it.
- synchronized (mUidRulesFirstLock) {
- if (mRestrictBackground && tethering) {
- Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
- setRestrictBackground(false);
- }
- }
- }
-
- @Override
public void setRestrictBackground(boolean restrictBackground) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setRestrictBackground");
try {
@@ -4522,7 +4511,11 @@
}
case MSG_STATS_PROVIDER_LIMIT_REACHED: {
mNetworkStats.forceUpdate();
+
synchronized (mNetworkPoliciesSecondLock) {
+ // Some providers might hit the limit reached event prior to others. Thus,
+ // re-calculate and update interface quota for every provider is needed.
+ updateNetworkRulesNL();
updateNetworkEnabledNL();
updateNotificationsNL();
}
@@ -4530,16 +4523,21 @@
}
case MSG_LIMIT_REACHED: {
final String iface = (String) msg.obj;
+ synchronized (mNetworkPoliciesSecondLock) {
+ // fast return if not needed.
+ if (!mMeteredIfaces.contains(iface)) {
+ return true;
+ }
+ }
+
+ // force stats update to make sure the service have the numbers that caused
+ // alert to trigger.
+ mNetworkStats.forceUpdate();
synchronized (mNetworkPoliciesSecondLock) {
- if (mMeteredIfaces.contains(iface)) {
- // force stats update to make sure we have
- // numbers that caused alert to trigger.
- mNetworkStats.forceUpdate();
-
- updateNetworkEnabledNL();
- updateNotificationsNL();
- }
+ updateNetworkRulesNL();
+ updateNetworkEnabledNL();
+ updateNotificationsNL();
}
return true;
}
@@ -5283,16 +5281,12 @@
}
private int parseSubId(NetworkState state) {
- // TODO: moved to using a legitimate NetworkSpecifier instead of string parsing
int subId = INVALID_SUBSCRIPTION_ID;
if (state != null && state.networkCapabilities != null
&& state.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
NetworkSpecifier spec = state.networkCapabilities.getNetworkSpecifier();
- if (spec instanceof StringNetworkSpecifier) {
- try {
- subId = Integer.parseInt(((StringNetworkSpecifier) spec).specifier);
- } catch (NumberFormatException e) {
- }
+ if (spec instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
}
}
return subId;
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index da2ca9b..eaf120e 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -42,9 +42,10 @@
return null;
}
- record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(),
+ record.updateNotificationChannel(mConfig.getConversationNotificationChannel(
+ record.sbn.getPackageName(),
record.sbn.getUid(), record.getChannel().getId(),
- record.getNotification().getShortcutId(), false));
+ record.getNotification().getShortcutId(), true, false));
return null;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0e08033..1d49364 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -472,7 +472,8 @@
private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE = "value";
private RankingHelper mRankingHelper;
- private PreferencesHelper mPreferencesHelper;
+ @VisibleForTesting
+ PreferencesHelper mPreferencesHelper;
private final UserProfiles mUserProfiles = new UserProfiles();
private NotificationListeners mListeners;
@@ -3054,23 +3055,24 @@
public NotificationChannel getNotificationChannel(String callingPkg, int userId,
String targetPkg, String channelId) {
return getConversationNotificationChannel(
- callingPkg, userId, targetPkg, channelId, null);
+ callingPkg, userId, targetPkg, channelId, true, null);
}
@Override
public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId,
- String targetPkg, String channelId, String conversationId) {
+ String targetPkg, String channelId, boolean returnParentIfNoConversationChannel,
+ String conversationId) {
if (canNotifyAsPackage(callingPkg, targetPkg, userId)
- || isCallingUidSystem()) {
+ || isCallerIsSystemOrSystemUi()) {
int targetUid = -1;
try {
targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
} catch (NameNotFoundException e) {
/* ignore */
}
- return mPreferencesHelper.getNotificationChannel(
+ return mPreferencesHelper.getConversationNotificationChannel(
targetPkg, targetUid, channelId, conversationId,
- false /* includeDeleted */);
+ returnParentIfNoConversationChannel, false /* includeDeleted */);
}
throw new SecurityException("Pkg " + callingPkg
+ " cannot read channels for " + targetPkg + " in " + userId);
@@ -5255,9 +5257,15 @@
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
- final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
- notificationUid, channelId, notification.getShortcutId(),
- false /* includeDeleted */);
+ // TODO: flag this behavior
+ String shortcutId = notification.getShortcutId();
+ if (shortcutId == null
+ && notification.getNotificationStyle() == Notification.MessagingStyle.class) {
+ shortcutId = id + tag + NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+ }
+ final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
+ pkg, notificationUid, channelId, shortcutId,
+ true /* parent ok */, false /* includeDeleted */);
if (channel == null) {
final String noChannelStr = "No Channel found for "
+ "pkg=" + pkg
@@ -7893,6 +7901,14 @@
return isUidSystemOrPhone(Binder.getCallingUid());
}
+ private boolean isCallerIsSystemOrSystemUi() {
+ if (isCallerSystemOrPhone()) {
+ return true;
+ }
+ return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+ == PERMISSION_GRANTED;
+ }
+
private void checkCallerIsSystemOrShell() {
int callingUid = Binder.getCallingUid();
if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 92fcb7f..185d75c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -858,12 +858,13 @@
public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted) {
Objects.requireNonNull(pkg);
- return getNotificationChannel(pkg, uid, channelId, null, includeDeleted);
+ return getConversationNotificationChannel(pkg, uid, channelId, null, true, includeDeleted);
}
@Override
- public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
- String conversationId, boolean includeDeleted) {
+ public NotificationChannel getConversationNotificationChannel(String pkg, int uid,
+ String channelId, String conversationId, boolean returnParentIfNoConversationChannel,
+ boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
@@ -873,16 +874,19 @@
if (channelId == null) {
channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
}
- if (conversationId == null) {
+ NotificationChannel channel = null;
+ if (conversationId != null) {
+ // look for an automatically created conversation specific channel
+ channel = findConversationChannel(r, channelId, conversationId, includeDeleted);
+ }
+ if (channel == null && returnParentIfNoConversationChannel) {
+ // look for it just based on its id
final NotificationChannel nc = r.channels.get(channelId);
if (nc != null && (includeDeleted || !nc.isDeleted())) {
return nc;
}
- } else {
- // look for an automatically created conversation specific channel
- return findConversationChannel(r, channelId, conversationId, includeDeleted);
}
- return null;
+ return channel;
}
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 4b044c1..7e98be7 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -45,8 +45,9 @@
boolean fromUser);
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted);
- NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
- String conversationId, boolean includeDeleted);
+ NotificationChannel getConversationNotificationChannel(String pkg, int uid, String channelId,
+ String conversationId, boolean returnParentIfNoConversationChannel,
+ boolean includeDeleted);
void deleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannels(String pkg, int uid);
diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java
index 31d30362..c5b868f 100644
--- a/services/core/java/com/android/server/people/PeopleServiceInternal.java
+++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java
@@ -16,9 +16,25 @@
package com.android.server.people;
+import android.annotation.NonNull;
import android.service.appprediction.IPredictionService;
/**
* @hide Only for use within the system server.
*/
-public abstract class PeopleServiceInternal extends IPredictionService.Stub {}
+public abstract class PeopleServiceInternal extends IPredictionService.Stub {
+
+ /**
+ * The number conversation infos will be dynamic, based on the currently installed apps on the
+ * device. All of which should be combined into a single blob to be backed up.
+ */
+ public abstract byte[] backupConversationInfos(@NonNull int userId);
+
+ /**
+ * Multiple conversation infos may exist in the restore payload, child classes are required to
+ * manage the restoration based on how individual conversation infos were originally combined
+ * during backup.
+ */
+ public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key,
+ @NonNull byte[] payload);
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 159b4e4..410017a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -190,6 +190,7 @@
import android.content.pm.ServiceInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
@@ -20021,8 +20022,9 @@
String initiatingPackageName;
String originatingPackageName;
+ final InstallSource installSource;
synchronized (mLock) {
- final InstallSource installSource = getInstallSourceLocked(packageName, callingUid);
+ installSource = getInstallSourceLocked(packageName, callingUid);
if (installSource == null) {
return null;
}
@@ -20036,9 +20038,16 @@
}
if (installSource.isInitiatingPackageUninstalled) {
- // TODO(b/146555198) Allow the app itself to see the info
- // (at least for non-instant apps)
- initiatingPackageName = null;
+ // We can't check visibility in the usual way, since the initiating package is no
+ // longer present. So we apply simpler rules to whether to expose the info:
+ // 1. Instant apps can't see it.
+ // 2. Otherwise only the installed app itself can see it.
+ final boolean isInstantApp = getInstantAppPackageName(callingUid) != null;
+ if (!isInstantApp && isCallerSameApp(packageName, callingUid)) {
+ initiatingPackageName = installSource.initiatingPackageName;
+ } else {
+ initiatingPackageName = null;
+ }
} else {
// All installSource strings are interned, so == is ok here
if (installSource.initiatingPackageName == installSource.installerPackageName) {
@@ -20063,13 +20072,27 @@
}
}
+ // Remaining work can safely be done outside the lock. (Note that installSource is
+ // immutable so it's ok to carry on reading from it.)
+
if (originatingPackageName != null && mContext.checkCallingOrSelfPermission(
Manifest.permission.INSTALL_PACKAGES) != PackageManager.PERMISSION_GRANTED) {
originatingPackageName = null;
}
- return new InstallSourceInfo(initiatingPackageName, originatingPackageName,
- installerPackageName);
+ // If you can see the initiatingPackageName, and we have valid signing info for it,
+ // then we let you see that too.
+ final SigningInfo initiatingPackageSigningInfo;
+ final PackageSignatures signatures = installSource.initiatingPackageSignatures;
+ if (initiatingPackageName != null && signatures != null
+ && signatures.mSigningDetails != SigningDetails.UNKNOWN) {
+ initiatingPackageSigningInfo = new SigningInfo(signatures.mSigningDetails);
+ } else {
+ initiatingPackageSigningInfo = null;
+ }
+
+ return new InstallSourceInfo(initiatingPackageName, initiatingPackageSigningInfo,
+ originatingPackageName, installerPackageName);
}
@GuardedBy("mLock")
@@ -22267,6 +22290,13 @@
mPermissionManager.onNewUserCreated(userId);
}
+ boolean readPermissionStateForUser(@UserIdInt int userId) {
+ synchronized (mPackages) {
+ mSettings.readPermissionStateForUserSyncLPr(userId);
+ return mSettings.areDefaultRuntimePermissionsGrantedLPr(userId);
+ }
+ }
+
@Override
public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
mContext.enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3e665c3..4f18cb4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3125,6 +3125,10 @@
return true;
}
+ void readPermissionStateForUserSyncLPr(@UserIdInt int userId) {
+ mRuntimePermissionsPersistence.readStateForUserSyncLPr(userId);
+ }
+
void applyDefaultPreferredAppsLPw(int userId) {
// First pull data from any pre-installed apps.
final PackageManagerInternal pmInternal =
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 2265d01..7888d1f 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -49,6 +49,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -570,18 +571,17 @@
} else {
params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
}
- int apkSessionId = mPi.createSession(
- params, originalSession.getInstallerPackageName(),
- 0 /* UserHandle.SYSTEM */);
- PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
-
try {
+ int apkSessionId = mPi.createSession(
+ params, originalSession.getInstallerPackageName(),
+ 0 /* UserHandle.SYSTEM */);
+ PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
apkSession.open();
for (String apkFilePath : apkFilePaths) {
File apkFile = new File(apkFilePath);
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
ParcelFileDescriptor.MODE_READ_ONLY);
- long sizeBytes = pfd.getStatSize();
+ long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize();
if (sizeBytes < 0) {
Slog.e(TAG, "Unable to get size of: " + apkFilePath);
throw new PackageManagerException(errorCode,
@@ -589,11 +589,11 @@
}
apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
}
- } catch (IOException e) {
+ return apkSession;
+ } catch (IOException | ParcelableException e) {
Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
- throw new PackageManagerException(errorCode, "Failed to write APK session", e);
+ throw new PackageManagerException(errorCode, "Failed to create/write APK session", e);
}
- return apkSession;
}
/**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e5d5b57..6ee8648 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -492,6 +492,10 @@
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mUms.cleanupPartialUsers();
+
+ if (mUms.mPm.isDeviceUpgrading()) {
+ mUms.cleanupPreCreatedUsers();
+ }
}
}
@@ -617,6 +621,33 @@
}
}
+ /**
+ * Removes any pre-created users from the system. Should be invoked after OTAs, to ensure
+ * pre-created users are not stale. New pre-created pool can be re-created after the update.
+ */
+ void cleanupPreCreatedUsers() {
+ final ArrayList<UserInfo> preCreatedUsers;
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ preCreatedUsers = new ArrayList<>(userSize);
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i).info;
+ if (ui.preCreated) {
+ preCreatedUsers.add(ui);
+ addRemovingUserIdLocked(ui.id);
+ ui.flags |= UserInfo.FLAG_DISABLED;
+ ui.partial = true;
+ }
+ }
+ }
+ final int preCreatedSize = preCreatedUsers.size();
+ for (int i = 0; i < preCreatedSize; i++) {
+ UserInfo ui = preCreatedUsers.get(i);
+ Slog.i(LOG_TAG, "Removing pre-created user " + ui.id);
+ removeUserState(ui.id);
+ }
+ }
+
@Override
public String getUserAccount(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("get user account");
@@ -3078,7 +3109,6 @@
@NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId,
boolean preCreate, @Nullable String[] disallowedPackages,
@NonNull TimingsTraceAndSlog t) {
-
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
if (userTypeDetails == null) {
Slog.e(LOG_TAG, "Cannot create user of invalid user type: " + userType);
@@ -3254,9 +3284,9 @@
mBaseUserRestrictions.append(userId, restrictions);
}
- t.traceBegin("PM.onNewUserCreated");
+ t.traceBegin("PM.onNewUserCreated-" + userId);
mPm.onNewUserCreated(userId);
-
+ t.traceEnd();
if (preCreate) {
// Must start user (which will be stopped right away, through
// UserController.finishUserUnlockedCompleted) so services can properly
@@ -3323,11 +3353,16 @@
preCreatedUser.preCreated = false;
preCreatedUser.creationTime = getCreationTime();
- dispatchUserAddedIntent(preCreatedUser);
synchronized (mPackagesLock) {
writeUserLP(preCreatedUserData);
writeUserListLP();
}
+ updateUserIds();
+ if (!mPm.readPermissionStateForUser(preCreatedUser.id)) {
+ // Could not read the existing permissions, re-grant them.
+ mPm.onNewUserCreated(preCreatedUser.id);
+ }
+ dispatchUserAddedIntent(preCreatedUser);
return preCreatedUser;
}
@@ -4027,14 +4062,16 @@
synchronized (mUsersLock) {
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).info.partial) {
+ UserInfo userInfo = mUsers.valueAt(i).info;
+ if (!userInfo.partial && !userInfo.preCreated) {
num++;
}
}
final int[] newUsers = new int[num];
int n = 0;
for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).info.partial) {
+ UserInfo userInfo = mUsers.valueAt(i).info;
+ if (!userInfo.partial && !userInfo.preCreated) {
newUsers[n++] = mUsers.keyAt(i);
}
}
@@ -4095,7 +4132,10 @@
* recycled.
*/
void reconcileUsers(String volumeUuid) {
- mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */));
+ mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(
+ /* excludePartial= */ true,
+ /* excludeDying= */ true,
+ /* excludePreCreated= */ false));
}
/**
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 815f7b4..89030ed 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -199,13 +199,31 @@
);
/**
- * Special user restrictions that are applied globally when set by the profile owner of a
- * managed profile that was created during the device provisioning flow.
+ * Special user restrictions that profile owner of an organization-owned managed profile can
+ * set on the parent profile instance to apply them globally.
*/
private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
Sets.newArraySet(
UserManager.DISALLOW_CONFIG_DATE_TIME,
- UserManager.DISALLOW_CAMERA
+ UserManager.DISALLOW_CAMERA,
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_BLUETOOTH,
+ UserManager.DISALLOW_BLUETOOTH_SHARING,
+ UserManager.DISALLOW_CONFIG_BLUETOOTH,
+ UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+ UserManager.DISALLOW_CONFIG_LOCATION,
+ UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+ UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+ UserManager.DISALLOW_CONFIG_TETHERING,
+ UserManager.DISALLOW_CONFIG_WIFI,
+ UserManager.DISALLOW_CONTENT_CAPTURE,
+ UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+ UserManager.DISALLOW_DATA_ROAMING,
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
+ UserManager.DISALLOW_SAFE_BOOT,
+ UserManager.DISALLOW_SHARE_LOCATION,
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_USB_FILE_TRANSFER
);
/**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d468cd9..0411e29 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -55,6 +55,8 @@
import android.app.ActivityManager;
import android.app.ApplicationPackageManager;
import android.app.IActivityManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -107,6 +109,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.IPlatformCompat;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.RoSystemProperties;
@@ -224,6 +227,8 @@
private final Handler mHandler;
private final Context mContext;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
/** Internal storage for permissions and related settings */
@GuardedBy("mLock")
@@ -1824,6 +1829,14 @@
return true;
}
+ /**
+ * This change makes it so that apps are told to show rationale for asking for background
+ * location access every time they request.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L;
+
@Override
public boolean shouldShowRequestPermissionRationale(String permName,
String packageName, int userId) {
@@ -1862,6 +1875,16 @@
return false;
}
+ try {
+ if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+ && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID,
+ packageName, userId)) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to check if compatibility change is enabled.", e);
+ }
+
return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
}
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 9f592b8..7d0072a 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -172,6 +172,13 @@
@Nullable public final String mInstallerPackageName;
/**
+ * This array holds all of the rollback tokens associated with package sessions included in
+ * this rollback.
+ */
+ @GuardedBy("mLock")
+ private final IntArray mTokens = new IntArray();
+
+ /**
* Constructs a new, empty Rollback instance.
*
* @param rollbackId the id of the rollback.
@@ -766,6 +773,26 @@
}
}
+ /**
+ * Adds a rollback token to be associated with this rollback. This may be used to
+ * identify which rollback should be removed in case {@link PackageManager} sends an
+ * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
+ */
+ void addToken(int token) {
+ synchronized (mLock) {
+ mTokens.add(token);
+ }
+ }
+
+ /**
+ * Returns true if this rollback is associated with the provided {@code token}.
+ */
+ boolean hasToken(int token) {
+ synchronized (mLock) {
+ return mTokens.indexOf(token) != -1;
+ }
+ }
+
static String rollbackStateToString(@RollbackState int state) {
switch (state) {
case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 8f8a5c4..eefcde6 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -235,12 +235,17 @@
Slog.v(TAG, "broadcast=ACTION_CANCEL_ENABLE_ROLLBACK token=" + token);
}
synchronized (mLock) {
- for (NewRollback rollback : mNewRollbacks) {
- if (rollback.hasToken(token)) {
- rollback.setCancelled();
- return;
+ NewRollback found = null;
+ for (NewRollback newRollback : mNewRollbacks) {
+ if (newRollback.rollback.hasToken(token)) {
+ found = newRollback;
+ break;
}
}
+ if (found != null) {
+ mNewRollbacks.remove(found);
+ found.rollback.delete(mAppDataRollbackHelper);
+ }
}
}
}
@@ -433,10 +438,14 @@
rollback.delete(mAppDataRollbackHelper);
}
}
- for (NewRollback newRollback : mNewRollbacks) {
+ Iterator<NewRollback> iter2 = mNewRollbacks.iterator();
+ while (iter2.hasNext()) {
+ NewRollback newRollback = iter2.next();
if (newRollback.rollback.includesPackage(packageName)) {
- newRollback.setCancelled();
+ iter2.remove();
+ newRollback.rollback.delete(mAppDataRollbackHelper);
}
+
}
}
}
@@ -798,7 +807,7 @@
mNewRollbacks.add(newRollback);
}
}
- newRollback.addToken(token);
+ newRollback.rollback.addToken(token);
return enableRollbackForPackageSession(newRollback.rollback, packageSession);
}
@@ -1220,12 +1229,6 @@
Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId());
}
- if (newRollback.isCancelled()) {
- Slog.e(TAG, "Rollback has been cancelled by PackageManager");
- rollback.delete(mAppDataRollbackHelper);
- return null;
- }
-
// We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is
// equal to the number of sessions we are installing, to ensure we didn't skip enabling
// of any sessions. If we successfully enable an apex, then we can assume we enabled
@@ -1335,22 +1338,12 @@
public final Rollback rollback;
/**
- * This array holds all of the rollback tokens associated with package sessions included in
- * this rollback.
- */
- @GuardedBy("mNewRollbackLock")
- private final IntArray mTokens = new IntArray();
-
- /**
* Session ids for all packages in the install. For multi-package sessions, this is the list
* of child session ids. For normal sessions, this list is a single element with the normal
* session id.
*/
private final int[] mPackageSessionIds;
- @GuardedBy("mNewRollbackLock")
- private boolean mIsCancelled = false;
-
/**
* The number of sessions in the install which are notified with success by
* {@link PackageInstaller.SessionCallback#onFinished(int, boolean)}.
@@ -1367,52 +1360,6 @@
}
/**
- * Adds a rollback token to be associated with this NewRollback. This may be used to
- * identify which rollback should be cancelled in case {@link PackageManager} sends an
- * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
- */
- void addToken(int token) {
- synchronized (mNewRollbackLock) {
- mTokens.add(token);
- }
- }
-
- /**
- * Returns true if this NewRollback is associated with the provided {@code token}.
- */
- boolean hasToken(int token) {
- synchronized (mNewRollbackLock) {
- return mTokens.indexOf(token) != -1;
- }
- }
-
- /**
- * Returns true if this NewRollback has been cancelled.
- *
- * <p>Rollback could be invalidated and cancelled if RollbackManager receives
- * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} from {@link PackageManager}.
- *
- * <p>The main underlying assumption here is that if enabling the rollback times out, then
- * {@link PackageManager} will NOT send
- * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)} before it broadcasts
- * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK}.
- */
- boolean isCancelled() {
- synchronized (mNewRollbackLock) {
- return mIsCancelled;
- }
- }
-
- /**
- * Sets this NewRollback to be marked as cancelled.
- */
- void setCancelled() {
- synchronized (mNewRollbackLock) {
- mIsCancelled = true;
- }
- }
-
- /**
* Returns true if this NewRollback contains the provided {@code packageSessionId}.
*/
boolean containsSessionId(int packageSessionId) {
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index a0ef8cf..b9ef7b3 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -280,7 +280,6 @@
.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
"");
- mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
} else if (sessionInfo.isStagedSessionFailed()
&& markStagedSessionHandled(rollbackId)) {
logEvent(moduleMetadataPackage,
@@ -291,6 +290,11 @@
}
}
}
+
+ // Wait for all pending staged sessions to get handled before rebooting.
+ if (isPendingStagedSessionsEmpty()) {
+ mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
+ }
}
/**
@@ -303,6 +307,16 @@
}
}
+ /**
+ * Returns {@code true} if all pending staged rollback sessions were marked as handled,
+ * {@code false} if there is any left.
+ */
+ private boolean isPendingStagedSessionsEmpty() {
+ synchronized (mPendingStagedRollbackIds) {
+ return mPendingStagedRollbackIds.isEmpty();
+ }
+ }
+
private void saveLastStagedRollbackId(int stagedRollbackId) {
try {
FileOutputStream fos = new FileOutputStream(mLastStagedRollbackIdFile);
@@ -414,6 +428,9 @@
reasonToLog, failedPackageToLog);
}
} else {
+ if (rollback.isStaged()) {
+ markStagedSessionHandled(rollback.getRollbackId());
+ }
logEvent(logPackage,
StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
reasonToLog, failedPackageToLog);
@@ -431,6 +448,16 @@
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
+ // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
+ // pending staged rollbacks are handled.
+ synchronized (mPendingStagedRollbackIds) {
+ for (RollbackInfo rollback : rollbacks) {
+ if (rollback.isStaged()) {
+ mPendingStagedRollbackIds.add(rollback.getRollbackId());
+ }
+ }
+ }
+
for (RollbackInfo rollback : rollbacks) {
VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
new file mode 100644
index 0000000..b19e2ed
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+
+/**
+ * A factory for creating instances of {@link ISoundTriggerHw}.
+ *
+ * @hide
+ */
+public interface HalFactory {
+ /**
+ * @return An instance of {@link ISoundTriggerHw}.
+ */
+ ISoundTriggerHw create();
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index 9d51b65..9f4b09a 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -78,18 +78,17 @@
}
/**
- * Most generic constructor - gets an array of HAL driver instances.
+ * Constructor - gets an array of HAL driver factories.
*/
- public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices,
+ public SoundTriggerMiddlewareImpl(@NonNull HalFactory[] halFactories,
@NonNull AudioSessionProvider audioSessionProvider) {
- List<SoundTriggerModule> modules = new ArrayList<>(halServices.length);
+ List<SoundTriggerModule> modules = new ArrayList<>(halFactories.length);
- for (int i = 0; i < halServices.length; ++i) {
- ISoundTriggerHw service = halServices[i];
+ for (int i = 0; i < halFactories.length; ++i) {
try {
- modules.add(new SoundTriggerModule(service, audioSessionProvider));
+ modules.add(new SoundTriggerModule(halFactories[i], audioSessionProvider));
} catch (Exception e) {
- Log.e(TAG, "Failed to a SoundTriggerModule instance", e);
+ Log.e(TAG, "Failed to add a SoundTriggerModule instance", e);
}
}
@@ -97,11 +96,11 @@
}
/**
- * Convenience constructor - gets a single HAL driver instance.
+ * Convenience constructor - gets a single HAL factory.
*/
- public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService,
+ public SoundTriggerMiddlewareImpl(@NonNull HalFactory factory,
@NonNull AudioSessionProvider audioSessionProvider) {
- this(new ISoundTriggerHw[]{halService}, audioSessionProvider);
+ this(new HalFactory[]{factory}, audioSessionProvider);
}
@Override
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 5a06a2c..12f9fd9 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -113,9 +113,9 @@
public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
static private final String TAG = "SoundTriggerMiddlewareService";
- final ISoundTriggerMiddlewareService mDelegate;
- final Context mContext;
- Set<Integer> mModuleHandles;
+ private final ISoundTriggerMiddlewareService mDelegate;
+ private final Context mContext;
+ private Set<Integer> mModuleHandles;
/**
* Constructor for internal use only. Could be exposed for testing purposes in the future.
@@ -280,7 +280,7 @@
}
/** Activity state. */
- public Activity activityState = Activity.LOADED;
+ Activity activityState = Activity.LOADED;
/**
* A map of known parameter support. A missing key means we don't know yet whether the
@@ -294,7 +294,7 @@
*
* @param modelParam The parameter key.
*/
- public void checkSupported(int modelParam) {
+ void checkSupported(int modelParam) {
if (!parameterSupport.containsKey(modelParam)) {
throw new IllegalStateException("Parameter has not been checked for support.");
}
@@ -311,7 +311,7 @@
* @param modelParam The parameter key.
* @param value The value.
*/
- public void checkSupported(int modelParam, int value) {
+ void checkSupported(int modelParam, int value) {
if (!parameterSupport.containsKey(modelParam)) {
throw new IllegalStateException("Parameter has not been checked for support.");
}
@@ -329,7 +329,7 @@
* @param modelParam The parameter key.
* @param range The parameter value range, or null if not supported.
*/
- public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+ void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
parameterSupport.put(modelParam, range);
}
}
@@ -338,27 +338,26 @@
* Entry-point to this module: exposes the module as a {@link SystemService}.
*/
public static final class Lifecycle extends SystemService {
- private SoundTriggerMiddlewareService mService;
-
public Lifecycle(Context context) {
super(context);
}
@Override
public void onStart() {
- ISoundTriggerHw[] services;
- try {
- services = new ISoundTriggerHw[]{ISoundTriggerHw.getService(true)};
- Log.d(TAG, "Connected to default ISoundTriggerHw");
- } catch (Exception e) {
- Log.e(TAG, "Failed to connect to default ISoundTriggerHw", e);
- services = new ISoundTriggerHw[0];
- }
+ HalFactory[] factories = new HalFactory[]{() -> {
+ try {
+ Log.d(TAG, "Connecting to default ISoundTriggerHw");
+ return ISoundTriggerHw.getService(true);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }};
- mService = new SoundTriggerMiddlewareService(
- new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()),
- getContext());
- publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService);
+ publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
+ new SoundTriggerMiddlewareService(
+ new SoundTriggerMiddlewareImpl(factories,
+ new AudioSessionProviderImpl()),
+ getContext()));
}
}
@@ -370,7 +369,7 @@
DeathRecipient {
private final ISoundTriggerCallback mCallback;
private ISoundTriggerModule mDelegate;
- private Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+ private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
ModuleService(@NonNull ISoundTriggerCallback callback) {
mCallback = callback;
@@ -680,7 +679,7 @@
} catch (RemoteException e) {
// Dead client will be handled by binderDied() - no need to handle here.
// In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
+ Log.e(TAG, "Client callback exception.", e);
}
}
}
@@ -696,20 +695,33 @@
} catch (RemoteException e) {
// Dead client will be handled by binderDied() - no need to handle here.
// In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
+ Log.e(TAG, "Client callback exception.", e);
}
}
}
@Override
- public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+ public void onRecognitionAvailabilityChange(boolean available) {
synchronized (this) {
try {
mCallback.onRecognitionAvailabilityChange(available);
} catch (RemoteException e) {
// Dead client will be handled by binderDied() - no need to handle here.
// In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
+ Log.e(TAG, "Client callback exception.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onModuleDied() {
+ synchronized (this) {
+ try {
+ mCallback.onModuleDied();
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
}
}
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index f024ede..adf16fa 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -30,7 +30,9 @@
import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
import android.media.soundtrigger_middleware.Status;
import android.os.IBinder;
+import android.os.IHwBinder;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.util.Log;
import java.util.HashMap;
@@ -57,6 +59,7 @@
* gracefully handle driver malfunction and such behavior will result in undefined behavior. If this
* service is to used with an untrusted driver, the driver must be wrapped with validation / error
* recovery code.
+ * <li>Recovery from driver death is supported.</li>
* <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not
* considered recoverable faults and should not occur in a properly functioning system.
* <li>There is no binder instance associated with this implementation. Do not call asBinder().
@@ -79,27 +82,29 @@
*
* @hide
*/
-class SoundTriggerModule {
+class SoundTriggerModule implements IHwBinder.DeathRecipient {
static private final String TAG = "SoundTriggerModule";
- @NonNull private final ISoundTriggerHw2 mHalService;
+ @NonNull private HalFactory mHalFactory;
+ @NonNull private ISoundTriggerHw2 mHalService;
@NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
private final Set<Session> mActiveSessions = new HashSet<>();
private int mNumLoadedModels = 0;
- private SoundTriggerModuleProperties mProperties = null;
+ private SoundTriggerModuleProperties mProperties;
private boolean mRecognitionAvailable;
/**
* Ctor.
*
- * @param halService The underlying HAL driver.
+ * @param halFactory A factory for the underlying HAL driver.
*/
- SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService,
+ SoundTriggerModule(@NonNull HalFactory halFactory,
@NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
- assert halService != null;
- mHalService = new SoundTriggerHw2Compat(halService);
+ assert halFactory != null;
+ mHalFactory = halFactory;
mAudioSessionProvider = audioSessionProvider;
- mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
+ attachToHal();
+ mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
// We conservatively assume that external capture is active until explicitly told otherwise.
mRecognitionAvailable = mProperties.concurrentCapture;
}
@@ -117,7 +122,7 @@
* @return The interface through which this module can be controlled.
*/
synchronized @NonNull
- Session attach(@NonNull ISoundTriggerCallback callback) {
+ ISoundTriggerModule attach(@NonNull ISoundTriggerCallback callback) {
Log.d(TAG, "attach()");
Session session = new Session(callback);
mActiveSessions.add(session);
@@ -145,6 +150,7 @@
*/
synchronized void setExternalCaptureState(boolean active) {
Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
+
if (mProperties.concurrentCapture) {
// If we support concurrent capture, we don't care about any of this.
return;
@@ -162,6 +168,23 @@
}
}
+ @Override
+ public synchronized void serviceDied(long cookie) {
+ Log.w(TAG, String.format("Underlying HAL driver died."));
+ for (Session session : mActiveSessions) {
+ session.moduleDied();
+ }
+ attachToHal();
+ }
+
+ /**
+ * Attached to the HAL service via factory.
+ */
+ private void attachToHal() {
+ mHalService = new SoundTriggerHw2Compat(mHalFactory.create());
+ mHalService.linkToDeath(this, 0);
+ }
+
/**
* Remove session from the list of active sessions.
*
@@ -204,7 +227,11 @@
public void detach() {
Log.d(TAG, "detach()");
synchronized (SoundTriggerModule.this) {
+ if (mCallback == null) {
+ return;
+ }
removeSession(this);
+ mCallback = null;
}
}
@@ -212,6 +239,7 @@
public int loadModel(@NonNull SoundModel model) {
Log.d(TAG, String.format("loadModel(model=%s)", model));
synchronized (SoundTriggerModule.this) {
+ checkValid();
if (mNumLoadedModels == mProperties.maxSoundModels) {
throw new RecoverableException(Status.RESOURCE_CONTENTION,
"Maximum number of models loaded.");
@@ -227,6 +255,7 @@
public int loadPhraseModel(@NonNull PhraseSoundModel model) {
Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
synchronized (SoundTriggerModule.this) {
+ checkValid();
if (mNumLoadedModels == mProperties.maxSoundModels) {
throw new RecoverableException(Status.RESOURCE_CONTENTION,
"Maximum number of models loaded.");
@@ -243,6 +272,7 @@
public void unloadModel(int modelHandle) {
Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).unload();
--mNumLoadedModels;
}
@@ -253,6 +283,7 @@
Log.d(TAG,
String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).startRecognition(config);
}
}
@@ -269,26 +300,28 @@
public void forceRecognitionEvent(int modelHandle) {
Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).forceRecognitionEvent();
}
}
@Override
- public void setModelParameter(int modelHandle, int modelParam, int value)
- throws RemoteException {
+ public void setModelParameter(int modelHandle, int modelParam, int value) {
Log.d(TAG,
String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
modelParam, value));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).setParameter(modelParam, value);
}
}
@Override
- public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+ public int getModelParameter(int modelHandle, int modelParam) {
Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
modelParam));
synchronized (SoundTriggerModule.this) {
+ checkValid();
return mLoadedModels.get(modelHandle).getParameter(modelParam);
}
}
@@ -299,6 +332,7 @@
Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
modelParam));
synchronized (SoundTriggerModule.this) {
+ checkValid();
return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
}
}
@@ -322,6 +356,25 @@
}
}
+ /**
+ * The underlying module HAL is dead.
+ */
+ private void moduleDied() {
+ try {
+ mCallback.onModuleDied();
+ removeSession(this);
+ mCallback = null;
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ private void checkValid() {
+ if (mCallback == null) {
+ throw new ServiceSpecificException(Status.DEAD_OBJECT);
+ }
+ }
+
@Override
public @NonNull
IBinder asBinder() {
@@ -350,10 +403,6 @@
SoundTriggerModule.this.notifyAll();
}
- private void waitStateChange() throws InterruptedException {
- SoundTriggerModule.this.wait();
- }
-
private int load(@NonNull SoundModel model) {
mModelType = model.type;
ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java
index 5ee7eff..1f7ed8a 100644
--- a/services/core/java/com/android/server/stats/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/StatsPullAtomService.java
@@ -210,6 +210,18 @@
@Override
public void onStart() {
mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER);
+
+ // Used to initialize the CPU Frequency atom.
+ PowerProfile powerProfile = new PowerProfile(mContext);
+ final int numClusters = powerProfile.getNumCpuClusters();
+ mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+ int firstCpuOfCluster = 0;
+ for (int i = 0; i < numClusters; i++) {
+ final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i);
+ mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+ numSpeedSteps);
+ firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
+ }
}
@Override
@@ -610,52 +622,193 @@
return StatsManager.PULL_SUCCESS;
}
+ private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+ private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
private void registerKernelWakelock() {
- // No op.
+ int tagId = StatsLog.KERNEL_WAKELOCK;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ /* PullAtomMetadata */ null,
+ (atomTag, data) -> pullKernelWakelock(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
}
- private void pullKernelWakelock() {
- // No op.
+ private int pullKernelWakelock(int atomTag, List<StatsEvent> pulledData) {
+ final KernelWakelockStats wakelockStats =
+ mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+ for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+ String name = ent.getKey();
+ KernelWakelockStats.Entry kws = ent.getValue();
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeString(name)
+ .writeInt(kws.mCount)
+ .writeInt(kws.mVersion)
+ .writeLong(kws.mTotalTime)
+ .build();
+ pulledData.add(e);
+ }
+ return StatsManager.PULL_SUCCESS;
}
+ private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+ // Disables throttler on CPU time readers.
+ private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+ new KernelCpuUidUserSysTimeReader(false);
+ private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+ new KernelCpuUidFreqTimeReader(false);
+ private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+ new KernelCpuUidActiveTimeReader(false);
+ private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+ new KernelCpuUidClusterTimeReader(false);
+
private void registerCpuTimePerFreq() {
- // No op.
+ int tagId = StatsLog.CPU_TIME_PER_FREQ;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuTimePerFreq(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
}
- private void pullCpuTimePerFreq() {
- // No op.
+ private int pullCpuTimePerFreq(int atomTag, List<StatsEvent> pulledData) {
+ for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+ long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
+ if (clusterTimeMs != null) {
+ for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(cluster)
+ .writeInt(speed)
+ .writeLong(clusterTimeMs[speed])
+ .build();
+ pulledData.add(e);
+ }
+ }
+ }
+ return StatsManager.PULL_SUCCESS;
}
private void registerCpuTimePerUid() {
- // No op.
+ int tagId = StatsLog.CPU_TIME_PER_UID;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2, 3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuTimePerUid(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
}
- private void pullCpuTimePerUid() {
- // No op.
+ private int pullCpuTimePerUid(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
+ long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeLong(userTimeUs)
+ .writeLong(systemTimeUs)
+ .build();
+ pulledData.add(e);
+ });
+ return StatsManager.PULL_SUCCESS;
}
private void registerCpuTimePerUidFreq() {
- // No op.
+ // the throttling is 3sec, handled in
+ // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+ int tagId = StatsLog.CPU_TIME_PER_UID_FREQ;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {4})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuTimeperUidFreq(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
}
- private void pullCpuTimeperUidFreq() {
- // No op.
+ private int pullCpuTimeperUidFreq(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
+ for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+ if (cpuFreqTimeMs[freqIndex] != 0) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeInt(freqIndex)
+ .writeLong(cpuFreqTimeMs[freqIndex])
+ .build();
+ pulledData.add(e);
+ }
+ }
+ });
+ return StatsManager.PULL_SUCCESS;
}
private void registerCpuActiveTime() {
- // No op.
+ // the throttling is 3sec, handled in
+ // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+ int tagId = StatsLog.CPU_ACTIVE_TIME;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuActiveTime(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
}
- private void pullCpuActiveTime() {
- // No op.
+ private int pullCpuActiveTime(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeLong(cpuActiveTimesMs)
+ .build();
+ pulledData.add(e);
+ });
+ return StatsManager.PULL_SUCCESS;
}
private void registerCpuClusterTime() {
- // No op.
+ // the throttling is 3sec, handled in
+ // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+ int tagId = StatsLog.CPU_CLUSTER_TIME;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuClusterTime(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
}
- private int pullCpuClusterTime() {
- return 0;
+ private int pullCpuClusterTime(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
+ for (int i = 0; i < cpuClusterTimesMs.length; i++) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeInt(i)
+ .writeLong(cpuClusterTimesMs[i])
+ .build();
+ pulledData.add(e);
+ }
+ });
+ return StatsManager.PULL_SUCCESS;
}
private void registerWifiActivityInfo() {
@@ -711,11 +864,22 @@
}
private void registerSystemUptime() {
- // No op.
+ int tagId = StatsLog.SYSTEM_UPTIME;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullSystemUptime(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
}
- private void pullSystemUptime() {
- // No op.
+ private int pullSystemUptime(int atomTag, List<StatsEvent> pulledData) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(SystemClock.elapsedRealtime())
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
}
private void registerRemainingBatteryCapacity() {
@@ -990,11 +1154,30 @@
}
private void registerBuildInformation() {
- // No op.
+ int tagId = StatsLog.BUILD_INFORMATION;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullBuildInformation(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
}
- private void pullBuildInformation() {
- // No op.
+ private int pullBuildInformation(int atomTag, List<StatsEvent> pulledData) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeString(Build.FINGERPRINT)
+ .writeString(Build.BRAND)
+ .writeString(Build.PRODUCT)
+ .writeString(Build.DEVICE)
+ .writeString(Build.VERSION.RELEASE)
+ .writeString(Build.ID)
+ .writeString(Build.VERSION.INCREMENTAL)
+ .writeString(Build.TYPE)
+ .writeString(Build.TAGS)
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
}
private void registerRoleHolder() {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index c964795..468b806 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -61,10 +61,10 @@
/** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
void acquireWakeLock();
- /** Returns the elapsedRealtimeMillis clock value. The WakeLock must be held. */
+ /** Returns the elapsedRealtimeMillis clock value. */
long elapsedRealtimeMillis();
- /** Returns the system clock value. The WakeLock must be held. */
+ /** Returns the system clock value. */
long systemClockMillis();
/** Sets the device system clock. The WakeLock must be held. */
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
index 9b89d94..19484db 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
@@ -88,13 +88,11 @@
@Override
public long elapsedRealtimeMillis() {
- checkWakeLockHeld();
return SystemClock.elapsedRealtime();
}
@Override
public long systemClockMillis() {
- checkWakeLockHeld();
return System.currentTimeMillis();
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 320be2d..19bc560 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1633,12 +1633,7 @@
requestedVrComponent = (aInfo.requestedVrComponent == null) ?
null : ComponentName.unflattenFromString(aInfo.requestedVrComponent);
- lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
- if (info.applicationInfo.isPrivilegedApp()
- && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
- || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
- lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
- }
+ lockTaskLaunchMode = getLockTaskLaunchMode(aInfo, options);
if (options != null) {
pendingOptions = options;
@@ -1646,15 +1641,27 @@
if (usageReport != null) {
appTimeTracker = new AppTimeTracker(usageReport);
}
- final boolean useLockTask = pendingOptions.getLockTaskMode();
- if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
- lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
- }
// Gets launch display id from options. It returns INVALID_DISPLAY if not set.
mHandoverLaunchDisplayId = options.getLaunchDisplayId();
}
}
+ static int getLockTaskLaunchMode(ActivityInfo aInfo, @Nullable ActivityOptions options) {
+ int lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
+ if (aInfo.applicationInfo.isPrivilegedApp()
+ && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+ || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+ lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+ }
+ if (options != null) {
+ final boolean useLockTask = options.getLockTaskMode();
+ if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
+ lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+ }
+ }
+ return lockTaskLaunchMode;
+ }
+
@Override
ActivityRecord asActivityRecord() {
// I am an activity record!
@@ -2130,8 +2137,10 @@
(intent == null || (intent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
}
+ @Override
boolean isFocusable() {
- return mRootWindowContainer.isFocusable(this, isAlwaysFocusable());
+ return super.isFocusable()
+ && (getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable());
}
boolean isResizeable() {
@@ -2482,7 +2491,7 @@
// We are finishing the top focused activity and its stack has nothing to be focused so
// the next focusable stack should be focused.
if (mayAdjustTop
- && (stack.topRunningActivity() == null || !stack.isFocusable())) {
+ && (stack.topRunningActivity() == null || !stack.isTopActivityFocusable())) {
if (shouldAdjustGlobalFocus) {
// Move the entire hierarchy to top with updating global top resumed activity
// and focused application if needed.
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 9fd3ea4..f5fba8e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -161,9 +161,9 @@
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.ITaskOrganizer;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.ITaskOrganizer;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -1248,13 +1248,20 @@
}
}
+ @Override
boolean isFocusable() {
+ return super.isFocusable() && !(inSplitScreenPrimaryWindowingMode()
+ && mRootWindowContainer.mIsDockMinimized);
+ }
+
+ boolean isTopActivityFocusable() {
final ActivityRecord r = topRunningActivity();
- return mRootWindowContainer.isFocusable(this, r != null && r.isFocusable());
+ return r != null ? r.isFocusable()
+ : (isFocusable() && getWindowConfiguration().canReceiveKeys());
}
boolean isFocusableAndVisible() {
- return isFocusable() && shouldBeVisible(null /* starting */);
+ return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index df97caa..d61d29d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -52,6 +52,7 @@
import android.os.UserManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.BlockedAppActivity;
import com.android.internal.app.HarmfulAppWarningActivity;
import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.app.UnlaunchableAppActivity;
@@ -166,6 +167,9 @@
// no user action can undo this.
return true;
}
+ if (interceptLockTaskModeViolationPackageIfNeeded()) {
+ return true;
+ }
if (interceptHarmfulAppIfNeeded()) {
// If the app has a "harmful app" warning associated with it, we should ask to uninstall
// before issuing the work challenge.
@@ -262,6 +266,25 @@
return true;
}
+ private boolean interceptLockTaskModeViolationPackageIfNeeded() {
+ if (mAInfo == null || mAInfo.applicationInfo == null) {
+ return false;
+ }
+ LockTaskController controller = mService.getLockTaskController();
+ String packageName = mAInfo.applicationInfo.packageName;
+ int lockTaskLaunchMode = ActivityRecord.getLockTaskLaunchMode(mAInfo, mActivityOptions);
+ if (controller.isActivityAllowed(mUserId, packageName, lockTaskLaunchMode)) {
+ return false;
+ }
+ mIntent = BlockedAppActivity.createIntent(mUserId, mAInfo.applicationInfo.packageName);
+ mCallingPid = mRealCallingPid;
+ mCallingUid = mRealCallingUid;
+ mResolvedType = null;
+ mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
+ mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
+ return true;
+ }
+
private boolean interceptWorkProfileChallengeIfNeeded() {
final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
if (interceptingIntent == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 61ba15c..6587226 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1569,7 +1569,7 @@
if (mDoResume) {
final ActivityRecord topTaskActivity =
mStartActivity.getTask().topRunningActivityLocked();
- if (!mTargetStack.isFocusable()
+ if (!mTargetStack.isTopActivityFocusable()
|| (topTaskActivity != null && topTaskActivity.isTaskOverlay()
&& mStartActivity != topTaskActivity)) {
// If the activity is not focusable, we can't resume it, but still would like to
@@ -1588,7 +1588,7 @@
// will not update the focused stack. If starting the new activity now allows the
// task stack to be focusable, then ensure that we now update the focused stack
// accordingly.
- if (mTargetStack.isFocusable()
+ if (mTargetStack.isTopActivityFocusable()
&& !mRootWindowContainer.isTopDisplayFocusedStack(mTargetStack)) {
mTargetStack.moveToFront("startActivityInner");
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ded603c..e308f6b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -28,7 +28,7 @@
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.Manifest.permission.STOP_APP_SWITCHES;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
@@ -248,7 +248,6 @@
import com.android.internal.policy.KeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -345,6 +344,10 @@
/** This activity is being relaunched due to a free-resize operation. */
public static final int RELAUNCH_REASON_FREE_RESIZE = 2;
+ /** Flag indicating that an applied transaction may have effected lifecycle */
+ private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1;
+ private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1;
+
Context mContext;
/**
@@ -1436,7 +1439,7 @@
int handleIncomingUser(int callingPid, int callingUid, int userId, String name) {
return mAmInternal.handleIncomingUser(callingPid, callingUid, userId, false /* allowAll */,
- ALLOW_FULL_ONLY, name, null /* callerPackage */);
+ ALLOW_NON_FULL, name, null /* callerPackage */);
}
@Override
@@ -3300,7 +3303,7 @@
}
}
- private void sanitizeAndApplyConfigChange(ConfigurationContainer container,
+ private int sanitizeAndApplyChange(ConfigurationContainer container,
WindowContainerTransaction.Change change) {
if (!(container instanceof Task || container instanceof ActivityStack)) {
throw new RuntimeException("Invalid token in task transaction");
@@ -3312,12 +3315,22 @@
configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
- Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
- c.setTo(change.getConfiguration(), configMask, windowMask);
- container.onRequestedOverrideConfigurationChanged(c);
- // TODO(b/145675353): remove the following once we could apply new bounds to the
- // pinned stack together with its children.
- resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+ int effects = 0;
+ if (configMask != 0) {
+ Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
+ c.setTo(change.getConfiguration(), configMask, windowMask);
+ container.onRequestedOverrideConfigurationChanged(c);
+ // TODO(b/145675353): remove the following once we could apply new bounds to the
+ // pinned stack together with its children.
+ resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+ effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ }
+ if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
+ if (container.setFocusable(change.getFocusable())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
+ return effects;
}
private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
@@ -3334,9 +3347,9 @@
}
}
- private void applyWindowContainerChange(ConfigurationContainer cc,
+ private int applyWindowContainerChange(ConfigurationContainer cc,
WindowContainerTransaction.Change c) {
- sanitizeAndApplyConfigChange(cc, c);
+ int effects = sanitizeAndApplyChange(cc, c);
Rect enterPipBounds = c.getEnterPipBounds();
if (enterPipBounds != null) {
@@ -3344,25 +3357,58 @@
mStackSupervisor.updatePictureInPictureMode(tr,
enterPipBounds, true);
}
+ return effects;
}
@Override
public void applyContainerTransaction(WindowContainerTransaction t) {
mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()");
+ if (t == null) {
+ return;
+ }
long ident = Binder.clearCallingIdentity();
try {
- if (t == null) {
- return;
- }
synchronized (mGlobalLock) {
- Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
- t.getChanges().entrySet().iterator();
- while (entries.hasNext()) {
- final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
- entries.next();
- final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder(
- entry.getKey()).getContainer();
- applyWindowContainerChange(cc, entry.getValue());
+ int effects = 0;
+ deferWindowLayout();
+ try {
+ ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
+ Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
+ t.getChanges().entrySet().iterator();
+ while (entries.hasNext()) {
+ final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
+ entries.next();
+ final ConfigurationContainer cc =
+ ConfigurationContainer.RemoteToken.fromBinder(
+ entry.getKey()).getContainer();
+ int containerEffect = applyWindowContainerChange(cc, entry.getValue());
+ effects |= containerEffect;
+ // Lifecycle changes will trigger ensureConfig for everything.
+ if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
+ && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+ if (cc instanceof WindowContainer) {
+ haveConfigChanges.add((WindowContainer) cc);
+ }
+ }
+ }
+ if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
+ // Already calls ensureActivityConfig
+ mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+ final PooledConsumer f = PooledLambda.obtainConsumer(
+ ActivityRecord::ensureActivityConfiguration,
+ PooledLambda.__(ActivityRecord.class), 0,
+ false /* preserveWindow */);
+ try {
+ for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
+ haveConfigChanges.valueAt(i).forAllActivities(f);
+ }
+ } finally {
+ f.recycle();
+ }
+ }
+ } finally {
+ continueWindowLayout();
}
}
} finally {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index d0310f1..1e60ce8 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -624,6 +624,21 @@
return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
}
+ /**
+ * Returns {@code true} if this container is focusable. Generally, if a parent is not focusable,
+ * this will not be focusable either.
+ */
+ boolean isFocusable() {
+ // TODO(split): Move this to WindowContainer once Split-screen is based on a WindowContainer
+ // like DisplayArea vs. TaskTiles.
+ ConfigurationContainer parent = getParent();
+ return parent == null || parent.isFocusable();
+ }
+
+ boolean setFocusable(boolean focusable) {
+ return false;
+ }
+
boolean hasChild() {
return getChildCount() > 0;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ba9d757..908c4f1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5915,7 +5915,7 @@
final ActivityRecord resumedActivity = stack.getResumedActivity();
if (resumedActivity != null
&& (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
- || !stack.isFocusable())) {
+ || !stack.isTopActivityFocusable())) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
+ " mResumedActivity=" + resumedActivity);
someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
@@ -6238,7 +6238,7 @@
for (int i = getStackCount() - 1; i >= 0; --i) {
final ActivityStack stack = getStackAt(i);
// Only consider focusable stacks other than the current focused one.
- if (stack == focusedStack || !stack.isFocusable()) {
+ if (stack == focusedStack || !stack.isTopActivityFocusable()) {
continue;
}
topRunning = stack.topRunningActivity();
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index c09834f..9d985d7 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -75,7 +75,8 @@
// activities are actually behind other fullscreen activities, but still required
// to be visible (such as performing Recents animation).
final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind
- && mContiner.isFocusable() && mContiner.isInStackLocked(starting) == null;
+ && mContiner.isTopActivityFocusable()
+ && mContiner.isInStackLocked(starting) == null;
final PooledConsumer f = PooledLambda.obtainConsumer(
EnsureActivitiesVisibleHelper::setActivityVisibilityState, this,
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 02413bb..33b0453 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -23,6 +23,8 @@
import static android.content.Context.DEVICE_POLICY_SERVICE;
import static android.content.Context.STATUS_BAR_SERVICE;
import static android.content.Intent.ACTION_CALL_EMERGENCY;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_CURRENT;
import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
@@ -339,6 +341,20 @@
& DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD) != 0;
}
+ boolean isActivityAllowed(int userId, String packageName, int lockTaskLaunchMode) {
+ if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED) {
+ return true;
+ }
+ switch (lockTaskLaunchMode) {
+ case LOCK_TASK_LAUNCH_MODE_ALWAYS:
+ return true;
+ case LOCK_TASK_LAUNCH_MODE_NEVER:
+ return false;
+ default:
+ }
+ return isPackageWhitelisted(userId, packageName);
+ }
+
private boolean isEmergencyCallTask(Task task) {
final Intent intent = task.intent;
if (intent == null) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c3e815d..2f726e9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1860,14 +1860,6 @@
return null;
}
- boolean isFocusable(ConfigurationContainer container, boolean alwaysFocusable) {
- if (container.inSplitScreenPrimaryWindowingMode() && mIsDockMinimized) {
- return false;
- }
-
- return container.getWindowConfiguration().canReceiveKeys() || alwaysFocusable;
- }
-
boolean isTopDisplayFocusedStack(ActivityStack stack) {
return stack != null && stack == getTopDisplayFocusedStack();
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index d36a5d4..38a7000 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -53,7 +53,7 @@
private static final String SNAPSHOTS_DIRNAME = "snapshots";
private static final String REDUCED_POSTFIX = "_reduced";
private static final float REDUCED_SCALE = .5f;
- private static final float LOW_RAM_REDUCED_SCALE = .6f;
+ private static final float LOW_RAM_REDUCED_SCALE = .8f;
static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
private static final long DELAY_MS = 100;
private static final int QUALITY = 95;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index c38868a..3b2d519 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -247,6 +247,8 @@
private MagnificationSpec mLastMagnificationSpec;
+ private boolean mIsFocusable = true;
+
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mPendingTransaction = wms.mTransactionFactory.get();
@@ -851,6 +853,21 @@
return false;
}
+ @Override
+ boolean isFocusable() {
+ return super.isFocusable() && mIsFocusable;
+ }
+
+ /** Set whether this container or its children can be focusable */
+ @Override
+ boolean setFocusable(boolean focusable) {
+ if (mIsFocusable == focusable) {
+ return false;
+ }
+ mIsFocusable = focusable;
+ return true;
+ }
+
/**
* @return Whether this child is on top of the window hierarchy.
*/
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 74fdba1..e3b593e9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4602,13 +4602,13 @@
if (newFocus != null) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Gaining focus: %s", newFocus);
- newFocus.reportFocusChangedSerialized(true, mInTouchMode);
+ newFocus.reportFocusChangedSerialized(true);
notifyFocusChanged();
}
if (lastFocus != null) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing focus: %s", lastFocus);
- lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
+ lastFocus.reportFocusChangedSerialized(false);
}
break;
}
@@ -4626,7 +4626,7 @@
for (int i = 0; i < N; i++) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing delayed focus: %s",
losers.get(i));
- losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
+ losers.get(i).reportFocusChangedSerialized(false);
}
break;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ba40f62..c2eb0e4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3339,11 +3339,7 @@
* Report a focus change. Must be called with no locks held, and consistently
* from the same serialized thread (such as dispatched from a handler).
*/
- void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
- try {
- mClient.windowFocusChanged(focused, inTouchMode);
- } catch (RemoteException e) {
- }
+ void reportFocusChangedSerialized(boolean focused) {
if (mFocusCallbacks != null) {
final int N = mFocusCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 03969b0..77d814e 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -51,7 +51,7 @@
"com_android_server_VibratorService.cpp",
"com_android_server_PersistentDataBlockService.cpp",
"com_android_server_GraphicsStatsService.cpp",
- "com_android_server_am_AppCompactor.cpp",
+ "com_android_server_am_CachedAppOptimizer.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_incremental_IncrementalManagerService.cpp",
"onload.cpp",
diff --git a/services/core/jni/com_android_server_am_AppCompactor.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
similarity index 89%
rename from services/core/jni/com_android_server_am_AppCompactor.cpp
rename to services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index de6aa8b..6a6da0e 100644
--- a/services/core/jni/com_android_server_am_AppCompactor.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "AppCompactor"
+#define LOG_TAG "CachedAppOptimizer"
//#define LOG_NDEBUG 0
#include <dirent.h>
@@ -42,7 +42,7 @@
// or potentially some mainline modules. The only process that should definitely
// not be compacted is system_server, since compacting system_server around the
// time of BOOT_COMPLETE could result in perceptible issues.
-static void com_android_server_am_AppCompactor_compactSystem(JNIEnv *, jobject) {
+static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, jobject) {
std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
struct dirent* current;
while ((current = readdir(proc.get()))) {
@@ -76,12 +76,12 @@
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
- {"compactSystem", "()V", (void*)com_android_server_am_AppCompactor_compactSystem},
+ {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
};
-int register_android_server_am_AppCompactor(JNIEnv* env)
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
{
- return jniRegisterNativeMethods(env, "com/android/server/am/AppCompactor",
+ return jniRegisterNativeMethods(env, "com/android/server/am/CachedAppOptimizer",
sMethods, NELEM(sMethods));
}
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c0a6e4e..19fa062 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -54,7 +54,7 @@
int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
int register_android_server_net_NetworkStatsService(JNIEnv* env);
int register_android_server_security_VerityUtils(JNIEnv* env);
-int register_android_server_am_AppCompactor(JNIEnv* env);
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
JNIEnv* env);
@@ -106,7 +106,7 @@
register_android_server_net_NetworkStatsFactory(env);
register_android_server_net_NetworkStatsService(env);
register_android_server_security_VerityUtils(env);
- register_android_server_am_AppCompactor(env);
+ register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_LowMemDetector(env);
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b6a8ca4..0bd7392 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -71,11 +71,11 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
+import android.util.StatsLog;
import android.view.WindowManager;
import android.view.contentcapture.ContentCaptureManager;
import com.android.internal.R;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
import com.android.internal.util.ConcurrentUtils;
@@ -213,6 +213,8 @@
"com.android.server.print.PrintManagerService";
private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
"com.android.server.companion.CompanionDeviceManagerService";
+ private static final String STATS_COMPANION_APEX_PATH =
+ "/apex/com.android.os.statsd/javalib/service-statsd.jar";
private static final String STATS_COMPANION_LIFECYCLE_CLASS =
"com.android.server.stats.StatsCompanion$Lifecycle";
private static final String STATS_PULL_ATOM_SERVICE_CLASS =
@@ -441,10 +443,12 @@
// Here we go!
Slog.i(TAG, "Entered the Android system server!");
- int uptimeMillis = (int) SystemClock.elapsedRealtime();
+ final long uptimeMillis = SystemClock.elapsedRealtime();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
if (!mRuntimeRestart) {
- MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_INIT_START,
+ uptimeMillis);
}
// In case the runtime switched since last boot (such as when
@@ -553,10 +557,12 @@
StrictMode.initVmDefaults(null);
if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
- int uptimeMillis = (int) SystemClock.elapsedRealtime();
- MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis);
- final int MAX_UPTIME_MILLIS = 60 * 1000;
- if (uptimeMillis > MAX_UPTIME_MILLIS) {
+ final long uptimeMillis = SystemClock.elapsedRealtime();
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_READY,
+ uptimeMillis);
+ final long maxUptimeMillis = 60 * 1000;
+ if (uptimeMillis > maxUptimeMillis) {
Slog.wtf(SYSTEM_SERVER_TIMING_TAG,
"SystemServer init took too long. uptimeMillis=" + uptimeMillis);
}
@@ -789,8 +795,9 @@
// Start the package manager.
if (!mRuntimeRestart) {
- MetricsLogger.histogram(null, "boot_package_manager_init_start",
- (int) SystemClock.elapsedRealtime());
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_START,
+ SystemClock.elapsedRealtime());
}
t.traceBegin("StartPackageManagerService");
@@ -806,8 +813,9 @@
mPackageManager = mSystemContext.getPackageManager();
t.traceEnd();
if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
- MetricsLogger.histogram(null, "boot_package_manager_init_ready",
- (int) SystemClock.elapsedRealtime());
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_READY,
+ SystemClock.elapsedRealtime());
}
// Manages A/B OTA dexopting. This is a bootstrap service as we need it to rename
// A/B artifacts after boot, before anything else might touch/need them.
@@ -1980,7 +1988,8 @@
// Statsd helper
t.traceBegin("StartStatsCompanion");
- mSystemServiceManager.startService(STATS_COMPANION_LIFECYCLE_CLASS);
+ mSystemServiceManager.startServiceFromJar(
+ STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH);
t.traceEnd();
// Statsd pulled atoms
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 9c7cfc1..cf84bdf 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -23,7 +23,6 @@
name: "services-tethering-shared-srcs",
srcs: [
":framework-annotations",
- "java/android/net/util/NetdService.java",
"java/android/net/util/NetworkConstants.java",
],
visibility: ["//frameworks/base/packages/Tethering"],
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index ef3da60..9569c6e 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -137,5 +137,14 @@
Slog.e(TAG, "Failed to calling callback" + e);
}
}
+
+ @Override
+ public byte[] backupConversationInfos(int userId) {
+ return new byte[0];
+ }
+
+ @Override
+ public void restoreConversationInfos(int userId, String key, byte[] payload) {
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
deleted file mode 100644
index 48e459f..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
+++ /dev/null
@@ -1,680 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import static com.android.server.am.ActivityManagerService.Injector;
-import static com.android.server.am.AppCompactor.compactActionIntToString;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.platform.test.annotations.Presubmit;
-import android.provider.DeviceConfig;
-import android.text.TextUtils;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.ServiceThread;
-import com.android.server.appop.AppOpsService;
-import com.android.server.testables.TestableDeviceConfig;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for {@link AppCompactor}.
- *
- * Build/Install/Run:
- * atest FrameworksMockingServicesTests:AppCompactorTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner.class)
-public final class AppCompactorTest {
-
- private ServiceThread mThread;
-
- @Mock
- private AppOpsService mAppOpsService;
- private AppCompactor mCompactorUnderTest;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private CountDownLatch mCountDown;
-
- @Rule
- public TestableDeviceConfig.TestableDeviceConfigRule
- mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
-
- @Before
- public void setUp() {
- mHandlerThread = new HandlerThread("");
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
-
- mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
- true /* allowIo */);
- mThread.start();
-
- ActivityManagerService ams = new ActivityManagerService(
- new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
- mThread);
- mCompactorUnderTest = new AppCompactor(ams,
- new AppCompactor.PropertyChangedCallbackForTest() {
- @Override
- public void onPropertyChanged() {
- if (mCountDown != null) {
- mCountDown.countDown();
- }
- }
- });
- }
-
- @After
- public void tearDown() {
- mHandlerThread.quit();
- mThread.quit();
- mCountDown = null;
- }
-
- @Test
- public void init_setsDefaults() {
- mCompactorUnderTest.init();
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
- Set<Integer> expected = new HashSet<>();
- for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
- expected.add(Integer.parseInt(s));
- }
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- }
-
- @Test
- public void init_withDeviceConfigSetsParameters() {
- // When the DeviceConfig already has a flag value stored (note this test will need to
- // change if the default value changes from false).
- assertThat(AppCompactor.DEFAULT_USE_COMPACTION).isFalse();
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "true", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1,
- Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2,
- Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_1,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_2,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_3,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_4,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_5,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_6,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
-
- // Then calling init will read and set that flag.
- mCompactorUnderTest.init();
- assertThat(mCompactorUnderTest.useCompaction()).isTrue();
- assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue();
-
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
- }
-
- @Test
- public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- // When we call init and change some the flag value...
- mCompactorUnderTest.init();
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "true", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that new flag value is updated in the implementation.
- assertThat(mCompactorUnderTest.useCompaction()).isTrue();
- assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue();
-
- // And again, setting the flag the other way.
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "false", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.useCompaction()).isFalse();
- }
-
- @Test
- public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- mCompactorUnderTest.init();
-
- // When we push an invalid flag value...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "foobar", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then we set the default.
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- }
-
- @Test
- public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override new values for the compaction action with reasonable values...
-
- // There are four possible values for compactAction[Some|Full].
- for (int i = 1; i < 5; i++) {
- mCountDown = new CountDownLatch(2);
- int expectedSome = (AppCompactor.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
- int expectedFull = (AppCompactor.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the updates are reflected in the flags.
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(expectedSome));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(expectedFull));
- }
- }
-
- @Test
- public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override new values for the compaction action with bad values ...
- mCountDown = new CountDownLatch(2);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1, "foo", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the default values are reflected in the flag
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
-
- mCountDown = new CountDownLatch(2);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1, "", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2, "", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
- }
-
- @Test
- public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override new reasonable throttle values after init...
- mCountDown = new CountDownLatch(6);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_1,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_2,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_3,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_4,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_5,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_6,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then those flags values are reflected in the compactor.
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
- }
-
- @Test
- public void compactThrottle_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When one of the throttles is overridden with a bad value...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_1, "foo", false);
- // Then all the throttles have the defaults set.
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- // Repeat for each of the throttle keys.
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_2, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_3, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_4, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_5, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_6, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
- }
-
- @Test
- public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with a reasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
- }
-
- @Test
- public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an unreasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
- }
-
- @Test
- public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an value outside of [0..1]...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(-1.0f), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the values is capped in the range.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(0.0f);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(1.01f), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the values is capped in the range.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
- }
-
- @Test
- public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with a reasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
- }
-
- @Test
- public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an unreasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
- }
-
- @Test
- public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with a reasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
- }
-
- @Test
- public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an unreasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
- }
-
- @Test
- public void procStateThrottle_listensToDeviceConfigChanges()
- throws InterruptedException {
- mCompactorUnderTest.init();
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).isEmpty();
- }
-
- @Test
- public void procStateThrottle_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- Set<Integer> expected = new HashSet<>();
- for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
- expected.add(Integer.parseInt(s));
- }
-
- // Not numbers
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-
- // Empty splits
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- }
-
- private class TestInjector extends Injector {
-
- TestInjector(Context context) {
- super(context);
- }
-
- @Override
- public AppOpsService getAppOpsService(File file, Handler handler) {
- return mAppOpsService;
- }
-
- @Override
- public Handler getUiHandler(ActivityManagerService service) {
- return mHandler;
- }
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
new file mode 100644
index 0000000..f037692
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.CachedAppOptimizer.compactActionIntToString;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link CachedAppOptimizer}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:CachedAppOptimizerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public final class CachedAppOptimizerTest {
+
+ private ServiceThread mThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ private CachedAppOptimizer mCachedAppOptimizerUnderTest;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private CountDownLatch mCountDown;
+
+ @Rule
+ public TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ @Before
+ public void setUp() {
+ mHandlerThread = new HandlerThread("");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
+ true /* allowIo */);
+ mThread.start();
+
+ ActivityManagerService ams = new ActivityManagerService(
+ new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
+ mThread);
+ mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams,
+ new CachedAppOptimizer.PropertyChangedCallbackForTest() {
+ @Override
+ public void onPropertyChanged() {
+ if (mCountDown != null) {
+ mCountDown.countDown();
+ }
+ }
+ });
+ }
+
+ @After
+ public void tearDown() {
+ mHandlerThread.quit();
+ mThread.quit();
+ mCountDown = null;
+ }
+
+ @Test
+ public void init_setsDefaults() {
+ mCachedAppOptimizerUnderTest.init();
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+ Set<Integer> expected = new HashSet<>();
+ for (String s : TextUtils.split(
+ CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+ expected.add(Integer.parseInt(s));
+ }
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void init_withDeviceConfigSetsParameters() {
+ // When the DeviceConfig already has a flag value stored (note this test will need to
+ // change if the default value changes from false).
+ assertThat(CachedAppOptimizer.DEFAULT_USE_COMPACTION).isFalse();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1,
+ Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2,
+ Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_2,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_3,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_4,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_5,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_6,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+ Long.toString(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+
+ // Then calling init will read and set that flag.
+ mCachedAppOptimizerUnderTest.init();
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
+
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(
+ (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(
+ (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+ }
+
+ @Test
+ public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ // When we call init and change some the flag value...
+ mCachedAppOptimizerUnderTest.init();
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that new flag value is updated in the implementation.
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
+
+ // And again, setting the flag the other way.
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "false", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isFalse();
+ }
+
+ @Test
+ public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we push an invalid flag value...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "foobar", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then we set the default.
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ }
+
+ @Test
+ public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override new values for the compaction action with reasonable values...
+
+ // There are four possible values for compactAction[Some|Full].
+ for (int i = 1; i < 5; i++) {
+ mCountDown = new CountDownLatch(2);
+ int expectedSome = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
+ int expectedFull = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the updates are reflected in the flags.
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(expectedSome));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(expectedFull));
+ }
+ }
+
+ @Test
+ public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override new values for the compaction action with bad values ...
+ mCountDown = new CountDownLatch(2);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1, "foo", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the default values are reflected in the flag
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+
+ mCountDown = new CountDownLatch(2);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1, "", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2, "", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+ }
+
+ @Test
+ public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override new reasonable throttle values after init...
+ mCountDown = new CountDownLatch(6);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_2,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_3,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_4,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_5,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_6,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then those flags values are reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+ }
+
+ @Test
+ public void compactThrottle_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When one of the throttles is overridden with a bad value...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_1, "foo", false);
+ // Then all the throttles have the defaults set.
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ // Repeat for each of the throttle keys.
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_2, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_3, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_4, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_5, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_6, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+ }
+
+ @Test
+ public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with a reasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
+ }
+
+ @Test
+ public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an unreasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
+ }
+
+ @Test
+ public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an value outside of [0..1]...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(-1.0f), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the values is capped in the range.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(0.0f);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(1.01f), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the values is capped in the range.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
+ }
+
+ @Test
+ public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with a reasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+ }
+
+ @Test
+ public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an unreasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+ }
+
+ @Test
+ public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with a reasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+ Long.toString(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
+ }
+
+ @Test
+ public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an unreasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+ }
+
+ @Test
+ public void procStateThrottle_listensToDeviceConfigChanges()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).isEmpty();
+ }
+
+ @Test
+ public void procStateThrottle_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ Set<Integer> expected = new HashSet<>();
+ for (String s : TextUtils.split(
+ CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+ expected.add(Integer.parseInt(s));
+ }
+
+ // Not numbers
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+
+ // Empty splits
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ }
+
+ private class TestInjector extends Injector {
+
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index c223f13..e1e9b7e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -75,6 +75,7 @@
import android.os.IPowerManager;
import android.os.PowerManager;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
@@ -693,6 +694,18 @@
assertThat(result, is(false));
}
+ @Test
+ public void takeScreenshot_returnNull() {
+ // no canTakeScreenshot, should return null.
+ when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false);
+ assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+
+ // no checkAccessibilityAccess, should return null.
+ when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true);
+ when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
+ assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+ }
+
private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType,
int feedbackType, int flags, String[] packageNames, int notificationTimeout) {
serviceInfo.eventTypes = eventType;
@@ -832,5 +845,8 @@
@Override
public void onFingerprintGesture(int gesture) {}
+
+ @Override
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
index 04ac7fe..1504097 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
@@ -346,6 +346,14 @@
}
@Test
+ public void canTakeScreenshot_hasCapability_returnTrue() {
+ when(mMockA11yServiceConnection.getCapabilities())
+ .thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
+
+ assertTrue(mA11ySecurityPolicy.canTakeScreenshotLocked(mMockA11yServiceConnection));
+ }
+
+ @Test
public void resolveProfileParent_userIdIsCurrentUser_returnCurrentUser() {
final int currentUserId = 10;
final int userId = currentUserId;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 96d9c47..ac5169c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -113,7 +113,6 @@
mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
mUserState.setTouchExplorationEnabledLocked(true);
mUserState.setDisplayMagnificationEnabledLocked(true);
- mUserState.setNavBarMagnificationEnabledLocked(true);
mUserState.setAutoclickEnabledLocked(true);
mUserState.setUserNonInteractiveUiTimeoutLocked(30);
mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -132,7 +131,6 @@
assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
- assertFalse(mUserState.isNavBarMagnificationEnabledLocked());
assertFalse(mUserState.isAutoclickEnabledLocked());
assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 79cc3db..f122014 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -31,6 +31,7 @@
import static com.google.android.collect.Lists.newArrayList;
import static com.google.android.collect.Sets.newHashSet;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
@@ -54,6 +55,7 @@
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.IUserSwitchObserver;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -74,10 +76,10 @@
import android.platform.test.annotations.Presubmit;
import android.util.Log;
-
import androidx.test.filters.SmallTest;
import com.android.server.FgThread;
+import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -107,6 +109,7 @@
private static final int TEST_USER_ID = 100;
private static final int TEST_USER_ID1 = 101;
private static final int TEST_USER_ID2 = 102;
+ private static final int TEST_USER_ID3 = 103;
private static final int NONEXIST_USER_ID = 2;
private static final int TEST_PRE_CREATED_USER_ID = 103;
@@ -120,6 +123,8 @@
private TestInjector mInjector;
private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
+ private final KeyEvictedCallback mKeyEvictedCallback = (userId) -> { /* ignore */ };
+
private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList(
Intent.ACTION_USER_STARTED,
Intent.ACTION_USER_SWITCHED,
@@ -153,6 +158,7 @@
doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt());
+ // All UserController params are set to default.
mUserController = new UserController(mInjector);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated=*/ true);
@@ -187,7 +193,9 @@
@Test
public void testStartUserUIDisabled() {
- mUserController.mUserSwitchUiEnabled = false;
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
mUserController.startUser(TEST_USER_ID, true /* foreground */);
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
@@ -245,7 +253,9 @@
@Test
public void testFailedStartUserInForeground() {
- mUserController.mUserSwitchUiEnabled = false;
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
mUserController.startUserInForeground(NONEXIST_USER_ID);
verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
verify(mInjector.getWindowManager()).setSwitchingUser(false);
@@ -326,7 +336,9 @@
@Test
public void testContinueUserSwitchUIDisabled() throws RemoteException {
- mUserController.mUserSwitchUiEnabled = false;
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, true);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -389,11 +401,13 @@
* Test stopping of user from max running users limit.
*/
@Test
- public void testUserStoppingForMultipleUsersNormalMode()
+ public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking()
throws InterruptedException, RemoteException {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
- mUserController.mMaxRunningUsers = 3;
int numerOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
numerOfUserSwitches, false);
@@ -430,12 +444,13 @@
* all middle steps which takes too much work to mock.
*/
@Test
- public void testUserStoppingForMultipleUsersDelayedLockingMode()
+ public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
throws InterruptedException, RemoteException {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
- mUserController.mMaxRunningUsers = 3;
- mUserController.mDelayUserDataLocking = true;
int numerOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
numerOfUserSwitches, false);
@@ -456,7 +471,7 @@
// Skip all other steps and test unlock delaying only
UserState uss = mUserStates.get(TEST_USER_ID);
uss.setState(UserState.STATE_SHUTDOWN); // necessary state change from skipped part
- mUserController.finishUserStopped(uss);
+ mUserController.finishUserStopped(uss, /* allowDelayedLocking= */ true);
// Cannot mock FgThread handler, so confirm that there is no posted message left before
// checking.
waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
@@ -473,12 +488,87 @@
mUserController.getRunningUsersLU());
UserState ussUser1 = mUserStates.get(TEST_USER_ID1);
ussUser1.setState(UserState.STATE_SHUTDOWN);
- mUserController.finishUserStopped(ussUser1);
+ mUserController.finishUserStopped(ussUser1, /* allowDelayedLocking= */ true);
waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
verify(mInjector.mStorageManagerMock, times(1))
.lockUserKey(TEST_USER_ID);
}
+ /**
+ * Test locking user with mDelayUserDataLocking false.
+ */
+ @Test
+ public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
+ setUpAndStartUserInBackground(TEST_USER_ID);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID1);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID2);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID3);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+ }
+
+ /**
+ * Test conditional delayed locking with mDelayUserDataLocking true.
+ */
+ @Test
+ public void testUserLockingForDelayedLockingMode() throws Exception {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
+ // delayedLocking set and no KeyEvictedCallback, so it should not lock.
+ setUpAndStartUserInBackground(TEST_USER_ID);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ false);
+
+ setUpAndStartUserInBackground(TEST_USER_ID1);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID2);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID3);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+ }
+
+ private void setUpAndStartUserInBackground(int userId) throws Exception {
+ setUpUser(userId, 0);
+ mUserController.startUser(userId, /* foreground= */ false);
+ verify(mInjector.mStorageManagerMock, times(1))
+ .unlockUserKey(TEST_USER_ID, 0, null, null);
+ mUserStates.put(userId, mUserController.getStartedUserState(userId));
+ }
+
+ private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking,
+ KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception {
+ int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */
+ delayedLocking, null, keyEvictedCallback);
+ assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
+ // fake all interim steps
+ UserState ussUser = mUserStates.get(userId);
+ ussUser.setState(UserState.STATE_SHUTDOWN);
+ // Passing delayedLocking invalidates incorrect internal data passing but currently there is
+ // no easy way to get that information passed through lambda.
+ mUserController.finishUserStopped(ussUser, delayedLocking);
+ waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
+ verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
+ .lockUserKey(userId);
+ }
+
private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
int expectedNumberOfCalls, boolean expectOldUserStopping)
throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index f96d996..bec265e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -411,7 +411,8 @@
// HAT sent to keystore
verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
// Send onAuthenticated to client
- verify(mReceiver1).onAuthenticationSucceeded();
+ verify(mReceiver1).onAuthenticationSucceeded(
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
// Current session becomes null
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -461,7 +462,8 @@
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
waitForIdle();
verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
- verify(mReceiver1).onAuthenticationSucceeded();
+ verify(mReceiver1).onAuthenticationSucceeded(
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index abe39f0..312ff2c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -223,4 +223,22 @@
Utils.biometricConstantsToBiometricManager(testCases[i][0]));
}
}
+
+ @Test
+ public void testGetAuthenticationTypeForResult_getsCorrectType() {
+ assertEquals(Utils.getAuthenticationTypeForResult(
+ BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL);
+ assertEquals(Utils.getAuthenticationTypeForResult(
+ BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED),
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+ assertEquals(Utils.getAuthenticationTypeForResult(
+ BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED),
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetAuthResultType_throwsForInvalidReason() {
+ Utils.getAuthenticationTypeForResult(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 45729e5..d670361 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1968,6 +1968,29 @@
// TODO Make sure restrictions are written to the file.
}
+ private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
+ Sets.newSet(
+ UserManager.DISALLOW_CONFIG_DATE_TIME,
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_BLUETOOTH,
+ UserManager.DISALLOW_BLUETOOTH_SHARING,
+ UserManager.DISALLOW_CONFIG_BLUETOOTH,
+ UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+ UserManager.DISALLOW_CONFIG_LOCATION,
+ UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+ UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+ UserManager.DISALLOW_CONFIG_TETHERING,
+ UserManager.DISALLOW_CONFIG_WIFI,
+ UserManager.DISALLOW_CONTENT_CAPTURE,
+ UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+ UserManager.DISALLOW_DATA_ROAMING,
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
+ UserManager.DISALLOW_SAFE_BOOT,
+ UserManager.DISALLOW_SHARE_LOCATION,
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_USB_FILE_TRANSFER
+ );
+
public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
final int MANAGED_PROFILE_ADMIN_UID =
@@ -1980,15 +2003,9 @@
when(getServices().userManager.getProfileParent(MANAGED_PROFILE_USER_ID))
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
- parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
- verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(MANAGED_PROFILE_USER_ID),
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_CONFIG_DATE_TIME),
- eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
- reset(getServices().userManagerInternal);
-
- parentDpm.clearUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
- reset(getServices().userManagerInternal);
+ for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS) {
+ addAndRemoveUserRestrictionOnParentDpm(restriction);
+ }
parentDpm.setCameraDisabled(admin1, true);
verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
@@ -2005,6 +2022,20 @@
reset(getServices().userManagerInternal);
}
+ private void addAndRemoveUserRestrictionOnParentDpm(String restriction) {
+ parentDpm.addUserRestriction(admin1, restriction);
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(DpmMockContext.CALLER_USER_HANDLE),
+ MockUtils.checkUserRestrictions(restriction),
+ eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
+ parentDpm.clearUserRestriction(admin1, restriction);
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(),
+ parentDpm.getUserRestrictions(admin1)
+ );
+ reset(getServices().userManagerInternal);
+ }
+
public void testNoDefaultEnabledUserRestrictions() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_USERS);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index 7a070ee..eda2b70 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -188,7 +188,7 @@
}
@Test
- public void testEvaluateRules_ruleNotInDNF_ignoreAndAllow() {
+ public void testEvaluateRules_orRules() {
CompoundFormula compoundFormula =
new CompoundFormula(
CompoundFormula.OR,
@@ -206,11 +206,11 @@
IntegrityCheckResult result =
RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
- assertEquals(ALLOW, result.getEffect());
+ assertEquals(DENY, result.getEffect());
}
@Test
- public void testEvaluateRules_compoundFormulaWithNot_allow() {
+ public void testEvaluateRules_compoundFormulaWithNot_deny() {
CompoundFormula openSubFormula =
new CompoundFormula(
CompoundFormula.AND,
@@ -230,7 +230,7 @@
IntegrityCheckResult result =
RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
- assertEquals(ALLOW, result.getEffect());
+ assertEquals(DENY, result.getEffect());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
index c7cc343..57274bf 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
@@ -53,17 +53,17 @@
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
- BitOutputStream bitOutputStream = new BitOutputStream();
+ BitOutputStream bitOutputStream = new BitOutputStream(byteTrackedOutputStream);
bitOutputStream.setNext(/* numOfBits= */5, /* value= */1);
- byteTrackedOutputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
// Even though we wrote 5 bits, this will complete to 1 byte.
assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(1);
// Add a bit less than 2 bytes (10 bits).
- bitOutputStream.clear();
bitOutputStream.setNext(/* numOfBits= */10, /* value= */1);
- byteTrackedOutputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
+ assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(3);
assertThat(outputStream.toByteArray().length).isEqualTo(3);
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
index 291cfee..742952e 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
@@ -115,8 +115,7 @@
+ getKeyValueString(START_INDEXING_KEY, 500)
+ getKeyValueString(END_INDEXING_KEY, 900)
+ getKeyValueString(START_INDEXING_KEY, 900)
- + getKeyValueString(END_INDEXING_KEY, 945)
- + getBits(1, 1));
+ + getKeyValueString(END_INDEXING_KEY, 945));
ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
rule.put(stringBytes);
InputStream inputStream = new ByteArrayInputStream(rule.array());
@@ -153,8 +152,7 @@
+ getKeyValueString("888", 800)
+ getKeyValueString(END_INDEXING_KEY, 900)
+ getKeyValueString(START_INDEXING_KEY, 900)
- + getKeyValueString(END_INDEXING_KEY, 945)
- + getBits(1, 1));
+ + getKeyValueString(END_INDEXING_KEY, 945));
ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
rule.put(stringBytes);
return new ByteArrayInputStream(rule.array());
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
index bc2aac0..8ee5f5f 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -139,10 +139,12 @@
SERIALIZED_START_INDEXING_KEY
+ getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
+ SERIALIZED_END_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */32);
+ + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
byte[] expectedIndexingBytes =
- getBytes(serializedIndexingBytes + serializedIndexingBytes
- + serializedIndexingBytes + getBits(1, 1));
+ getBytes(
+ serializedIndexingBytes
+ + serializedIndexingBytes
+ + serializedIndexingBytes);
expectedIndexingOutputStream.write(expectedIndexingBytes);
assertThat(indexingOutputStream.toByteArray())
.isEqualTo(expectedIndexingOutputStream.toByteArray());
@@ -200,11 +202,14 @@
SERIALIZED_START_INDEXING_KEY
+ getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
+ SERIALIZED_END_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length
- + getBytes(expectedBits).length, /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(getBytes(
- expectedIndexingBitsForIndexed + expectedIndexingBitsForIndexed
- + expectedIndexingBitsForUnindexed + getBits(1, 1)));
+ + getBits(
+ DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
+ /* numOfBits= */ 32);
+ expectedIndexingOutputStream.write(
+ getBytes(
+ expectedIndexingBitsForIndexed
+ + expectedIndexingBitsForIndexed
+ + expectedIndexingBitsForUnindexed));
assertThat(indexingOutputStream.toByteArray())
.isEqualTo(expectedIndexingOutputStream.toByteArray());
@@ -513,16 +518,19 @@
// and 225 non-indexed rules..
List<Rule> ruleList = new ArrayList();
for (int count = 0; count < ruleCount; count++) {
- ruleList.add(getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
+ ruleList.add(
+ getRuleWithPackageNameAndSampleInstallerName(
+ String.format("%s%04d", packagePrefix, count)));
}
for (int count = 0; count < ruleCount; count++) {
- ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
+ ruleList.add(
+ getRuleWithAppCertificateAndSampleInstallerName(
+ String.format("%s%04d", appCertificatePrefix, count)));
}
for (int count = 0; count < ruleCount; count++) {
- ruleList.add(getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
+ ruleList.add(
+ getNonIndexedRuleWithInstallerName(
+ String.format("%s%04d", installerNamePrefix, count)));
}
// Serialize the rules.
@@ -543,8 +551,7 @@
int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
String expectedIndexingBytesForPackageNameIndexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
for (int count = 0; count < ruleCount; count++) {
String packageName = String.format("%s%04d", packagePrefix, count);
if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
@@ -556,18 +563,17 @@
}
byte[] bytesForPackage =
- getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- packageName));
+ getBytes(
+ getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+ packageName));
expectedOrderedRuleOutputStream.write(bytesForPackage);
totalBytesWritten += bytesForPackage.length;
}
expectedIndexingBytesForPackageNameIndexed +=
- SERIALIZED_END_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
String expectedIndexingBytesForAppCertificateIndexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
for (int count = 0; count < ruleCount; count++) {
String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
@@ -579,33 +585,32 @@
}
byte[] bytesForPackage =
- getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- appCertificate));
+ getBytes(
+ getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+ appCertificate));
expectedOrderedRuleOutputStream.write(bytesForPackage);
totalBytesWritten += bytesForPackage.length;
}
expectedIndexingBytesForAppCertificateIndexed +=
- SERIALIZED_END_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
String expectedIndexingBytesForUnindexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
for (int count = 0; count < ruleCount; count++) {
byte[] bytesForPackage =
- getBytes(getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
- String.format("%s%04d", installerNamePrefix, count)));
+ getBytes(
+ getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
+ String.format("%s%04d", installerNamePrefix, count)));
expectedOrderedRuleOutputStream.write(bytesForPackage);
totalBytesWritten += bytesForPackage.length;
}
expectedIndexingBytesForUnindexed +=
- SERIALIZED_END_INDEXING_KEY
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
expectedIndexingOutputStream.write(
- getBytes(expectedIndexingBytesForPackageNameIndexed
- + expectedIndexingBytesForAppCertificateIndexed
- + expectedIndexingBytesForUnindexed
- + getBits(1, 1)));
+ getBytes(
+ expectedIndexingBytesForPackageNameIndexed
+ + expectedIndexingBytesForAppCertificateIndexed
+ + expectedIndexingBytesForUnindexed));
assertThat(ruleOutputStream.toByteArray())
.isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index e1c489e..355cada 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -112,7 +112,7 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Binder;
import android.os.Handler;
import android.os.INetworkManagementService;
@@ -1804,9 +1804,11 @@
.getService(NetworkPolicyManagerInternal.class);
npmi.onStatsProviderLimitReached("TEST");
- // Verifies that the limit reached leads to a force update.
+ // Verifies that the limit reached leads to a force update and new limit should be set.
postMsgAndWaitForCompletion();
verify(mStatsService).forceUpdate();
+ postMsgAndWaitForCompletion();
+ verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L - 1999L);
}
/**
@@ -1911,7 +1913,8 @@
if (!roaming) {
nc.addCapability(NET_CAPABILITY_NOT_ROAMING);
}
- nc.setNetworkSpecifier(new StringNetworkSpecifier(String.valueOf(subId)));
+ nc.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(subId).build());
return nc;
}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index cc170af..c56034a 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -335,7 +335,9 @@
}).when(driver).getProperties_2_3(any());
}
- mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
+ mService = new SoundTriggerMiddlewareImpl(() -> {
+ return mHalDriver;
+ }, mAudioSessionProvider);
}
private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module,
@@ -798,12 +800,12 @@
@Override
public boolean linkToDeath(DeathRecipient recipient, long cookie) {
- throw new UnsupportedOperationException();
+ return true;
}
@Override
public boolean unlinkToDeath(DeathRecipient recipient) {
- throw new UnsupportedOperationException();
+ return true;
}
};
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 8a3183f..d940a6a 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -651,7 +651,6 @@
@Override
public long systemClockMillis() {
- assertWakeLockAcquired();
return mSystemClockMillis;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index bd7d9ec..f5af3ec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -64,7 +64,8 @@
NotificationChannel updatedChannel =
new NotificationChannel("a", "", IMPORTANCE_HIGH);
- when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(null), eq(false)))
+ when(mConfig.getConversationNotificationChannel(
+ any(), anyInt(), eq("a"), eq(null), eq(true), eq(false)))
.thenReturn(updatedChannel);
assertNull(extractor.process(r));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 62f5230..2ac4642 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -471,16 +471,17 @@
mTestableLooper.processAllMessages();
}
- private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled,
- boolean channelEnabled) {
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
- when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
- when(mPreferencesHelper.getNotificationChannel(
- anyString(), anyInt(), anyString(), eq(null), anyBoolean())).thenReturn(
- mTestNotificationChannel);
- when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
- mTestNotificationChannel.getImportance());
+ private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled,
+ boolean pkgEnabled, boolean channelEnabled) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_BUBBLES, globalEnabled ? 1 : 0);
+ mService.mPreferencesHelper.updateBubblesEnabled();
+ assertEquals(globalEnabled, mService.mPreferencesHelper.bubblesEnabled());
+ try {
+ mBinderService.setBubblesAllowed(pkg, uid, pkgEnabled);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
mTestNotificationChannel.setAllowBubbles(channelEnabled);
}
@@ -1763,8 +1764,8 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mPreferencesHelper, times(1)).getNotificationChannel(
- anyString(), anyInt(), eq("foo"), eq(null), anyBoolean());
+ verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
+ anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
}
@Test
@@ -1778,9 +1779,9 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
0, generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mPreferencesHelper, times(1)).getNotificationChannel(
+ verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
- anyBoolean());
+ anyBoolean(), anyBoolean());
}
@Test
@@ -4759,7 +4760,7 @@
@Test
public void testFlagBubble() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr =
generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble");
@@ -4778,7 +4779,7 @@
@Test
public void testFlagBubble_noFlag_appNotAllowed() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, false /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubble_noFlag_appNotAllowed");
@@ -4797,7 +4798,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif with bubble metadata but not our other misc requirements
Notification.Builder nb = new Notification.Builder(mContext,
@@ -4825,7 +4826,7 @@
@Test
public void testFlagBubbleNotifs_flag_messaging() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_flag_messaging");
@@ -4842,7 +4843,7 @@
@Test
public void testFlagBubbleNotifs_flag_phonecall() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4878,7 +4879,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noForegroundService() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4911,7 +4912,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noPerson() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4942,7 +4943,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noCategory() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -4977,7 +4978,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException {
// Bubbles are NOT allowed!
- setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_noFlag_messaging_appNotAllowed");
@@ -4995,7 +4996,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_notBubble() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Messaging notif WITHOUT bubble metadata
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
@@ -5019,7 +5020,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed() throws RemoteException {
// Bubbles are allowed except on this channel
- setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed");
@@ -5037,7 +5038,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_notAllowed() throws RemoteException {
// Bubbles are not allowed!
- setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, false /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -5072,7 +5073,7 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed() throws RemoteException {
// Bubbles are allowed, but not on channel.
- setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
// Give it bubble metadata
Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
@@ -5280,7 +5281,7 @@
@Test
public void testNotificationBubbleChanged_false() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif with bubble metadata
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
@@ -5311,7 +5312,7 @@
@Test
public void testNotificationBubbleChanged_true() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif that is not a bubble
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
@@ -5348,7 +5349,7 @@
@Test
public void testNotificationBubbleChanged_true_notAllowed() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif that is not a bubble
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
@@ -5547,7 +5548,7 @@
@Test
public void testNotificationBubbles_disabled_lowRamDevice() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// And we are low ram
when(mActivityManager.isLowRamDevice()).thenReturn(true);
@@ -5631,7 +5632,7 @@
public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testNotificationBubbles_flagAutoExpandForeground_fails_notForeground");
@@ -5661,7 +5662,7 @@
public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
"testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground");
@@ -5691,7 +5692,7 @@
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
true /* summaryAutoCancel */);
@@ -5714,7 +5715,7 @@
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
true /* summaryAutoCancel */);
@@ -5836,7 +5837,7 @@
mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend");
NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel(
- PKG, 0, PKG, original.getId(), "friend");
+ PKG, 0, PKG, original.getId(), false, "friend");
assertEquals(original.getName(), friendChannel.getName());
assertEquals(original.getId(), friendChannel.getParentChannelId());
@@ -5874,9 +5875,9 @@
conversationId);
NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel(
- PKG, 0, PKG, messagesParent.getId(), conversationId);
+ PKG, 0, PKG, messagesParent.getId(), false, conversationId);
NotificationChannel callsChild = mBinderService.getConversationNotificationChannel(
- PKG, 0, PKG, callsParent.getId(), conversationId);
+ PKG, 0, PKG, callsParent.getId(), false, conversationId);
assertEquals(messagesParent.getId(), messagesChild.getParentChannelId());
assertEquals(conversationId, messagesChild.getConversationId());
@@ -5887,8 +5888,8 @@
mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId);
assertNull(mBinderService.getConversationNotificationChannel(
- PKG, 0, PKG, messagesParent.getId(), conversationId));
+ PKG, 0, PKG, messagesParent.getId(), false, conversationId));
assertNull(mBinderService.getConversationNotificationChannel(
- PKG, 0, PKG, callsParent.getId(), conversationId));
+ PKG, 0, PKG, callsParent.getId(), false, conversationId));
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 7173e0c..7ac45f0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -354,6 +354,7 @@
channel2.setGroup(ncg.getId());
channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
channel2.setLightColor(Color.BLUE);
+ channel2.setConversationId("id1", "conversation");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
@@ -2822,8 +2823,20 @@
friend.setConversationId(parent.getId(), conversationId);
mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
- compareChannelsParentChild(parent, mHelper.getNotificationChannel(
- PKG_O, UID_O, parent.getId(), conversationId, false), conversationId);
+ compareChannelsParentChild(parent, mHelper.getConversationNotificationChannel(
+ PKG_O, UID_O, parent.getId(), conversationId, false, false), conversationId);
+ }
+
+ @Test
+ public void testGetNotificationChannel_conversationProvidedByNotCustomizedYet() {
+ String conversationId = "friend";
+
+ NotificationChannel parent =
+ new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+ compareChannels(parent, mHelper.getConversationNotificationChannel(
+ PKG_O, UID_O, parent.getId(), conversationId, true, false));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 399cf49..135d005 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -44,6 +45,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.app.BlockedAppActivity;
import com.android.internal.app.HarmfulAppWarningActivity;
import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.app.UnlaunchableAppActivity;
@@ -105,6 +107,8 @@
private PackageManagerService mPackageManager;
@Mock
private ActivityManagerInternal mAmInternal;
+ @Mock
+ private LockTaskController mLockTaskController;
private ActivityStartInterceptor mInterceptor;
private ActivityInfo mAInfo = new ActivityInfo();
@@ -145,6 +149,13 @@
when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
.thenReturn(null);
+ // Mock LockTaskController
+ mAInfo.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+ when(mService.getLockTaskController()).thenReturn(mLockTaskController);
+ when(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+ .thenReturn(true);
+
// Initialise activity info
mAInfo.applicationInfo = new ApplicationInfo();
mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
@@ -196,6 +207,18 @@
}
@Test
+ public void testInterceptLockTaskModeViolationPackage() {
+ when(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+ .thenReturn(false);
+
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+
+ assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME)
+ .filterEquals(mInterceptor.mIntent));
+ }
+
+ @Test
public void testInterceptQuietProfile() {
// GIVEN that the user the activity is starting as is currently in quiet mode
when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 079c49f..d22502d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -27,6 +27,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
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.ArgumentMatchers.any;
@@ -153,6 +154,19 @@
}
@Test
+ public void testContainerChanges() {
+ removeGlobalMinSizeRestriction();
+ final ActivityStack stack = new StackBuilder(mRootWindowContainer)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ final Task task = stack.getTopMostTask();
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ assertTrue(task.isFocusable());
+ t.setFocusable(stack.mRemoteToken, false);
+ mService.applyContainerTransaction(t);
+ assertFalse(task.isFocusable());
+ }
+
+ @Test
public void testDisplayWindowListener() {
final ArrayList<Integer> added = new ArrayList<>();
final ArrayList<Integer> changed = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index 039ff60..75ec53d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -29,6 +29,9 @@
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
import static android.os.Process.SYSTEM_UID;
import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
@@ -693,6 +696,38 @@
assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
}
+ @Test
+ public void testIsActivityAllowed() {
+ // WHEN lock task mode is not enabled
+ assertTrue(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+ // WHEN lock task mode is enabled
+ Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+ mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+
+ // package with LOCK_TASK_LAUNCH_MODE_ALWAYS should always be allowed
+ assertTrue(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_ALWAYS));
+
+ // unwhitelisted package should not be allowed
+ assertFalse(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+ // update the whitelist
+ String[] whitelist = new String[] { TEST_PACKAGE_NAME };
+ mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+
+ // whitelisted package should be allowed
+ assertTrue(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+ // package with LOCK_TASK_LAUNCH_MODE_NEVER should never be allowed
+ assertFalse(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_NEVER));
+ }
+
private Task getTask(int lockTaskAuth) {
return getTask(TEST_PACKAGE_NAME, lockTaskAuth);
}
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 04d79ca..ea8d082 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -253,12 +253,12 @@
// Under split screen primary we should be focusable when not minimized
mRootWindowContainer.setDockedStackMinimized(false);
- assertTrue(stack.isFocusable());
+ assertTrue(stack.isTopActivityFocusable());
assertTrue(activity.isFocusable());
// Under split screen primary we should not be focusable when minimized
mRootWindowContainer.setDockedStackMinimized(true);
- assertFalse(stack.isFocusable());
+ assertFalse(stack.isTopActivityFocusable());
assertFalse(activity.isFocusable());
final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack(
@@ -267,19 +267,19 @@
.setStack(pinnedStack).build();
// We should not be focusable when in pinned mode
- assertFalse(pinnedStack.isFocusable());
+ assertFalse(pinnedStack.isTopActivityFocusable());
assertFalse(pinnedActivity.isFocusable());
// Add flag forcing focusability.
pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE;
// We should not be focusable when in pinned mode
- assertTrue(pinnedStack.isFocusable());
+ assertTrue(pinnedStack.isTopActivityFocusable());
assertTrue(pinnedActivity.isFocusable());
// Without the overridding activity, stack should not be focusable.
pinnedStack.removeChild(pinnedActivity.getTask(), "testFocusability");
- assertFalse(pinnedStack.isFocusable());
+ assertFalse(pinnedStack.isTopActivityFocusable());
}
/**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8397aa4..b8cd378 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2069,6 +2069,13 @@
}
@Override
+ public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
+ boolean shouldObfuscateInstantApps) {
+ return UsageStatsService.this.queryEvents(
+ userId, beginTime, endTime, shouldObfuscateInstantApps);
+ }
+
+ @Override
public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index bde2cfd..7099c09 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -756,7 +756,9 @@
return;
}
- model.setStopped();
+ if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ model.setStopped();
+ }
try {
callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
@@ -900,7 +902,9 @@
return;
}
- modelData.setStopped();
+ if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ modelData.setStopped();
+ }
try {
modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a8852a8..826a89e 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -568,6 +568,7 @@
private final Bundle mExtras;
private final Bundle mIntentExtras;
private final long mCreationTimeMillis;
+ private final String mContactDisplayName;
private final @CallDirection int mCallDirection;
private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
@@ -872,6 +873,17 @@
}
/**
+ * Returns the name of the caller on the remote end, as derived from a
+ * {@link android.provider.ContactsContract} lookup of the call's handle.
+ * @return The name of the caller, or {@code null} if the lookup is not yet complete, if
+ * there's no contacts entry for the caller, or if the {@link InCallService} does
+ * not hold the {@link android.Manifest.permission#READ_CONTACTS} permission.
+ */
+ public @Nullable String getContactDisplayName() {
+ return mContactDisplayName;
+ }
+
+ /**
* Indicates whether the call is an incoming or outgoing call.
* @return The call's direction.
*/
@@ -909,6 +921,7 @@
areBundlesEqual(mExtras, d.mExtras) &&
areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
+ Objects.equals(mContactDisplayName, d.mContactDisplayName) &&
Objects.equals(mCallDirection, d.mCallDirection) &&
Objects.equals(mCallerNumberVerificationStatus,
d.mCallerNumberVerificationStatus);
@@ -933,6 +946,7 @@
mExtras,
mIntentExtras,
mCreationTimeMillis,
+ mContactDisplayName,
mCallDirection,
mCallerNumberVerificationStatus);
}
@@ -955,6 +969,7 @@
Bundle extras,
Bundle intentExtras,
long creationTimeMillis,
+ String contactDisplayName,
int callDirection,
int callerNumberVerificationStatus) {
mTelecomCallId = telecomCallId;
@@ -973,6 +988,7 @@
mExtras = extras;
mIntentExtras = intentExtras;
mCreationTimeMillis = creationTimeMillis;
+ mContactDisplayName = contactDisplayName;
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
}
@@ -996,6 +1012,7 @@
parcelableCall.getExtras(),
parcelableCall.getIntentExtras(),
parcelableCall.getCreationTimeMillis(),
+ parcelableCall.getContactDisplayName(),
parcelableCall.getCallDirection(),
parcelableCall.getCallerNumberVerificationStatus());
}
@@ -1445,6 +1462,7 @@
private boolean mChildrenCached;
private String mParentId = null;
+ private String mActiveGenericConferenceChild = null;
private int mState;
private List<String> mCannedTextResponses = null;
private String mCallingPackage;
@@ -1943,6 +1961,20 @@
}
/**
+ * Returns the child {@link Call} in a generic conference that is currently active.
+ * For calls that are not generic conferences, or when the generic conference has more than
+ * 2 children, returns {@code null}.
+ * @see Details#PROPERTY_GENERIC_CONFERENCE
+ * @return The active child call.
+ */
+ public @Nullable Call getGenericConferenceActiveChildCall() {
+ if (mActiveGenericConferenceChild != null) {
+ return mPhone.internalGetCallByTelecomId(mActiveGenericConferenceChild);
+ }
+ return null;
+ }
+
+ /**
* Obtains a list of canned, pre-configured message responses to present to the user as
* ways of rejecting this {@code Call} using via a text message.
*
@@ -2190,6 +2222,13 @@
mChildrenCached = false;
}
+ String activeChildCallId = parcelableCall.getActiveChildCallId();
+ boolean activeChildChanged = !Objects.equals(activeChildCallId,
+ mActiveGenericConferenceChild);
+ if (activeChildChanged) {
+ mActiveGenericConferenceChild = activeChildCallId;
+ }
+
List<String> conferenceableCallIds = parcelableCall.getConferenceableCallIds();
List<Call> conferenceableCalls = new ArrayList<Call>(conferenceableCallIds.size());
for (String otherId : conferenceableCallIds) {
@@ -2249,7 +2288,7 @@
if (parentChanged) {
fireParentChanged(getParent());
}
- if (childrenChanged) {
+ if (childrenChanged || activeChildChanged) {
fireChildrenChanged(getChildren());
}
if (isRttChanged) {
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index be4e2f4..415a817 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
@@ -36,6 +37,265 @@
* @hide
*/
public final class ParcelableCall implements Parcelable {
+
+ public static class ParcelableCallBuilder {
+ private String mId;
+ private int mState;
+ private DisconnectCause mDisconnectCause;
+ private List<String> mCannedSmsResponses;
+ private int mCapabilities;
+ private int mProperties;
+ private int mSupportedAudioRoutes;
+ private long mConnectTimeMillis;
+ private Uri mHandle;
+ private int mHandlePresentation;
+ private String mCallerDisplayName;
+ private int mCallerDisplayNamePresentation;
+ private GatewayInfo mGatewayInfo;
+ private PhoneAccountHandle mAccountHandle;
+ private boolean mIsVideoCallProviderChanged;
+ private IVideoProvider mVideoCallProvider;
+ private boolean mIsRttCallChanged;
+ private ParcelableRttCall mRttCall;
+ private String mParentCallId;
+ private List<String> mChildCallIds;
+ private StatusHints mStatusHints;
+ private int mVideoState;
+ private List<String> mConferenceableCallIds;
+ private Bundle mIntentExtras;
+ private Bundle mExtras;
+ private long mCreationTimeMillis;
+ private int mCallDirection;
+ private int mCallerNumberVerificationStatus;
+ private String mContactDisplayName;
+ private String mActiveChildCallId;
+
+ public ParcelableCallBuilder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ public ParcelableCallBuilder setState(int state) {
+ mState = state;
+ return this;
+ }
+
+ public ParcelableCallBuilder setDisconnectCause(DisconnectCause disconnectCause) {
+ mDisconnectCause = disconnectCause;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCannedSmsResponses(List<String> cannedSmsResponses) {
+ mCannedSmsResponses = cannedSmsResponses;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCapabilities(int capabilities) {
+ mCapabilities = capabilities;
+ return this;
+ }
+
+ public ParcelableCallBuilder setProperties(int properties) {
+ mProperties = properties;
+ return this;
+ }
+
+ public ParcelableCallBuilder setSupportedAudioRoutes(int supportedAudioRoutes) {
+ mSupportedAudioRoutes = supportedAudioRoutes;
+ return this;
+ }
+
+ public ParcelableCallBuilder setConnectTimeMillis(long connectTimeMillis) {
+ mConnectTimeMillis = connectTimeMillis;
+ return this;
+ }
+
+ public ParcelableCallBuilder setHandle(Uri handle) {
+ mHandle = handle;
+ return this;
+ }
+
+ public ParcelableCallBuilder setHandlePresentation(int handlePresentation) {
+ mHandlePresentation = handlePresentation;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallerDisplayName(String callerDisplayName) {
+ mCallerDisplayName = callerDisplayName;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallerDisplayNamePresentation(
+ int callerDisplayNamePresentation) {
+ mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+ return this;
+ }
+
+ public ParcelableCallBuilder setGatewayInfo(GatewayInfo gatewayInfo) {
+ mGatewayInfo = gatewayInfo;
+ return this;
+ }
+
+ public ParcelableCallBuilder setAccountHandle(PhoneAccountHandle accountHandle) {
+ mAccountHandle = accountHandle;
+ return this;
+ }
+
+ public ParcelableCallBuilder setIsVideoCallProviderChanged(
+ boolean isVideoCallProviderChanged) {
+ mIsVideoCallProviderChanged = isVideoCallProviderChanged;
+ return this;
+ }
+
+ public ParcelableCallBuilder setVideoCallProvider(IVideoProvider videoCallProvider) {
+ mVideoCallProvider = videoCallProvider;
+ return this;
+ }
+
+ public ParcelableCallBuilder setIsRttCallChanged(boolean isRttCallChanged) {
+ mIsRttCallChanged = isRttCallChanged;
+ return this;
+ }
+
+ public ParcelableCallBuilder setRttCall(ParcelableRttCall rttCall) {
+ mRttCall = rttCall;
+ return this;
+ }
+
+ public ParcelableCallBuilder setParentCallId(String parentCallId) {
+ mParentCallId = parentCallId;
+ return this;
+ }
+
+ public ParcelableCallBuilder setChildCallIds(List<String> childCallIds) {
+ mChildCallIds = childCallIds;
+ return this;
+ }
+
+ public ParcelableCallBuilder setStatusHints(StatusHints statusHints) {
+ mStatusHints = statusHints;
+ return this;
+ }
+
+ public ParcelableCallBuilder setVideoState(int videoState) {
+ mVideoState = videoState;
+ return this;
+ }
+
+ public ParcelableCallBuilder setConferenceableCallIds(
+ List<String> conferenceableCallIds) {
+ mConferenceableCallIds = conferenceableCallIds;
+ return this;
+ }
+
+ public ParcelableCallBuilder setIntentExtras(Bundle intentExtras) {
+ mIntentExtras = intentExtras;
+ return this;
+ }
+
+ public ParcelableCallBuilder setExtras(Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCreationTimeMillis(long creationTimeMillis) {
+ mCreationTimeMillis = creationTimeMillis;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallDirection(int callDirection) {
+ mCallDirection = callDirection;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallerNumberVerificationStatus(
+ int callerNumberVerificationStatus) {
+ mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+ return this;
+ }
+
+ public ParcelableCallBuilder setContactDisplayName(String contactDisplayName) {
+ mContactDisplayName = contactDisplayName;
+ return this;
+ }
+
+ public ParcelableCallBuilder setActiveChildCallId(String activeChildCallId) {
+ mActiveChildCallId = activeChildCallId;
+ return this;
+ }
+
+ public ParcelableCall createParcelableCall() {
+ return new ParcelableCall(
+ mId,
+ mState,
+ mDisconnectCause,
+ mCannedSmsResponses,
+ mCapabilities,
+ mProperties,
+ mSupportedAudioRoutes,
+ mConnectTimeMillis,
+ mHandle,
+ mHandlePresentation,
+ mCallerDisplayName,
+ mCallerDisplayNamePresentation,
+ mGatewayInfo,
+ mAccountHandle,
+ mIsVideoCallProviderChanged,
+ mVideoCallProvider,
+ mIsRttCallChanged,
+ mRttCall,
+ mParentCallId,
+ mChildCallIds,
+ mStatusHints,
+ mVideoState,
+ mConferenceableCallIds,
+ mIntentExtras,
+ mExtras,
+ mCreationTimeMillis,
+ mCallDirection,
+ mCallerNumberVerificationStatus,
+ mContactDisplayName,
+ mActiveChildCallId);
+ }
+
+ public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
+ ParcelableCallBuilder newBuilder = new ParcelableCallBuilder();
+ newBuilder.mId = parcelableCall.mId;
+ newBuilder.mState = parcelableCall.mState;
+ newBuilder.mDisconnectCause = parcelableCall.mDisconnectCause;
+ newBuilder.mCannedSmsResponses = parcelableCall.mCannedSmsResponses;
+ newBuilder.mCapabilities = parcelableCall.mCapabilities;
+ newBuilder.mProperties = parcelableCall.mProperties;
+ newBuilder.mSupportedAudioRoutes = parcelableCall.mSupportedAudioRoutes;
+ newBuilder.mConnectTimeMillis = parcelableCall.mConnectTimeMillis;
+ newBuilder.mHandle = parcelableCall.mHandle;
+ newBuilder.mHandlePresentation = parcelableCall.mHandlePresentation;
+ newBuilder.mCallerDisplayName = parcelableCall.mCallerDisplayName;
+ newBuilder.mCallerDisplayNamePresentation =
+ parcelableCall.mCallerDisplayNamePresentation;
+ newBuilder.mGatewayInfo = parcelableCall.mGatewayInfo;
+ newBuilder.mAccountHandle = parcelableCall.mAccountHandle;
+ newBuilder.mIsVideoCallProviderChanged = parcelableCall.mIsVideoCallProviderChanged;
+ newBuilder.mVideoCallProvider = parcelableCall.mVideoCallProvider;
+ newBuilder.mIsRttCallChanged = parcelableCall.mIsRttCallChanged;
+ newBuilder.mRttCall = parcelableCall.mRttCall;
+ newBuilder.mParentCallId = parcelableCall.mParentCallId;
+ newBuilder.mChildCallIds = parcelableCall.mChildCallIds;
+ newBuilder.mStatusHints = parcelableCall.mStatusHints;
+ newBuilder.mVideoState = parcelableCall.mVideoState;
+ newBuilder.mConferenceableCallIds = parcelableCall.mConferenceableCallIds;
+ newBuilder.mIntentExtras = parcelableCall.mIntentExtras;
+ newBuilder.mExtras = parcelableCall.mExtras;
+ newBuilder.mCreationTimeMillis = parcelableCall.mCreationTimeMillis;
+ newBuilder.mCallDirection = parcelableCall.mCallDirection;
+ newBuilder.mCallerNumberVerificationStatus =
+ parcelableCall.mCallerNumberVerificationStatus;
+ newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
+ newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+ return newBuilder;
+ }
+ }
+
private final String mId;
private final int mState;
private final DisconnectCause mDisconnectCause;
@@ -65,6 +325,8 @@
private final long mCreationTimeMillis;
private final int mCallDirection;
private final int mCallerNumberVerificationStatus;
+ private final String mContactDisplayName;
+ private final String mActiveChildCallId; // Only valid for CDMA conferences
public ParcelableCall(
String id,
@@ -94,7 +356,10 @@
Bundle extras,
long creationTimeMillis,
int callDirection,
- int callerNumberVerificationStatus) {
+ int callerNumberVerificationStatus,
+ String contactDisplayName,
+ String activeChildCallId
+ ) {
mId = id;
mState = state;
mDisconnectCause = disconnectCause;
@@ -123,6 +388,8 @@
mCreationTimeMillis = creationTimeMillis;
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+ mContactDisplayName = contactDisplayName;
+ mActiveChildCallId = activeChildCallId;
}
/** The unique ID of the call. */
@@ -332,6 +599,21 @@
return mCallerNumberVerificationStatus;
}
+ /**
+ * @return the name of the remote party as derived from a contacts DB lookup.
+ */
+ public @Nullable String getContactDisplayName() {
+ return mContactDisplayName;
+ }
+
+ /**
+ * @return On a CDMA conference with two participants, returns the ID of the child call that's
+ * currently active.
+ */
+ public @Nullable String getActiveChildCallId() {
+ return mActiveChildCallId;
+ }
+
/** Responsible for creating ParcelableCall objects for deserialized Parcels. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR =
@@ -371,35 +653,40 @@
long creationTimeMillis = source.readLong();
int callDirection = source.readInt();
int callerNumberVerificationStatus = source.readInt();
- return new ParcelableCall(
- id,
- state,
- disconnectCause,
- cannedSmsResponses,
- capabilities,
- properties,
- supportedAudioRoutes,
- connectTimeMillis,
- handle,
- handlePresentation,
- callerDisplayName,
- callerDisplayNamePresentation,
- gatewayInfo,
- accountHandle,
- isVideoCallProviderChanged,
- videoCallProvider,
- isRttCallChanged,
- rttCall,
- parentCallId,
- childCallIds,
- statusHints,
- videoState,
- conferenceableCallIds,
- intentExtras,
- extras,
- creationTimeMillis,
- callDirection,
- callerNumberVerificationStatus);
+ String contactDisplayName = source.readString();
+ String activeChildCallId = source.readString();
+ return new ParcelableCallBuilder()
+ .setId(id)
+ .setState(state)
+ .setDisconnectCause(disconnectCause)
+ .setCannedSmsResponses(cannedSmsResponses)
+ .setCapabilities(capabilities)
+ .setProperties(properties)
+ .setSupportedAudioRoutes(supportedAudioRoutes)
+ .setConnectTimeMillis(connectTimeMillis)
+ .setHandle(handle)
+ .setHandlePresentation(handlePresentation)
+ .setCallerDisplayName(callerDisplayName)
+ .setCallerDisplayNamePresentation(callerDisplayNamePresentation)
+ .setGatewayInfo(gatewayInfo)
+ .setAccountHandle(accountHandle)
+ .setIsVideoCallProviderChanged(isVideoCallProviderChanged)
+ .setVideoCallProvider(videoCallProvider)
+ .setIsRttCallChanged(isRttCallChanged)
+ .setRttCall(rttCall)
+ .setParentCallId(parentCallId)
+ .setChildCallIds(childCallIds)
+ .setStatusHints(statusHints)
+ .setVideoState(videoState)
+ .setConferenceableCallIds(conferenceableCallIds)
+ .setIntentExtras(intentExtras)
+ .setExtras(extras)
+ .setCreationTimeMillis(creationTimeMillis)
+ .setCallDirection(callDirection)
+ .setCallerNumberVerificationStatus(callerNumberVerificationStatus)
+ .setContactDisplayName(contactDisplayName)
+ .setActiveChildCallId(activeChildCallId)
+ .createParcelableCall();
}
@Override
@@ -446,6 +733,8 @@
destination.writeLong(mCreationTimeMillis);
destination.writeInt(mCallDirection);
destination.writeInt(mCallerNumberVerificationStatus);
+ destination.writeString(mContactDisplayName);
+ destination.writeString(mActiveChildCallId);
}
@Override
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 5e2e554..0cfb8c5 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -138,7 +138,7 @@
* To check the SDK version for {@link PreciseDataConnectionState#getDataConnectionState}.
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long GET_DATA_CONNECTION_STATE_CODE_CHANGE = 147600208L;
/**
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 2c62d06..ad8ac76 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1624,7 +1624,8 @@
* @return Current data network type
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ @SystemApi
+ @TestApi
public @NetworkType int getDataNetworkType() {
final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0ffeccf..83c241a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1104,6 +1104,16 @@
*/
public static final int CDMA_ROAMING_MODE_ANY = 2;
+ /** @hide */
+ @IntDef(prefix = { "CDMA_ROAMING_MODE_" }, value = {
+ CDMA_ROAMING_MODE_RADIO_DEFAULT,
+ CDMA_ROAMING_MODE_HOME,
+ CDMA_ROAMING_MODE_AFFILIATED,
+ CDMA_ROAMING_MODE_ANY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CdmaRoamingMode{}
+
/**
* An unknown carrier id. It could either be subscription unavailable or the subscription
* carrier cannot be recognized. Unrecognized carriers here means
@@ -5439,7 +5449,7 @@
* To check the SDK version for {@link TelephonyManager#getDataState}.
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long GET_DATA_STATE_CODE_CHANGE = 147600208L;
/**
@@ -5525,7 +5535,7 @@
* To check the SDK version for {@link TelephonyManager#listen}.
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
private static final long LISTEN_CODE_CHANGE = 147600208L;
/**
@@ -8974,8 +8984,9 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public int getCdmaRoamingMode() {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @CdmaRoamingMode int getCdmaRoamingMode() {
int mode = CDMA_ROAMING_MODE_RADIO_DEFAULT;
try {
ITelephony telephony = getITelephony();
@@ -9002,8 +9013,9 @@
*
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setCdmaRoamingMode(int mode) {
+ public boolean setCdmaRoamingMode(@CdmaRoamingMode int mode) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -9015,6 +9027,36 @@
return false;
}
+ /** @hide */
+ @IntDef(flag = true, prefix = { "CDMA_SUBSCRIPTION_" }, value = {
+ CDMA_SUBSCRIPTION_UNKNOWN,
+ CDMA_SUBSCRIPTION_RUIM_SIM,
+ CDMA_SUBSCRIPTION_NV
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CdmaSubscription{}
+
+ /** Used for CDMA subscription mode, it'll be UNKNOWN if there is no Subscription source.
+ * @hide
+ */
+ @SystemApi
+ public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1;
+
+ /** Used for CDMA subscription mode: RUIM/SIM (default)
+ * @hide
+ */
+ @SystemApi
+ public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0;
+
+ /** Used for CDMA subscription mode: NV -> non-volatile memory
+ * @hide
+ */
+ @SystemApi
+ public static final int CDMA_SUBSCRIPTION_NV = 1;
+
+ /** @hide */
+ public static final int PREFERRED_CDMA_SUBSCRIPTION = CDMA_SUBSCRIPTION_RUIM_SIM;
+
/**
* Sets the subscription mode for CDMA phone to the given mode {@code mode}.
*
@@ -9022,14 +9064,15 @@
*
* @return {@code true} if successed.
*
- * @see Phone#CDMA_SUBSCRIPTION_UNKNOWN
- * @see Phone#CDMA_SUBSCRIPTION_RUIM_SIM
- * @see Phone#CDMA_SUBSCRIPTION_NV
+ * @see #CDMA_SUBSCRIPTION_UNKNOWN
+ * @see #CDMA_SUBSCRIPTION_RUIM_SIM
+ * @see #CDMA_SUBSCRIPTION_NV
*
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setCdmaSubscriptionMode(int mode) {
+ public boolean setCdmaSubscriptionMode(@CdmaSubscription int mode) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -10642,6 +10685,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param enabled control enable or disable carrier data.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10668,6 +10712,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param enabled control enable or disable radio.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10694,6 +10739,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param report control start/stop reporting network status.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 6005f77..9b739d3 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -25,8 +25,6 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.WorkerThread;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.RemoteException;
import android.telephony.CarrierConfigManager;
@@ -382,10 +380,6 @@
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull Callback callback) throws ImsException {
- if (!isImsAvailableOnDevice()) {
- throw new ImsException("IMS not available on device.",
- ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
- }
callback.setExecutor(executor);
try {
getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder());
@@ -597,39 +591,25 @@
/**
* Notify the framework that an RCS autoconfiguration XML file has been received for
* provisioning.
+ * <p>
+ * Requires Permission: Manifest.permission.MODIFY_PHONE_STATE or that the calling app has
+ * carrier privileges (see {@link #hasCarrierPrivileges}).
* @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed.
* @param isCompressed The XML file is compressed in gzip format and must be decompressed
* before being read.
- * @hide
+ *
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
if (config == null) {
throw new IllegalArgumentException("Must include a non-null config XML file.");
}
- // TODO: Connect to ImsConfigImplBase.
- throw new UnsupportedOperationException("notifyRcsAutoConfigurationReceived is not"
- + "supported");
- }
-
- private static boolean isImsAvailableOnDevice() {
- IPackageManager pm = IPackageManager.Stub.asInterface(
- TelephonyFrameworkInitializer
- .getTelephonyServiceManager()
- .getPackageManagerServiceRegisterer()
- .get());
- if (pm == null) {
- // For some reason package manger is not available.. This will fail internally anyways,
- // so do not throw error and allow.
- return true;
- }
try {
- return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0);
+ getITelephony().notifyRcsAutoConfigurationReceived(mSubId, config, isCompressed);
} catch (RemoteException e) {
- // For some reason package manger is not available.. This will fail internally anyways,
- // so do not throw error and allow.
+ throw e.rethrowAsRuntimeException();
}
- return true;
+
}
private static ITelephony getITelephony() {
diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
index 53e4596..57206c9 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
@@ -40,4 +40,5 @@
// Return result code defined in ImsConfig#OperationStatusConstants
int setConfigString(int item, String value);
void updateImsCarrierConfigs(in PersistableBundle bundle);
+ void notifyRcsAutoConfigurationReceived(in byte[] config, boolean isCompressed);
}
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index 60cf216..6a2638b 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -17,6 +17,7 @@
package android.telephony.ims.stub;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
@@ -200,6 +201,12 @@
}
}
+ @Override
+ public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed)
+ throws RemoteException {
+ getImsConfigImpl().notifyRcsAutoConfigurationReceived(config, isCompressed);
+ }
+
private void notifyImsConfigChanged(int item, int value) throws RemoteException {
getImsConfigImpl().notifyConfigChanged(item, value);
}
@@ -358,9 +365,9 @@
* @param config The XML file to be read, if not compressed, it should be in ASCII/UTF8 format.
* @param isCompressed The XML file is compressed in gzip format and must be decompressed
* before being read.
- * @hide
+ *
*/
- public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) {
+ public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
}
/**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 28f3974..cdb95a8 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2137,4 +2137,9 @@
* Command line command to enable or disable handling of CEP data for test purposes.
*/
oneway void setCepEnabled(boolean isCepEnabled);
+
+ /**
+ * Notify Rcs auto config received.
+ */
+ void notifyRcsAutoConfigurationReceived(int subId, in byte[] config, boolean isCompressed);
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 3877cc1..ce20311 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -23,12 +23,14 @@
import android.Manifest;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
import android.provider.DeviceConfig;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -185,12 +187,6 @@
*/
@Test
public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
- // When multiple staged sessions are installed on a device which doesn't support checkpoint,
- // only the 1st one will prevail. We have to check no other rollbacks available to ensure
- // TestApp.A is always the 1st and the only one to commit so rollback can work as intended.
- // If there are leftover rollbacks from previous tests, this assertion will fail.
- assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
-
Uninstall.packages(TestApp.A);
Install.single(TestApp.A1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
@@ -220,6 +216,64 @@
TestApp.A)).isNotNull();
}
+ /**
+ * Stage install an apk with rollback that will be later triggered by unattributable crash.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase1() throws Exception {
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+ Install.single(TestApp.A2).setEnableRollback().setStaged().commit();
+ }
+
+ /**
+ * Verify the rollback is available and then install another package with rollback.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ TestApp.A)).isNotNull();
+
+ // Install another package with rollback
+ Uninstall.packages(TestApp.B);
+ Install.single(TestApp.B1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+
+ Install.single(TestApp.B2).setEnableRollback().setStaged().commit();
+ }
+
+ /**
+ * Verify the rollbacks are available.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase3() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ TestApp.A)).isNotNull();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ TestApp.B)).isNotNull();
+ }
+
+ /**
+ * Verify the rollbacks are committed after crashing.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase4() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ TestApp.A)).isNotNull();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ TestApp.B)).isNotNull();
+ }
+
@Test
public void testNetworkFailedRollback_Phase1() throws Exception {
// Remove available rollbacks and uninstall NetworkStack on /data/
@@ -438,6 +492,7 @@
RollbackManager rm = RollbackUtils.getRollbackManager();
rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
.map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+ assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
}
private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
@@ -498,4 +553,11 @@
.executeShellCommand(cmd);
IoUtils.closeQuietly(pfd);
}
+
+ @Test
+ public void isCheckpointSupported() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+ assertThat(sm.isCheckpointSupported()).isTrue();
+ }
}
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 6daa6bc..181e29a 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -17,6 +17,7 @@
package com.android.tests.rollback.host;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -62,6 +63,7 @@
"rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
+ "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
getDevice().reboot();
+ runPhase("testCleanUp");
}
@After
@@ -95,7 +97,6 @@
@Test
public void testNativeWatchdogTriggersRollback() throws Exception {
- //Stage install ModuleMetadata package - this simulates a Mainline module update
runPhase("testNativeWatchdogTriggersRollback_Phase1");
// Reboot device to activate staged package
@@ -121,6 +122,40 @@
runPhase("testNativeWatchdogTriggersRollback_Phase3");
}
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll() throws Exception {
+ // This test requires committing multiple staged rollbacks
+ assumeTrue(isCheckpointSupported());
+
+ // Install a package with rollback enabled.
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1");
+ getDevice().reboot();
+
+ // Once previous staged install is applied, install another package
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2");
+ getDevice().reboot();
+
+ // Verify the new staged install has also been applied successfully.
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3");
+
+ // crash system_server enough times to trigger a rollback
+ crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
+
+ // Rollback should be committed automatically now.
+ // Give time for rollback to be committed. This could take a while,
+ // because we need all of the following to happen:
+ // 1. system_server comes back up and boot completes.
+ // 2. Rollback health observer detects updatable crashing signal.
+ // 3. Staged rollback session becomes ready.
+ // 4. Device actually reboots.
+ // So we give a generous timeout here.
+ assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+ getDevice().waitForDeviceAvailable();
+
+ // verify all available rollbacks have been committed
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4");
+ }
+
/**
* Tests failed network health check triggers watchdog staged rollbacks.
*/
@@ -244,4 +279,13 @@
// Find the NetworkStack path (can be NetworkStack.apk or NetworkStackNext.apk)
return getDevice().executeShellCommand("ls /system/priv-app/NetworkStack*/*.apk");
}
+
+ private boolean isCheckpointSupported() throws Exception {
+ try {
+ runPhase("isCheckpointSupported");
+ return true;
+ } catch (AssertionError ignore) {
+ return false;
+ }
+ }
}
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index a7328ac..6005cc3 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -27,8 +27,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.net.LinkProperties.CompareResult;
import android.net.LinkProperties.ProvisioningChange;
+import android.net.util.LinkPropertiesUtils.CompareResult;
import android.system.OsConstants;
import android.util.ArraySet;
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index daf187d..91c9a2a 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -22,6 +22,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.net.util.MacAddressUtils;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -122,11 +124,11 @@
for (MacAddress mac : multicastAddresses) {
String msg = mac.toString() + " expected to be a multicast address";
- assertTrue(msg, mac.isMulticastAddress());
+ assertTrue(msg, MacAddressUtils.isMulticastAddress(mac));
}
for (MacAddress mac : unicastAddresses) {
String msg = mac.toString() + " expected not to be a multicast address";
- assertFalse(msg, mac.isMulticastAddress());
+ assertFalse(msg, MacAddressUtils.isMulticastAddress(mac));
}
}
@@ -156,7 +158,7 @@
public void testMacAddressConversions() {
final int iterations = 10000;
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.createRandomUnicastAddress();
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
String stringRepr = mac.toString();
byte[] bytesRepr = mac.toByteArray();
@@ -188,7 +190,7 @@
final String expectedLocalOui = "26:5f:78";
final MacAddress base = MacAddress.fromString(anotherOui + ":0:0:0");
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.createRandomUnicastAddress(base, r);
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress(base, r);
String stringRepr = mac.toString();
assertTrue(stringRepr + " expected to be a locally assigned address",
@@ -199,7 +201,7 @@
}
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.createRandomUnicastAddress();
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
String stringRepr = mac.toString();
assertTrue(stringRepr + " expected to be a locally assigned address",
diff --git a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
new file mode 100644
index 0000000..47afed4
--- /dev/null
+++ b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+
+import android.telephony.SubscriptionManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link android.net.TelephonyNetworkSpecifier}.
+ */
+@SmallTest
+public class TelephonyNetworkSpecifierTest {
+ private static final int TEST_SUBID = 5;
+
+ /**
+ * Validate that IllegalArgumentException will be thrown if build TelephonyNetworkSpecifier
+ * without calling {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+ */
+ @Test
+ public void testBuilderBuildWithDefault() {
+ try {
+ new TelephonyNetworkSpecifier.Builder().build();
+ } catch (IllegalArgumentException iae) {
+ // expected, test pass
+ }
+ }
+
+ /**
+ * Validate that no exception will be thrown even if pass invalid subscription id to
+ * {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+ */
+ @Test
+ public void testBuilderBuildWithInvalidSubId() {
+ TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .build();
+ assertEquals(specifier.getSubscriptionId(), SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ /**
+ * Validate the correctness of TelephonyNetworkSpecifier when provide valid subId.
+ */
+ @Test
+ public void testBuilderBuildWithValidSubId() {
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ assertEquals(TEST_SUBID, specifier.getSubscriptionId());
+ }
+
+ /**
+ * Validate that parcel marshalling/unmarshalling works.
+ */
+ @Test
+ public void testParcel() {
+ TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ assertParcelSane(specifier, 1 /* fieldCount */);
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index b783467..de1028c 100644
--- a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -51,6 +51,7 @@
import android.net.NetworkPolicyManager;
import android.net.NetworkTemplate;
import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Handler;
import android.provider.Settings;
import android.telephony.TelephonyManager;
@@ -229,7 +230,7 @@
verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any());
// Simulate callback after capability changes
- final NetworkCapabilities capabilities = new NetworkCapabilities()
+ NetworkCapabilities capabilities = new NetworkCapabilities()
.addCapability(NET_CAPABILITY_INTERNET)
.addTransportType(TRANSPORT_CELLULAR)
.setNetworkSpecifier(new StringNetworkSpecifier("234"));
@@ -239,6 +240,19 @@
networkCallback.getValue().onCapabilitiesChanged(
TEST_NETWORK,
capabilities);
+
+ // make sure it also works with the new introduced TelephonyNetworkSpecifier
+ capabilities = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(234).build());
+ if (!roaming) {
+ capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
+ }
+ networkCallback.getValue().onCapabilitiesChanged(
+ TEST_NETWORK,
+ capabilities);
}
@Test
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 70c9bef..791da30 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -97,7 +97,6 @@
},
hostdex: true, // for hiddenapi check
visibility: [
- "//frameworks/base", // TODO(b/140299412) remove once all dependencies are fixed
"//frameworks/opt/net/wifi/service:__subpackages__",
] + test_access_hidden_api_whitelist,
apex_available: [
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index ee2b575..a9621fc 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -29,6 +29,7 @@
import android.net.ProxyInfo;
import android.net.StaticIpConfiguration;
import android.net.Uri;
+import android.net.util.MacAddressUtils;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -372,7 +373,6 @@
* ECDHE_ECDSA
* ECDHE_RSA
* </pre>
- * @hide
*/
public static class SuiteBCipher {
private SuiteBCipher() { }
@@ -1166,7 +1166,7 @@
* @return true if mac is good to use
*/
public static boolean isValidMacAddressForRandomization(MacAddress mac) {
- return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned()
+ return mac != null && !MacAddressUtils.isMulticastAddress(mac) && mac.isLocallyAssigned()
&& !MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS).equals(mac);
}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 8689a38..0ef75aa 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -27,6 +27,7 @@
import static org.junit.Assert.assertTrue;
import android.net.MacAddress;
+import android.net.util.MacAddressUtils;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.os.Parcel;
@@ -63,7 +64,7 @@
config.updateIdentifier = "1234";
config.fromWifiNetworkSpecifier = true;
config.fromWifiNetworkSuggestion = true;
- config.setRandomizedMacAddress(MacAddress.createRandomUnicastAddress());
+ config.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
MacAddress macBeforeParcel = config.getRandomizedMacAddress();
Parcel parcelW = Parcel.obtain();
config.writeToParcel(parcelW, 0);
@@ -169,7 +170,7 @@
MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
assertEquals(defaultMac, config.getRandomizedMacAddress());
- MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress();
+ MacAddress macToChangeInto = MacAddressUtils.createRandomUnicastAddress();
config.setRandomizedMacAddress(macToChangeInto);
MacAddress macAfterChange = config.getRandomizedMacAddress();