Merge "[Passpoint] Truncate IMSI from the SimCredential toString method"
diff --git a/Android.bp b/Android.bp
index df852bd..412099d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -372,6 +372,7 @@
"devicepolicyprotosnano",
"com.android.sysprop.apex",
+ "com.android.sysprop.init",
"PlatformProperties",
],
sdk_version: "core_platform",
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 08b1c2b..abf78c6 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -56,6 +56,12 @@
* instantiate this class directly; instead, retrieve it through
* {@link android.content.Context#getSystemService
* Context.getSystemService(Context.JOB_SCHEDULER_SERVICE)}.
+ *
+ * <p class="caution"><strong>Note:</strong> Beginning with API 30
+ * ({@link android.os.Build.VERSION_CODES#R}), JobScheduler will throttle runaway applications.
+ * Calling {@link #schedule(JobInfo)} and other such methods with very high frequency is indicative
+ * of an app bug and so, to make sure the system doesn't get overwhelmed, JobScheduler will begin
+ * to throttle apps that show buggy behavior, regardless of target SDK version.
*/
@SystemService(Context.JOB_SCHEDULER_SERVICE)
public abstract class JobScheduler {
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index 4ffcf8a..4b4fb96 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -80,27 +80,22 @@
}
/**
- * Add the specified package to the power save whitelist.
- *
- * @return true if the package was successfully added to the whitelist
+ * Add the specified package to the permanent power save whitelist.
*/
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
- public boolean addToWhitelist(@NonNull String packageName) {
- return addToWhitelist(Collections.singletonList(packageName)) == 1;
+ public void addToWhitelist(@NonNull String packageName) {
+ addToWhitelist(Collections.singletonList(packageName));
}
/**
- * Add the specified packages to the power save whitelist.
- *
- * @return the number of packages that were successfully added to the whitelist
+ * Add the specified packages to the permanent power save whitelist.
*/
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
- public int addToWhitelist(@NonNull List<String> packageNames) {
+ public void addToWhitelist(@NonNull List<String> packageNames) {
try {
- return mService.addPowerSaveWhitelistApps(packageNames);
+ mService.addPowerSaveWhitelistApps(packageNames);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
- return 0;
}
}
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index d2d942a..dc72d6d 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -85,6 +85,7 @@
/**
* Checks if an app has been idle for a while and filters out apps that are excluded.
* It returns false if the current system state allows all apps to be considered active.
+ * This happens if the device is plugged in or otherwise temporarily allowed to make exceptions.
* Called by interface impls.
*/
boolean isAppIdleFiltered(String packageName, int appId, int userId,
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 b516279..e4c6b52 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -37,8 +37,6 @@
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -57,7 +55,6 @@
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -90,7 +87,6 @@
import com.android.server.DeviceIdleInternal;
import com.android.server.FgThread;
import com.android.server.LocalServices;
-import com.android.server.compat.PlatformCompat;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
import com.android.server.job.controllers.BackgroundJobsController;
@@ -155,16 +151,6 @@
/** The maximum number of jobs that we allow an unprivileged app to schedule */
private static final int MAX_JOBS_PER_APP = 100;
- /**
- * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and
- * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app
- * calls the APIs too frequently.
- */
- @ChangeId
- // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up.
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
- protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L;
-
@VisibleForTesting
public static Clock sSystemClock = Clock.systemUTC();
@@ -264,7 +250,6 @@
private final CountQuotaTracker mQuotaTracker;
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
- private final PlatformCompat mPlatformCompat;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -986,9 +971,7 @@
Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
mAppStandbyInternal.restrictApp(
pkg, userId, UsageStatsManager.REASON_SUB_RESTRICT_BUGGY);
- if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
- && mPlatformCompat.isChangeEnabledByPackageName(
- CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
+ if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION) {
final boolean isDebuggable;
synchronized (mLock) {
if (!mDebuggableApps.containsKey(packageName)) {
@@ -1370,8 +1353,6 @@
// Set up the app standby bucketing tracker
mStandbyTracker = new StandbyTracker();
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
- mPlatformCompat =
- (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER);
mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
mConstants.API_QUOTA_SCHEDULE_COUNT,
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index f1bfa04..e343478 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -48,6 +48,7 @@
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import android.annotation.NonNull;
@@ -71,9 +72,8 @@
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkRequest;
import android.net.NetworkScoreManager;
+import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Environment;
@@ -285,6 +285,7 @@
long mInitialForegroundServiceStartTimeoutMillis;
private volatile boolean mAppIdleEnabled;
+ private boolean mIsCharging;
private boolean mSystemServicesReady = false;
// There was a system update, defaults need to be initialized after services are ready
private boolean mPendingInitializeDefaults;
@@ -360,6 +361,11 @@
mHandler = new AppStandbyHandler(mInjector.getLooper());
mPackageManager = mContext.getPackageManager();
+ DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver();
+ IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING);
+ deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+ mContext.registerReceiver(deviceStateReceiver, deviceStates);
+
synchronized (mAppIdleLock) {
mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
mInjector.elapsedRealtime());
@@ -417,6 +423,8 @@
if (mPendingOneTimeCheckIdleStates) {
postOneTimeCheckIdleStates();
}
+ } else if (phase == PHASE_BOOT_COMPLETED) {
+ setChargingState(mInjector.isCharging());
}
}
@@ -515,6 +523,16 @@
appUsage.bucketingReason, false);
}
+ @VisibleForTesting
+ void setChargingState(boolean isCharging) {
+ synchronized (mAppIdleLock) {
+ if (mIsCharging != isCharging) {
+ if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging);
+ mIsCharging = isCharging;
+ }
+ }
+ }
+
@Override
public void postCheckIdleStates(int userId) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
@@ -977,6 +995,11 @@
if (isAppSpecial(packageName, appId, userId)) {
return false;
} else {
+ synchronized (mAppIdleLock) {
+ if (!mAppIdleEnabled || mIsCharging) {
+ return false;
+ }
+ }
return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
}
}
@@ -1543,6 +1566,8 @@
pw.println();
pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
+ pw.print(" mIsCharging=");
+ pw.print(mIsCharging);
pw.println();
pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
@@ -1560,6 +1585,7 @@
private final Looper mLooper;
private IDeviceIdleController mDeviceIdleController;
private IBatteryStats mBatteryStats;
+ private BatteryManager mBatteryManager;
private PackageManagerInternal mPackageManagerInternal;
private DisplayManager mDisplayManager;
private PowerManager mPowerManager;
@@ -1593,6 +1619,7 @@
mDisplayManager = (DisplayManager) mContext.getSystemService(
Context.DISPLAY_SERVICE);
mPowerManager = mContext.getSystemService(PowerManager.class);
+ mBatteryManager = mContext.getSystemService(BatteryManager.class);
final ActivityManager activityManager =
(ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -1630,6 +1657,10 @@
return buildFlag && runtimeFlag;
}
+ boolean isCharging() {
+ return mBatteryManager.isCharging();
+ }
+
boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
}
@@ -1766,15 +1797,19 @@
}
};
- private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder().build();
-
- private final ConnectivityManager.NetworkCallback mNetworkCallback
- = new ConnectivityManager.NetworkCallback() {
+ private class DeviceStateReceiver extends BroadcastReceiver {
@Override
- public void onAvailable(Network network) {
- mConnectivityManager.unregisterNetworkCallback(this);
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case BatteryManager.ACTION_CHARGING:
+ setChargingState(true);
+ break;
+ case BatteryManager.ACTION_DISCHARGING:
+ setChargingState(false);
+ break;
+ }
}
- };
+ }
private final DisplayManager.DisplayListener mDisplayListener
= new DisplayManager.DisplayListener() {
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 3eed26b..7d18578 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -50,6 +50,7 @@
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.ParsableByteArray;
+import com.google.android.exoplayer2.video.ColorInfo;
import java.io.EOFException;
import java.io.IOException;
@@ -810,19 +811,17 @@
// Private static methods.
private static MediaFormat toMediaFormat(Format format) {
-
MediaFormat result = new MediaFormat();
- if (format.bitrate != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_BIT_RATE, format.bitrate);
- }
- if (format.channelCount != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
- }
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
- if (format.colorInfo != null) {
- result.setInteger(MediaFormat.KEY_COLOR_TRANSFER, format.colorInfo.colorTransfer);
- result.setInteger(MediaFormat.KEY_COLOR_RANGE, format.colorInfo.colorRange);
- result.setInteger(MediaFormat.KEY_COLOR_STANDARD, format.colorInfo.colorSpace);
+ ColorInfo colorInfo = format.colorInfo;
+ if (colorInfo != null) {
+ setOptionalMediaFormatInt(
+ result, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace);
+
if (format.colorInfo.hdrStaticInfo != null) {
result.setByteBuffer(
MediaFormat.KEY_HDR_STATIC_INFO,
@@ -830,63 +829,50 @@
}
}
- if (format.sampleMimeType != null) {
- result.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
- }
- if (format.codecs != null) {
- result.setString(MediaFormat.KEY_CODECS_STRING, format.codecs);
- }
+ setOptionalMediaFormatString(result, MediaFormat.KEY_MIME, format.sampleMimeType);
+ setOptionalMediaFormatString(result, MediaFormat.KEY_CODECS_STRING, format.codecs);
if (format.frameRate != Format.NO_VALUE) {
result.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate);
}
- if (format.width != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_WIDTH, format.width);
- }
- if (format.height != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_HEIGHT, format.height);
- }
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_WIDTH, format.width);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_HEIGHT, format.height);
+
List<byte[]> initData = format.initializationData;
if (initData != null) {
for (int i = 0; i < initData.size(); i++) {
result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i)));
}
}
- if (format.language != null) {
- result.setString(MediaFormat.KEY_LANGUAGE, format.language);
- }
- if (format.maxInputSize != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
- }
- if (format.pcmEncoding != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
- }
- if (format.rotationDegrees != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_ROTATION, format.rotationDegrees);
- }
- if (format.sampleRate != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
- }
+ setOptionalMediaFormatString(result, MediaFormat.KEY_LANGUAGE, format.language);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_ROTATION, format.rotationDegrees);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
+
int selectionFlags = format.selectionFlags;
- if ((selectionFlags & C.SELECTION_FLAG_AUTOSELECT) != 0) {
- result.setInteger(MediaFormat.KEY_IS_AUTOSELECT, 1);
+ result.setInteger(
+ MediaFormat.KEY_IS_AUTOSELECT, selectionFlags & C.SELECTION_FLAG_AUTOSELECT);
+ result.setInteger(MediaFormat.KEY_IS_DEFAULT, selectionFlags & C.SELECTION_FLAG_DEFAULT);
+ result.setInteger(
+ MediaFormat.KEY_IS_FORCED_SUBTITLE, selectionFlags & C.SELECTION_FLAG_FORCED);
+
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
+ setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
+
+ if (format.pixelWidthHeightRatio != Format.NO_VALUE && format.pixelWidthHeightRatio != 0) {
+ int parWidth = 1;
+ int parHeight = 1;
+ if (format.pixelWidthHeightRatio < 1.0f) {
+ parHeight = 1 << 30;
+ parWidth = (int) (format.pixelWidthHeightRatio * parHeight);
+ } else if (format.pixelWidthHeightRatio > 1.0f) {
+ parWidth = 1 << 30;
+ parHeight = (int) (parWidth / format.pixelWidthHeightRatio);
+ }
+ result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, parWidth);
+ result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, parHeight);
+ result.setFloat("pixel-width-height-ratio-float", format.pixelWidthHeightRatio);
}
- if ((selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0) {
- result.setInteger(MediaFormat.KEY_IS_DEFAULT, 1);
- }
- if ((selectionFlags & C.SELECTION_FLAG_FORCED) != 0) {
- result.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 1);
- }
- if (format.encoderDelay != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
- }
- if (format.encoderPadding != Format.NO_VALUE) {
- result.setInteger(MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
- }
- // TODO: Implement float to fraction conversion.
- // if (format.pixelWidthHeightRatio != Format.NO_VALUE) {
- // result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, );
- // result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, );
- // }
// LACK OF SUPPORT FOR:
// format.accessibilityChannel;
@@ -899,6 +885,19 @@
return result;
}
+ private static void setOptionalMediaFormatInt(MediaFormat mediaFormat, String key, int value) {
+ if (value != Format.NO_VALUE) {
+ mediaFormat.setInteger(key, value);
+ }
+ }
+
+ private static void setOptionalMediaFormatString(
+ MediaFormat mediaFormat, String key, @Nullable String value) {
+ if (value != null) {
+ mediaFormat.setString(key, value);
+ }
+ }
+
private static DrmInitData toFrameworkDrmInitData(
com.google.android.exoplayer2.drm.DrmInitData drmInitData) {
// TODO: Implement.
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index 1f9f18c..c0f84a0 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -20,6 +20,7 @@
apex_defaults {
native_shared_libs: [
+ "libstatspull",
"libstats_jni",
],
// binaries: ["vold"],
@@ -28,6 +29,7 @@
"service-statsd",
],
// prebuilts: ["my_prebuilt"],
+ compile_multilib: "both",
name: "com.android.os.statsd-defaults",
key: "com.android.os.statsd.key",
certificate: ":com.android.os.statsd.certificate",
diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp
index 7c93bc7..4ccdd7e 100644
--- a/apex/statsd/aidl/Android.bp
+++ b/apex/statsd/aidl/Android.bp
@@ -38,6 +38,10 @@
},
ndk: {
enabled: true,
+ apex_available: [
+ "com.android.os.statsd",
+ ],
}
- }
+
+ },
}
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index 63a853a..ab669d4 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_visibility: [ ":__pkg__" ]
+}
+
genrule {
name: "statslog-statsd-java-gen",
tools: ["stats-log-api-gen"],
@@ -25,6 +29,9 @@
srcs: [
":statslog-statsd-java-gen",
],
+ visibility: [
+ "//cts/hostsidetests/statsd/apps:__subpackages__",
+ ]
}
filegroup {
@@ -34,6 +41,9 @@
":framework-statsd-aidl-sources",
":statslog-statsd-java-gen",
],
+ visibility: [
+ "//frameworks/base", // For the "global" stubs.
+ ],
}
java_defaults {
@@ -139,6 +149,10 @@
"framework-statsd-defaults",
],
srcs: [ ":framework-statsd-stubs-srcs-publicapi" ],
+ visibility: [
+ "//frameworks/base", // Framework
+ "//frameworks/base/apex/statsd", // statsd apex
+ ]
}
java_library {
@@ -147,6 +161,10 @@
"framework-statsd-defaults",
],
srcs: [ ":framework-statsd-stubs-srcs-systemapi" ],
+ visibility: [
+ "//frameworks/base", // Framework
+ "//frameworks/base/apex/statsd", // statsd apex
+ ]
}
java_library {
@@ -155,4 +173,9 @@
"framework-statsd-defaults",
],
srcs: [ ":framework-statsd-stubs-srcs-module_libs_api" ],
+ visibility: [
+ "//frameworks/base", // Framework
+ "//frameworks/base/apex/statsd", // statsd apex
+ "//frameworks/opt/net/wifi/service" // wifi service
+ ]
}
diff --git a/api/current.txt b/api/current.txt
index 611fec3..4887c66 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6917,6 +6917,7 @@
method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]);
method public boolean hasGrantedPolicy(@NonNull android.content.ComponentName, int);
+ method public boolean hasLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
method public boolean installCaCert(@Nullable android.content.ComponentName, byte[]);
method public boolean installExistingPackage(@NonNull android.content.ComponentName, String);
method public boolean installKeyPair(@Nullable android.content.ComponentName, @NonNull java.security.PrivateKey, @NonNull java.security.cert.Certificate, @NonNull String);
@@ -6935,7 +6936,6 @@
method public boolean isDeviceOwnerApp(String);
method public boolean isEphemeralUser(@NonNull android.content.ComponentName);
method public boolean isLockTaskPermitted(String);
- method public boolean isLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName);
method public boolean isLogoutEnabled();
method public boolean isManagedProfile(@NonNull android.content.ComponentName);
method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
@@ -6982,6 +6982,7 @@
method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
method public void setCommonCriteriaModeEnabled(@NonNull android.content.ComponentName, boolean);
+ method public void setConfiguredNetworksLockdownState(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
@@ -7001,7 +7002,6 @@
method public void setLocationEnabled(@NonNull android.content.ComponentName, boolean);
method public void setLockTaskFeatures(@NonNull android.content.ComponentName, int);
method public void setLockTaskPackages(@NonNull android.content.ComponentName, @NonNull String[]) throws java.lang.SecurityException;
- method public void setLockdownAdminConfiguredNetworks(@NonNull android.content.ComponentName, boolean);
method public void setLogoutEnabled(@NonNull android.content.ComponentName, boolean);
method public void setLongSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
method public void setManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName, long);
@@ -12703,8 +12703,7 @@
public class Resources {
ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
- method public void addLoader(@NonNull android.content.res.loader.ResourcesLoader);
- method public void clearLoaders();
+ method public void addLoaders(@NonNull android.content.res.loader.ResourcesLoader...);
method public final void finishPreloading();
method public final void flushLayoutCache();
method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimRes @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
@@ -12731,7 +12730,6 @@
method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
- method @NonNull public java.util.List<android.content.res.loader.ResourcesLoader> getLoaders();
method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12759,8 +12757,7 @@
method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
- method public void removeLoader(@NonNull android.content.res.loader.ResourcesLoader);
- method public void setLoaders(@NonNull java.util.List<android.content.res.loader.ResourcesLoader>);
+ method public void removeLoaders(@NonNull android.content.res.loader.ResourcesLoader...);
method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
field @AnyRes public static final int ID_NULL = 0; // 0x0
}
@@ -45514,7 +45511,7 @@
field public static final int DIRECTION_INCOMING = 0; // 0x0
field public static final int DIRECTION_OUTGOING = 1; // 0x1
field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff
- field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
+ field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
field public static final int PROPERTY_CONFERENCE = 1; // 0x1
field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
@@ -45603,7 +45600,8 @@
method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
method public final int getConnectionCapabilities();
method public final int getConnectionProperties();
- method public final long getConnectionTime();
+ method public final long getConnectionStartElapsedRealtimeMillis();
+ method @IntRange(from=0) public final long getConnectionTime();
method public final java.util.List<android.telecom.Connection> getConnections();
method public final android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
@@ -45633,8 +45631,9 @@
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConnectionCapabilities(int);
method public final void setConnectionProperties(int);
- method public final void setConnectionStartElapsedRealTime(long);
- method public final void setConnectionTime(long);
+ method @Deprecated public final void setConnectionStartElapsedRealTime(long);
+ method public final void setConnectionStartElapsedRealtimeMillis(long);
+ method public final void setConnectionTime(@IntRange(from=0) long);
method public final void setDialing();
method public final void setDisconnected(android.telecom.DisconnectCause);
method public final void setExtras(@Nullable android.os.Bundle);
@@ -45794,7 +45793,7 @@
field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT";
field public static final String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
field public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
- field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
+ field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
field public static final int PROPERTY_HIGH_DEF_AUDIO = 4; // 0x4
field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
@@ -46973,7 +46972,7 @@
method @NonNull public abstract android.telephony.CellIdentity getCellIdentity();
method @NonNull public abstract android.telephony.CellSignalStrength getCellSignalStrength();
method @Deprecated public long getTimeStamp();
- method public long getTimestampNanos();
+ method public long getTimestampMillis();
method public boolean isRegistered();
field public static final int CONNECTION_NONE = 0; // 0x0
field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
@@ -47133,6 +47132,19 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ClosedSubscriberGroupInfo> CREATOR;
}
+ public final class DisplayInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getNetworkType();
+ method public int getOverrideNetworkType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.DisplayInfo> CREATOR;
+ field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
+ field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
+ field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
+ field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
+ field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
+ }
+
public class IccOpenLogicalChannelResponse implements android.os.Parcelable {
method public int describeContents();
method public int getChannel();
@@ -47389,6 +47401,7 @@
method public void onDataActivity(int);
method public void onDataConnectionStateChanged(int);
method public void onDataConnectionStateChanged(int, int);
+ method @RequiresPermission("android.permission.READ_PHONE_STATE") public void onDisplayInfoChanged(@NonNull android.telephony.DisplayInfo);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
method public void onMessageWaitingIndicatorChanged(boolean);
method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
@@ -47406,6 +47419,7 @@
field public static final int LISTEN_CELL_LOCATION = 16; // 0x10
field public static final int LISTEN_DATA_ACTIVITY = 128; // 0x80
field public static final int LISTEN_DATA_CONNECTION_STATE = 64; // 0x40
+ field public static final int LISTEN_DISPLAY_INFO_CHANGED = 1048576; // 0x100000
field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
@@ -47488,7 +47502,7 @@
method @Deprecated public int getGsmBitErrorRate();
method @Deprecated public int getGsmSignalStrength();
method public int getLevel();
- method public long getTimestampNanos();
+ method public long getTimestampMillis();
method @Deprecated public boolean isGsm();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SignalStrength> CREATOR;
diff --git a/api/system-current.txt b/api/system-current.txt
index 59cc616..0fd8a20 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -9,6 +9,7 @@
field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
+ field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
field public static final String ACCESS_MESSAGES_ON_ICC = "android.permission.ACCESS_MESSAGES_ON_ICC";
field public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION";
field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
@@ -6030,7 +6031,7 @@
public class CaptivePortal implements android.os.Parcelable {
method public void logEvent(int, @NonNull String);
- method public void reevaluateNetwork();
+ method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
method public void useNetwork();
field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
field public static final int APP_RETURN_DISMISSED = 0; // 0x0
@@ -8838,8 +8839,8 @@
}
public class PowerWhitelistManager {
- method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean addToWhitelist(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public int addToWhitelist(@NonNull java.util.List<java.lang.String>);
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long);
method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
field public static final int EVENT_MMS = 2; // 0x2
@@ -10853,27 +10854,26 @@
public abstract class Conference extends android.telecom.Conferenceable {
method @Deprecated public final android.telecom.AudioState getAudioState();
method @Deprecated public final long getConnectTimeMillis();
- method public final long getConnectionStartElapsedRealTime();
method public android.telecom.Connection getPrimaryConnection();
method @NonNull public final String getTelecomCallId();
method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
- method public final void setAddress(@NonNull android.net.Uri, int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setAddress(@NonNull android.net.Uri, int);
method public final void setCallerDisplayName(@NonNull String, int);
- method public void setConferenceState(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setConferenceState(boolean);
method @Deprecated public final void setConnectTimeMillis(long);
}
public abstract class Connection extends android.telecom.Conferenceable {
method @Deprecated public final android.telecom.AudioState getAudioState();
- method public final long getConnectElapsedTimeMillis();
- method public final long getConnectTimeMillis();
+ method @IntRange(from=0) public final long getConnectTimeMillis();
+ method public final long getConnectionStartElapsedRealtimeMillis();
method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
method @Nullable public final String getTelecomCallId();
method @Deprecated public void onAudioStateChanged(android.telecom.AudioState);
method public final void resetConnectionTime();
method public void setCallDirection(int);
- method public final void setConnectTimeMillis(long);
- method public final void setConnectionStartElapsedRealTime(long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectionStartElapsedRealtimeMillis(long);
method public void setPhoneAccountHandle(@NonNull android.telecom.PhoneAccountHandle);
method public void setTelecomCallId(@NonNull String);
field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
@@ -11032,7 +11032,7 @@
}
public static class PhoneAccount.Builder {
- method @NonNull public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
}
public class PhoneAccountSuggestionService extends android.app.Service {
@@ -11107,7 +11107,7 @@
method public int getCallState();
method public android.telecom.PhoneAccountHandle getConnectionManager();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCurrentTtyMode();
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
method @Deprecated public android.content.ComponentName getDefaultPhoneApp();
method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
@@ -12571,6 +12571,7 @@
method public void notifyDataActivityChanged(int, int);
method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
method public void notifyDisconnectCause(int, int, int, int);
+ method public void notifyDisplayInfoChanged(int, int, @NonNull android.telephony.DisplayInfo);
method public void notifyEmergencyNumberList(int, int);
method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
method public void notifyMessageWaitingChanged(int, int, boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index 7e8eb0c..4c8bb02 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -15,6 +15,7 @@
field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
+ field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
@@ -1641,7 +1642,7 @@
public class CaptivePortal implements android.os.Parcelable {
method public void logEvent(int, @NonNull String);
- method public void reevaluateNetwork();
+ method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
method public void useNetwork();
field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
field public static final int APP_RETURN_DISMISSED = 0; // 0x0
@@ -2450,8 +2451,8 @@
}
public class PowerWhitelistManager {
- method @RequiresPermission("android.permission.DEVICE_POWER") public boolean addToWhitelist(@NonNull String);
- method @RequiresPermission("android.permission.DEVICE_POWER") public int addToWhitelist(@NonNull java.util.List<java.lang.String>);
+ method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull String);
+ method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public void whitelistAppTemporarily(@NonNull String, long);
method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
field public static final int EVENT_MMS = 2; // 0x2
@@ -3440,23 +3441,22 @@
}
public abstract class Conference extends android.telecom.Conferenceable {
- method public final long getConnectionStartElapsedRealTime();
method public android.telecom.Connection getPrimaryConnection();
method @NonNull public final String getTelecomCallId();
- method public final void setAddress(@NonNull android.net.Uri, int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setAddress(@NonNull android.net.Uri, int);
method public final void setCallerDisplayName(@NonNull String, int);
- method public void setConferenceState(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setConferenceState(boolean);
}
public abstract class Connection extends android.telecom.Conferenceable {
- method public final long getConnectElapsedTimeMillis();
- method public final long getConnectTimeMillis();
+ method @IntRange(from=0) public final long getConnectTimeMillis();
+ method public final long getConnectionStartElapsedRealtimeMillis();
method @Nullable public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
method @Nullable public final String getTelecomCallId();
method public final void resetConnectionTime();
method public void setCallDirection(int);
- method public final void setConnectTimeMillis(long);
- method public final void setConnectionStartElapsedRealTime(long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectTimeMillis(@IntRange(from=0) long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public final void setConnectionStartElapsedRealtimeMillis(long);
method public void setPhoneAccountHandle(@NonNull android.telecom.PhoneAccountHandle);
method public void setTelecomCallId(@NonNull String);
field public static final int CAPABILITY_CONFERENCE_HAS_NO_CHILDREN = 2097152; // 0x200000
@@ -3488,7 +3488,7 @@
}
public static class PhoneAccount.Builder {
- method @NonNull public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.PhoneAccount.Builder setGroupId(@NonNull String);
}
public class PhoneAccountSuggestionService extends android.app.Service {
@@ -3502,7 +3502,7 @@
public class TelecomManager {
method @NonNull @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(boolean);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getCurrentTtyMode();
- method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDefaultDialerPackage(int);
+ method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
field public static final int TTY_MODE_FULL = 1; // 0x1
@@ -3720,6 +3720,7 @@
method public void notifyDataActivityChanged(int, int);
method public void notifyDataConnectionForSubscriber(int, int, int, @Nullable android.telephony.PreciseDataConnectionState);
method public void notifyDisconnectCause(int, int, int, int);
+ method public void notifyDisplayInfoChanged(int, int, @NonNull android.telephony.DisplayInfo);
method public void notifyEmergencyNumberList(int, int);
method public void notifyImsDisconnectCause(int, @NonNull android.telephony.ims.ImsReasonInfo);
method public void notifyMessageWaitingChanged(int, int, boolean);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1bc235a..03f97d8 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -378,7 +378,7 @@
240 [(module) = "framework"];
BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
BootTimeEventErrorCode boot_time_event_error_code_reported = 242 [(module) = "framework"];
- UserspaceRebootReported userspace_reboot_reported = 243;
+ UserspaceRebootReported userspace_reboot_reported = 243 [(module) = "framework"];
NotificationReported notification_reported = 244 [(module) = "framework"];
NotificationPanelReported notification_panel_reported = 245;
NotificationChannelModified notification_panel_modified = 246;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 50b9d6b..8b07418 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -109,6 +109,8 @@
import android.media.soundtrigger.SoundTriggerManager;
import android.media.tv.ITvInputManager;
import android.media.tv.TvInputManager;
+import android.media.tv.tuner.ITunerResourceManager;
+import android.media.tv.tuner.TunerResourceManager;
import android.net.ConnectivityDiagnosticsManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityThread;
@@ -937,6 +939,17 @@
return new TvInputManager(service, ctx.getUserId());
}});
+ registerService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, TunerResourceManager.class,
+ new CachedServiceFetcher<TunerResourceManager>() {
+ @Override
+ public TunerResourceManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder iBinder =
+ ServiceManager.getServiceOrThrow(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
+ ITunerResourceManager service = ITunerResourceManager.Stub.asInterface(iBinder);
+ return new TunerResourceManager(service, ctx.getUserId());
+ }});
+
registerService(Context.NETWORK_SCORE_SERVICE, NetworkScoreManager.class,
new CachedServiceFetcher<NetworkScoreManager>() {
@Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 48eab92..4a5a23a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4718,15 +4718,40 @@
public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
/**
- * Keyguard features that when set on a managed profile that doesn't have its own challenge will
- * affect the profile's parent user. These can also be set on the managed profile's parent
+ * Keyguard features that when set on a non-organization-owned managed profile that doesn't
+ * have its own challenge will affect the profile's parent user. These can also be set on the
+ * managed profile's parent {@link DevicePolicyManager} instance to explicitly control the
+ * parent user.
+ *
+ * <p>
+ * Organization-owned managed profile supports disabling additional keyguard features on the
+ * parent user as defined in {@link #ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY}.
+ *
+ * @hide
+ */
+ public static final int NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER =
+ DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS
+ | DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS;
+
+ /**
+ * Keyguard features that when set by the profile owner of an organization-owned managed
+ * profile will affect the profile's parent user if set on the managed profile's parent
* {@link DevicePolicyManager} instance.
*
* @hide
*/
+ public static final int ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY =
+ KEYGUARD_DISABLE_SECURE_CAMERA;
+
+ /**
+ * Keyguard features that when set on a normal or organization-owned managed profile, have
+ * the potential to affect the profile's parent user.
+ *
+ * @hide
+ */
public static final int PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER =
- DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS
- | DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS;
+ DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER
+ | DevicePolicyManager.ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY;
/**
* @deprecated This method does not actually modify the storage encryption of the device.
@@ -6115,11 +6140,20 @@
* <li>{@link #KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS} which affects notifications generated
* by applications in the managed profile.
* </ul>
+ * <p>
+ * From version {@link android.os.Build.VERSION_CODES#R} the profile owner of an
+ * organization-owned managed profile can set:
+ * <ul>
+ * <li>{@link #KEYGUARD_DISABLE_SECURE_CAMERA} which affects the parent user when called on the
+ * parent profile.
+ * </ul>
* {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, {@link #KEYGUARD_DISABLE_FINGERPRINT},
- * {@link #KEYGUARD_DISABLE_FACE} and {@link #KEYGUARD_DISABLE_IRIS} can also be
- * set on the {@link DevicePolicyManager} instance returned by
- * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
- * profile.
+ * {@link #KEYGUARD_DISABLE_FACE}, {@link #KEYGUARD_DISABLE_IRIS} and
+ * {@link #KEYGUARD_DISABLE_SECURE_CAMERA} can also be set on the {@link DevicePolicyManager}
+ * instance returned by {@link #getParentProfileInstance(ComponentName)} in order to set
+ * restrictions on the parent profile. {@link #KEYGUARD_DISABLE_SECURE_CAMERA} can only be set
+ * on the parent profile instance if the calling device admin is the profile owner of an
+ * organization-owned managed profile.
* <p>
* Requests to disable other features on a managed profile will be ignored.
* <p>
@@ -8789,11 +8823,11 @@
* @throws SecurityException if caller is not a device owner or a profile owner of an
* organization-owned managed profile.
*/
- public void setLockdownAdminConfiguredNetworks(@NonNull ComponentName admin, boolean lockdown) {
- throwIfParentInstance("setLockdownAdminConfiguredNetworks");
+ public void setConfiguredNetworksLockdownState(@NonNull ComponentName admin, boolean lockdown) {
+ throwIfParentInstance("setConfiguredNetworksLockdownState");
if (mService != null) {
try {
- mService.setLockdownAdminConfiguredNetworks(admin, lockdown);
+ mService.setConfiguredNetworksLockdownState(admin, lockdown);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8809,11 +8843,11 @@
* @throws SecurityException if caller is not a device owner or a profile owner of an
* organization-owned managed profile.
*/
- public boolean isLockdownAdminConfiguredNetworks(@NonNull ComponentName admin) {
- throwIfParentInstance("setLockdownAdminConfiguredNetworks");
+ public boolean hasLockdownAdminConfiguredNetworks(@NonNull ComponentName admin) {
+ throwIfParentInstance("hasLockdownAdminConfiguredNetworks");
if (mService != null) {
try {
- return mService.isLockdownAdminConfiguredNetworks(admin);
+ return mService.hasLockdownAdminConfiguredNetworks(admin);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8ef25bd..0aed39c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -266,8 +266,8 @@
void setSystemSetting(in ComponentName who, in String setting, in String value);
void setSecureSetting(in ComponentName who, in String setting, in String value);
- void setLockdownAdminConfiguredNetworks(in ComponentName who, boolean lockdown);
- boolean isLockdownAdminConfiguredNetworks(in ComponentName who);
+ void setConfiguredNetworksLockdownState(in ComponentName who, boolean lockdown);
+ boolean hasLockdownAdminConfiguredNetworks(in ComponentName who);
void setLocationEnabled(in ComponentName who, boolean locationEnabled);
void requestSetLocationProviderAllowed(in ComponentName who, in String provider, boolean providerAllowed);
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index ea66fd47..db4f1de 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -90,6 +90,7 @@
* The name of the dialer role.
*
* @see Intent#ACTION_DIAL
+ * @see android.telecom.InCallService
*/
public static final String ROLE_DIALER = "android.app.role.DIALER";
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 5668944..2c701b4 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -599,7 +599,8 @@
/**
* Returns whether the specified app is currently considered inactive. This will be true if the
* app hasn't been used directly or indirectly for a period of time defined by the system. This
- * could be of the order of several hours or days.
+ * could be of the order of several hours or days. Apps are not considered inactive when the
+ * device is charging.
* @param packageName The package name of the app to query
* @return whether the app is currently considered inactive
*/
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 249e582..49f62f4 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3457,6 +3457,7 @@
CONSUMER_IR_SERVICE,
//@hide: TRUST_SERVICE,
TV_INPUT_SERVICE,
+ //@hide: TV_TUNER_RESOURCE_MGR_SERVICE,
//@hide: NETWORK_SCORE_SERVICE,
USAGE_STATS_SERVICE,
MEDIA_SESSION_SERVICE,
@@ -4757,6 +4758,17 @@
public static final String TV_INPUT_SERVICE = "tv_input";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.tv.TunerResourceManager} for interacting with TV
+ * tuner resources on the device.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.tv.TunerResourceManager
+ * @hide
+ */
+ public static final String TV_TUNER_RESOURCE_MGR_SERVICE = "tv_tuner_resource_mgr";
+
+ /**
* {@link android.net.NetworkScoreManager} for managing network scoring.
* @see #getSystemService(String)
* @see android.net.NetworkScoreManager
diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
index 475f019..9d37299 100644
--- a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
+++ b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
@@ -16,6 +16,10 @@
package android.content.integrity;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
import java.util.Map;
/**
@@ -25,7 +29,29 @@
*
* @hide
*/
-public class InstallerAllowedByManifestFormula extends IntegrityFormula {
+public class InstallerAllowedByManifestFormula extends IntegrityFormula implements Parcelable {
+
+ public static final String INSTALLER_CERTIFICATE_NOT_EVALUATED = "";
+
+ public InstallerAllowedByManifestFormula() {
+ }
+
+ private InstallerAllowedByManifestFormula(Parcel in) {
+ }
+
+ @NonNull
+ public static final Creator<InstallerAllowedByManifestFormula> CREATOR =
+ new Creator<InstallerAllowedByManifestFormula>() {
+ @Override
+ public InstallerAllowedByManifestFormula createFromParcel(Parcel in) {
+ return new InstallerAllowedByManifestFormula(in);
+ }
+
+ @Override
+ public InstallerAllowedByManifestFormula[] newArray(int size) {
+ return new InstallerAllowedByManifestFormula[size];
+ }
+ };
@Override
public int getTag() {
@@ -54,10 +80,30 @@
private static boolean installerInAllowedInstallersFromManifest(
AppInstallMetadata appInstallMetadata,
Map<String, String> allowedInstallersAndCertificates) {
- return allowedInstallersAndCertificates.containsKey(appInstallMetadata.getInstallerName())
- && appInstallMetadata.getInstallerCertificates()
- .contains(
- allowedInstallersAndCertificates
- .get(appInstallMetadata.getInstallerName()));
+ String installerPackage = appInstallMetadata.getInstallerName();
+
+ if (!allowedInstallersAndCertificates.containsKey(installerPackage)) {
+ return false;
+ }
+
+ // If certificate is not specified in the manifest, we do not check it.
+ if (!allowedInstallersAndCertificates.get(installerPackage)
+ .equals(INSTALLER_CERTIFICATE_NOT_EVALUATED)) {
+ return appInstallMetadata.getInstallerCertificates()
+ .contains(
+ allowedInstallersAndCertificates
+ .get(appInstallMetadata.getInstallerName()));
+ }
+
+ return true;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
}
}
diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java
index ac4c907..c5e5c8a 100644
--- a/core/java/android/content/integrity/IntegrityFormula.java
+++ b/core/java/android/content/integrity/IntegrityFormula.java
@@ -214,6 +214,8 @@
return LongAtomicFormula.CREATOR.createFromParcel(in);
case BOOLEAN_ATOMIC_FORMULA_TAG:
return BooleanAtomicFormula.CREATOR.createFromParcel(in);
+ case INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
+ return InstallerAllowedByManifestFormula.CREATOR.createFromParcel(in);
default:
throw new IllegalArgumentException("Unknown formula tag " + tag);
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 471e83c..cb809da 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -62,6 +62,7 @@
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -112,7 +113,7 @@
static final String TAG = "Resources";
private static final Object sSync = new Object();
- private final Object mLock = new Object();
+ private final Object mUpdateLock = new Object();
// Used by BridgeResources in layoutlib
@UnsupportedAppUsage
@@ -139,6 +140,7 @@
@UnsupportedAppUsage
final ClassLoader mClassLoader;
+ @GuardedBy("mUpdateLock")
private UpdateCallbacks mCallbacks = null;
/**
@@ -2375,6 +2377,7 @@
*
* <p>Loaders are listed in increasing precedence order. A loader will override the resources
* and assets of loaders listed before itself.
+ * @hide
*/
@NonNull
public List<ResourcesLoader> getLoaders() {
@@ -2382,87 +2385,81 @@
}
/**
- * Appends a loader to the end of the loader list. If the loader is already present in the
- * loader list, the list will not be modified.
- *
- * @param loader the loader to add
- */
- public void addLoader(@NonNull ResourcesLoader loader) {
- synchronized (mLock) {
- checkCallbacksRegistered();
-
- final List<ResourcesLoader> loaders = new ArrayList<>(
- mResourcesImpl.getAssets().getLoaders());
- if (loaders.contains(loader)) {
- return;
- }
-
- loaders.add(loader);
- mCallbacks.onLoadersChanged(this, loaders);
- loader.registerOnProvidersChangedCallback(this, mCallbacks);
- }
- }
-
- /**
- * Removes a loader from the loaders. If the loader is not present in the loader list, the list
+ * Adds a loader to the list of loaders. If the loader is already present in the list, the list
* will not be modified.
*
- * @param loader the loader to remove
+ * @param loaders the loaders to add
*/
- public void removeLoader(@NonNull ResourcesLoader loader) {
- synchronized (mLock) {
+ public void addLoaders(@NonNull ResourcesLoader... loaders) {
+ synchronized (mUpdateLock) {
checkCallbacksRegistered();
+ final List<ResourcesLoader> newLoaders =
+ new ArrayList<>(mResourcesImpl.getAssets().getLoaders());
+ final ArraySet<ResourcesLoader> loaderSet = new ArraySet<>(newLoaders);
- final List<ResourcesLoader> loaders = new ArrayList<>(
- mResourcesImpl.getAssets().getLoaders());
- if (!loaders.remove(loader)) {
+ for (int i = 0; i < loaders.length; i++) {
+ final ResourcesLoader loader = loaders[i];
+ if (!loaderSet.contains(loader)) {
+ newLoaders.add(loader);
+ }
+ }
+
+ if (loaderSet.size() == newLoaders.size()) {
return;
}
- mCallbacks.onLoadersChanged(this, loaders);
- loader.unregisterOnProvidersChangedCallback(this);
+ mCallbacks.onLoadersChanged(this, newLoaders);
+ for (int i = loaderSet.size(), n = newLoaders.size(); i < n; i++) {
+ newLoaders.get(i).registerOnProvidersChangedCallback(this, mCallbacks);
+ }
}
}
/**
- * Sets the list of loaders.
+ * Removes loaders from the list of loaders. If the loader is not present in the list, the list
+ * will not be modified.
*
- * @param loaders the new loaders
+ * @param loaders the loaders to remove
*/
- public void setLoaders(@NonNull List<ResourcesLoader> loaders) {
- synchronized (mLock) {
+ public void removeLoaders(@NonNull ResourcesLoader... loaders) {
+ synchronized (mUpdateLock) {
checkCallbacksRegistered();
-
+ final ArraySet<ResourcesLoader> removedLoaders = new ArraySet<>(loaders);
+ final List<ResourcesLoader> newLoaders = new ArrayList<>();
final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
- int index = 0;
- boolean modified = loaders.size() != oldLoaders.size();
- final ArraySet<ResourcesLoader> seenLoaders = new ArraySet<>();
- for (final ResourcesLoader loader : loaders) {
- if (!seenLoaders.add(loader)) {
- throw new IllegalArgumentException("Loader " + loader + " present twice");
- }
- if (!modified && oldLoaders.get(index++) != loader) {
- modified = true;
+ for (int i = 0, n = oldLoaders.size(); i < n; i++) {
+ final ResourcesLoader loader = oldLoaders.get(i);
+ if (!removedLoaders.contains(loader)) {
+ newLoaders.add(loader);
}
}
- if (!modified) {
+ if (oldLoaders.size() == newLoaders.size()) {
return;
}
- mCallbacks.onLoadersChanged(this, loaders);
- for (int i = 0, n = oldLoaders.size(); i < n; i++) {
- oldLoaders.get(i).unregisterOnProvidersChangedCallback(this);
- }
- for (ResourcesLoader newLoader : loaders) {
- newLoader.registerOnProvidersChangedCallback(this, mCallbacks);
+ mCallbacks.onLoadersChanged(this, newLoaders);
+ for (int i = 0; i < loaders.length; i++) {
+ loaders[i].unregisterOnProvidersChangedCallback(this);
}
}
}
- /** Removes all {@link ResourcesLoader ResourcesLoader(s)}. */
+ /**
+ * Removes all {@link ResourcesLoader ResourcesLoader(s)}.
+ * @hide
+ */
+ @VisibleForTesting
public void clearLoaders() {
- setLoaders(Collections.emptyList());
+ synchronized (mUpdateLock) {
+ checkCallbacksRegistered();
+ final List<ResourcesLoader> newLoaders = Collections.emptyList();
+ final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
+ mCallbacks.onLoadersChanged(this, newLoaders);
+ for (ResourcesLoader loader : oldLoaders) {
+ loader.unregisterOnProvidersChangedCallback(this);
+ }
+ }
}
}
diff --git a/core/java/android/content/res/loader/ResourcesLoader.java b/core/java/android/content/res/loader/ResourcesLoader.java
index 69dacee..58fec60 100644
--- a/core/java/android/content/res/loader/ResourcesLoader.java
+++ b/core/java/android/content/res/loader/ResourcesLoader.java
@@ -40,8 +40,8 @@
* of {@link ResourcesProvider ResourcesProvider(s)} a loader contains propagates to all Resources
* objects that use the loader.
*
- * <p>Loaders retrieved with {@link Resources#getLoaders()} are listed in increasing precedence
- * order. A loader will override the resources and assets of loaders listed before itself.
+ * <p>Loaders must be added to Resources objects in increasing precedence order. A loader will
+ * override the resources and assets of loaders added before itself.
*
* <p>Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A
* provider will override the resources and assets of providers listed before itself.
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 6bda46b..3a0660d 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -699,6 +699,9 @@
return context.getString(com.android.internal.R.string.face_error_not_enrolled);
case FACE_ERROR_HW_NOT_PRESENT:
return context.getString(com.android.internal.R.string.face_error_hw_not_present);
+ case BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
+ return context.getString(
+ com.android.internal.R.string.face_error_security_update_required);
case FACE_ERROR_VENDOR: {
String[] msgArray = context.getResources().getStringArray(
com.android.internal.R.array.face_error_vendor);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index ea576bc..f301a5c 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1011,6 +1011,9 @@
case FINGERPRINT_ERROR_HW_NOT_PRESENT:
return context.getString(
com.android.internal.R.string.fingerprint_error_hw_not_present);
+ case BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
+ return context.getString(
+ com.android.internal.R.string.fingerprint_error_security_update_required);
case FINGERPRINT_ERROR_VENDOR: {
String[] msgArray = context.getResources().getStringArray(
com.android.internal.R.array.fingerprint_error_vendor);
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index fb35b4b..8afeb30 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -15,7 +15,9 @@
*/
package android.net;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.IBinder;
@@ -23,6 +25,8 @@
import android.os.Parcelable;
import android.os.RemoteException;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
/**
* A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
* activity to indicate to the system different outcomes of captive portal sign in. This class is
@@ -76,6 +80,17 @@
private final IBinder mBinder;
/** @hide */
+ @IntDef(value = {
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED,
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_UNWANTED,
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_WANTED_AS_IS,
+ MetricsEvent.CAPTIVE_PORTAL_LOGIN_ACTIVITY_SSL_ERROR,
+ })
+ public @interface EventId {
+ }
+
+ /** @hide */
public CaptivePortal(@NonNull IBinder binder) {
mBinder = binder;
}
@@ -153,6 +168,7 @@
*/
@SystemApi
@TestApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public void reevaluateNetwork() {
try {
ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED);
@@ -168,7 +184,7 @@
*/
@SystemApi
@TestApi
- public void logEvent(int eventId, @NonNull String packageName) {
+ public void logEvent(@EventId int eventId, @NonNull String packageName) {
try {
ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName);
} catch (RemoteException e) {
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 09ec6c3..d83715c 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -51,7 +51,7 @@
*
* <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
* transport mode security associations and apply them to individual sockets. Applications looking
- * to create a VPN should use {@link VpnService}.
+ * to create an IPsec VPN should use {@link VpnManager} and {@link Ikev2VpnProfile}.
*
* @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
* Internet Protocol</a>
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 6d46c20..3c1b86b 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -548,32 +548,32 @@
final int startIndex = getIndexAfter(end);
for (int i = startIndex; i >= 0; i--) {
final long curStart = bucketStart[i];
- final long curEnd = curStart + bucketDuration;
+ long curEnd = curStart + bucketDuration;
// bucket is older than request; we're finished
if (curEnd <= start) break;
// bucket is newer than request; keep looking
if (curStart >= end) continue;
- // include full value for active buckets, otherwise only fractional
- final boolean activeBucket = curStart < now && curEnd > now;
- final long overlap;
- if (activeBucket) {
- overlap = bucketDuration;
- } else {
- final long overlapEnd = curEnd < end ? curEnd : end;
- final long overlapStart = curStart > start ? curStart : start;
- overlap = overlapEnd - overlapStart;
- }
+ // the active bucket is shorter then a normal completed bucket
+ if (curEnd > now) curEnd = now;
+ // usually this is simply bucketDuration
+ final long bucketSpan = curEnd - curStart;
+ // prevent division by zero
+ if (bucketSpan <= 0) continue;
+
+ final long overlapEnd = curEnd < end ? curEnd : end;
+ final long overlapStart = curStart > start ? curStart : start;
+ final long overlap = overlapEnd - overlapStart;
if (overlap <= 0) continue;
// integer math each time is faster than floating point
- if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration;
- if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
- if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
- if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration;
- if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
- if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
+ if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketSpan;
+ if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketSpan;
+ if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketSpan;
+ if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketSpan;
+ if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketSpan;
+ if (operations != null) entry.operations += operations[i] * overlap / bucketSpan;
}
return entry;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7c4ec8e..37efec3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -997,8 +997,9 @@
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard against this.
* <p>
- * Input: Optionally, in versions of Android prior to 11, the Intent's data URI can specify the
- * application package name to directly invoke the management GUI specific to the package name.
+ * Input: Optionally, in versions of Android prior to {@link android.os.Build.VERSION_CODES#R},
+ * the Intent's data URI can specify the application package name to directly invoke the
+ * management GUI specific to the package name.
* For example "package:com.my.app".
* <p>
* Output: Nothing.
@@ -1011,9 +1012,10 @@
* Activity Action: Show screen for controlling if the app specified in the data URI of the
* intent can draw on top of other apps.
* <p>
- * Unlike {@link #ACTION_MANAGE_OVERLAY_PERMISSION}, which in Android 11 can't be used to show
- * a GUI for a specific package, permission {@code android.permission.INTERNAL_SYSTEM_WINDOW} is
- * needed to start an activity with this intent.
+ * Unlike {@link #ACTION_MANAGE_OVERLAY_PERMISSION}, which in Android {@link
+ * android.os.Build.VERSION_CODES#R} can't be used to show a GUI for a specific package,
+ * permission {@code android.permission.INTERNAL_SYSTEM_WINDOW} is needed to start an activity
+ * with this intent.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e65bd9f..d273500 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -301,6 +301,13 @@
public static final int LISTEN_USER_MOBILE_DATA_STATE = 0x00080000;
/**
+ * Listen for display info changed event.
+ *
+ * @see #onDisplayInfoChanged
+ */
+ public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000;
+
+ /**
* Listen for changes to the phone capability.
*
* @see #onPhoneCapabilityChanged
@@ -848,6 +855,21 @@
}
/**
+ * Callback invoked when the display info has changed on the registered subscription.
+ * <p> The {@link DisplayInfo} contains status information shown to the user based on
+ * carrier policy.
+ *
+ * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling
+ * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+ *
+ * @param displayInfo The display information.
+ */
+ @RequiresPermission((android.Manifest.permission.READ_PHONE_STATE))
+ public void onDisplayInfoChanged(@NonNull DisplayInfo displayInfo) {
+ // default implementation empty
+ }
+
+ /**
* Callback invoked when the current emergency number list has changed on the registered
* subscription.
* Note, the registration subId comes from {@link TelephonyManager} object which registers
@@ -1226,6 +1248,15 @@
() -> psl.onUserMobileDataStateChanged(enabled)));
}
+ public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> psl.onDisplayInfoChanged(displayInfo)));
+ }
+
public void onOemHookRawEvent(byte[] rawData) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 4024db1..2c077bb 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -589,6 +589,24 @@
}
/**
+ * Notify display info changed.
+ *
+ * @param slotIndex The SIM slot index for which display info has changed. Can be
+ * derived from {@code subscriptionId} except when {@code subscriptionId} is invalid, such as
+ * when the device is in emergency-only mode.
+ * @param subscriptionId Subscription id for which display network info has changed.
+ * @param displayInfo The display info.
+ */
+ public void notifyDisplayInfoChanged(int slotIndex, int subscriptionId,
+ @NonNull DisplayInfo displayInfo) {
+ try {
+ sRegistry.notifyDisplayInfoChanged(slotIndex, subscriptionId, displayInfo);
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ /**
* Notify IMS call disconnect causes which contains {@link android.telephony.ims.ImsReasonInfo}.
*
* @param subId for which ims call disconnect.
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 2548068..9cd6050e 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -882,6 +882,9 @@
} else {
hideDirectly(types);
}
+ if (mViewRoot.mView == null) {
+ return;
+ }
mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5964e12..857bc50 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -223,6 +223,13 @@
* @see #USE_NEW_INSETS_PROPERTY
* @hide
*/
+ public static int sNewInsetsMode =
+ SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0);
+
+ /**
+ * @see #USE_NEW_INSETS_PROPERTY
+ * @hide
+ */
public static final int NEW_INSETS_MODE_NONE = 0;
/**
@@ -238,13 +245,6 @@
public static final int NEW_INSETS_MODE_FULL = 2;
/**
- * @see #USE_NEW_INSETS_PROPERTY
- * @hide
- */
- public static int sNewInsetsMode =
- SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, NEW_INSETS_MODE_IME);
-
- /**
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 93659a4..a1c22e9 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -22,6 +22,7 @@
import static com.android.internal.util.ArrayUtils.convertToLongArray;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AlertDialog;
@@ -53,6 +54,8 @@
import com.android.internal.R;
import com.android.internal.util.function.pooled.PooledLambda;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -85,6 +88,17 @@
private boolean mEnabledOnLockScreen;
private int mUserId;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DialogStaus.NOT_SHOWN,
+ DialogStaus.SHOWN,
+ })
+ /** Denotes the user shortcut type. */
+ private @interface DialogStaus {
+ int NOT_SHOWN = 0;
+ int SHOWN = 1;
+ }
+
// Visible for testing
public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();
@@ -163,7 +177,8 @@
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, mUserId) == 1;
// Enable the shortcut from the lockscreen by default if the dialog has been shown
final int dialogAlreadyShown = Settings.Secure.getIntForUser(
- cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, mUserId);
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.NOT_SHOWN,
+ mUserId);
mEnabledOnLockScreen = Settings.Secure.getIntForUser(
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
dialogAlreadyShown, mUserId) == 1;
@@ -178,7 +193,8 @@
final ContentResolver cr = mContext.getContentResolver();
final int userId = ActivityManager.getCurrentUser();
final int dialogAlreadyShown = Settings.Secure.getIntForUser(
- cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.NOT_SHOWN,
+ userId);
// Play a notification vibration
Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
if ((vibrator != null) && vibrator.hasVibrator()) {
@@ -205,7 +221,8 @@
w.setAttributes(attr);
mAlertDialog.show();
Settings.Secure.putIntForUser(
- cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1, userId);
+ cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStaus.SHOWN,
+ userId);
} else {
playNotificationTone();
if (mAlertDialog != null) {
@@ -251,15 +268,8 @@
}
private AlertDialog createShortcutWarningDialog(int userId) {
- final String serviceDescription = getShortcutFeatureDescription(true /* Include summary */);
-
- if (serviceDescription == null) {
- return null;
- }
-
- final String warningMessage = String.format(
- mContext.getString(R.string.accessibility_shortcut_toogle_warning),
- serviceDescription);
+ final String warningMessage = mContext.getString(
+ R.string.accessibility_shortcut_toogle_warning);
final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(
// Use SystemUI context so we pick up any theme set in a vendor overlay
mFrameworkObjectProvider.getSystemUiContext())
@@ -272,11 +282,17 @@
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
userId);
+
+ // If canceled, treat as if the dialog has never been shown
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ DialogStaus.NOT_SHOWN, userId);
})
.setOnCancelListener((DialogInterface d) -> {
// If canceled, treat as if the dialog has never been shown
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId);
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ DialogStaus.NOT_SHOWN, userId);
})
.create();
return alertDialog;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 022573c..c2c9fff 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1278,10 +1278,9 @@
throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
+ "cannot be null.");
}
- boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true);
-
// We partially rebuild the inactive adapter to determine if we should auto launch
- mMultiProfilePagerAdapter.rebuildInactiveTab(false);
+ boolean rebuildActiveCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true);
+ boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false);
if (useLayoutWithDefault()) {
mLayoutId = R.layout.resolver_list_with_default;
@@ -1290,7 +1289,7 @@
}
setContentView(mLayoutId);
mMultiProfilePagerAdapter.setupViewPager(findViewById(R.id.profile_pager));
- return postRebuildList(rebuildCompleted);
+ return postRebuildList(rebuildActiveCompleted && rebuildInactiveCompleted);
}
/**
@@ -1338,10 +1337,11 @@
int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount();
if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
return true;
- } else if (numberOfProfiles == 2 && maybeAutolaunchIfCrossProfileSupported()) {
- // note that autolaunching when we have 2 profiles, 1 resolved target on the active
- // tab and 0 resolved targets on the inactive tab, is already handled before launching
- // ResolverActivity
+ } else if (numberOfProfiles == 2
+ && mMultiProfilePagerAdapter.getActiveListAdapter().isListLoaded()
+ && mMultiProfilePagerAdapter.getInactiveListAdapter().isListLoaded()
+ && (maybeAutolaunchIfNoAppsOnInactiveTab()
+ || maybeAutolaunchIfCrossProfileSupported())) {
return true;
}
return false;
@@ -1364,6 +1364,23 @@
return false;
}
+ private boolean maybeAutolaunchIfNoAppsOnInactiveTab() {
+ int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+ if (count != 1) {
+ return false;
+ }
+ ResolverListAdapter inactiveListAdapter =
+ mMultiProfilePagerAdapter.getInactiveListAdapter();
+ if (inactiveListAdapter.getUnfilteredCount() != 0) {
+ return false;
+ }
+ TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .targetInfoForPosition(0, false);
+ safelyStartActivity(target);
+ finish();
+ return true;
+ }
+
/**
* When we have a personal and a work profile, we auto launch in the following scenario:
* - There is 1 resolved target on each profile
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index ea84090..54453d0 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -87,6 +87,7 @@
private final ResolverListCommunicator mResolverListCommunicator;
private Runnable mPostListReadyRunnable;
private final boolean mIsAudioCaptureDevice;
+ private boolean mIsListLoaded;
public ResolverListAdapter(Context context, List<Intent> payloadIntents,
Intent[] initialIntents, List<ResolveInfo> rList,
@@ -191,6 +192,7 @@
mLastChosenPosition = -1;
mAllTargetsAreBrowsers = false;
mDisplayList.clear();
+ mIsListLoaded = false;
if (mBaseResolveList != null) {
currentResolveList = mUnfilteredResolveList = new ArrayList<>();
@@ -352,6 +354,7 @@
mResolverListCommunicator.sendVoiceChoicesIfNeeded();
postListReadyRunnable(doPostProcessing);
+ mIsListLoaded = true;
}
/**
@@ -611,6 +614,10 @@
return mIntents;
}
+ protected boolean isListLoaded() {
+ return mIsListLoaded;
+ }
+
/**
* Necessary methods to communicate between {@link ResolverListAdapter}
* and {@link ResolverActivity}.
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 0f50596..3d5dfbb 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -21,6 +21,7 @@
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.DisplayInfo;
import android.telephony.PhoneCapability;
import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
@@ -54,6 +55,7 @@
void onOemHookRawEvent(in byte[] rawData);
void onCarrierNetworkChange(in boolean active);
void onUserMobileDataStateChanged(in boolean enabled);
+ void onDisplayInfoChanged(in DisplayInfo displayInfo);
void onPhoneCapabilityChanged(in PhoneCapability capability);
void onActiveDataSubIdChanged(in int subId);
void onRadioPowerStateChanged(in int state);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 47752c5..520ffc9 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -23,6 +23,7 @@
import android.telephony.CallQuality;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
+import android.telephony.DisplayInfo;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.PhoneCapability;
import android.telephony.PhysicalChannelConfig;
@@ -87,6 +88,7 @@
void notifyOpportunisticSubscriptionInfoChanged();
void notifyCarrierNetworkChange(in boolean active);
void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
+ void notifyDisplayInfoChanged(int slotIndex, int subId, in DisplayInfo displayInfo);
void notifyPhoneCapabilityChanged(in PhoneCapability capability);
void notifyActiveDataSubIdChanged(int activeDataSubId);
void notifyRadioPowerStateChanged(in int phoneId, in int subId, in int state);
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b47b7e3..0b1c8b7 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -182,6 +182,8 @@
extern int register_android_view_MotionEvent(JNIEnv* env);
extern int register_android_view_PointerIcon(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
+extern int register_android_view_VerifiedKeyEvent(JNIEnv* env);
+extern int register_android_view_VerifiedMotionEvent(JNIEnv* env);
extern int register_android_content_res_ObbScanner(JNIEnv* env);
extern int register_android_content_res_Configuration(JNIEnv* env);
extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
@@ -1562,6 +1564,8 @@
REG_JNI(register_android_view_MotionEvent),
REG_JNI(register_android_view_PointerIcon),
REG_JNI(register_android_view_VelocityTracker),
+ REG_JNI(register_android_view_VerifiedKeyEvent),
+ REG_JNI(register_android_view_VerifiedMotionEvent),
REG_JNI(register_android_content_res_ObbScanner),
REG_JNI(register_android_content_res_Configuration),
diff --git a/core/jni/android_view_VerifiedKeyEvent.cpp b/core/jni/android_view_VerifiedKeyEvent.cpp
index 8fc301c..bba10aa 100644
--- a/core/jni/android_view_VerifiedKeyEvent.cpp
+++ b/core/jni/android_view_VerifiedKeyEvent.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "MotionEvent-JNI"
+#define LOG_TAG "VerifiedKey-JNI"
#include "android_view_VerifiedKeyEvent.h"
#include <input/Input.h>
@@ -22,18 +22,29 @@
namespace android {
+static struct {
+ jclass clazz;
+
+ jmethodID constructor;
+} gVerifiedKeyEventClassInfo;
+
// ----------------------------------------------------------------------------
jobject android_view_VerifiedKeyEvent(JNIEnv* env, const VerifiedKeyEvent& event) {
- static jclass clazz = FindClassOrDie(env, "android/view/VerifiedKeyEvent");
+ return env->NewObject(gVerifiedKeyEventClassInfo.clazz, gVerifiedKeyEventClassInfo.constructor,
+ event.deviceId, event.eventTimeNanos, event.source, event.displayId,
+ event.action, event.downTimeNanos, event.flags, event.keyCode,
+ event.scanCode, event.metaState, event.repeatCount);
+}
- static jmethodID constructor = GetMethodIDOrDie(env, clazz, "<init>", "(IJIIIJIIIII)V");
+int register_android_view_VerifiedKeyEvent(JNIEnv* env) {
+ jclass clazz = FindClassOrDie(env, "android/view/VerifiedKeyEvent");
+ gVerifiedKeyEventClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
- jobject object =
- env->NewObject(clazz, constructor, event.deviceId, event.eventTimeNanos, event.source,
- event.displayId, event.action, event.downTimeNanos, event.flags,
- event.keyCode, event.scanCode, event.metaState, event.repeatCount);
- return object;
+ gVerifiedKeyEventClassInfo.constructor =
+ GetMethodIDOrDie(env, clazz, "<init>", "(IJIIIJIIIII)V");
+
+ return OK;
}
} // namespace android
diff --git a/core/jni/android_view_VerifiedMotionEvent.cpp b/core/jni/android_view_VerifiedMotionEvent.cpp
index 7a5c71a..c281197 100644
--- a/core/jni/android_view_VerifiedMotionEvent.cpp
+++ b/core/jni/android_view_VerifiedMotionEvent.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "MotionEvent-JNI"
+#define LOG_TAG "VerifiedMotion-JNI"
#include "android_view_VerifiedMotionEvent.h"
#include <input/Input.h>
@@ -22,18 +22,30 @@
namespace android {
+static struct {
+ jclass clazz;
+
+ jmethodID constructor;
+} gVerifiedMotionEventClassInfo;
+
// ----------------------------------------------------------------------------
jobject android_view_VerifiedMotionEvent(JNIEnv* env, const VerifiedMotionEvent& event) {
- static jclass clazz = FindClassOrDie(env, "android/view/VerifiedMotionEvent");
+ return env->NewObject(gVerifiedMotionEventClassInfo.clazz,
+ gVerifiedMotionEventClassInfo.constructor, event.deviceId,
+ event.eventTimeNanos, event.source, event.displayId, event.rawX,
+ event.rawY, event.actionMasked, event.downTimeNanos, event.flags,
+ event.metaState, event.buttonState);
+}
- static jmethodID constructor = GetMethodIDOrDie(env, clazz, "<init>", "(IJIIFFIJIII)V");
+int register_android_view_VerifiedMotionEvent(JNIEnv* env) {
+ jclass clazz = FindClassOrDie(env, "android/view/VerifiedMotionEvent");
+ gVerifiedMotionEventClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
- jobject object =
- env->NewObject(clazz, constructor, event.deviceId, event.eventTimeNanos, event.source,
- event.displayId, event.rawX, event.rawY, event.actionMasked,
- event.downTimeNanos, event.flags, event.metaState, event.buttonState);
- return object;
+ gVerifiedMotionEventClassInfo.constructor =
+ GetMethodIDOrDie(env, clazz, "<init>", "(IJIIFFIJIII)V");
+
+ return OK;
}
} // namespace android
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index bf4cdee..03676dd 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -428,7 +428,7 @@
(section).args = "dropbox --proto system_app_wtf"
];
- optional android.service.dropbox.DropBoxManagerServiceDumpProto dropbox_system_server_crashes = 3037 [
+ optional android.service.dropbox.DropBoxManagerServiceDumpProto dropbox_system_server_crash = 3037 [
(section).type = SECTION_DUMPSYS,
(section).args = "dropbox --proto system_server_crash"
];
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index d1392a5..a648831 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -159,4 +159,7 @@
ALLOW_MODIFICATION_OF_ADMIN_CONFIGURED_NETWORKS = 132;
SET_TIME = 133;
SET_TIME_ZONE = 134;
+ SET_PERSONAL_APPS_SUSPENDED = 135;
+ SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF = 136;
+ COMP_TO_ORG_OWNED_PO_MIGRATED = 137;
}
diff --git a/core/proto/android/stats/mediametrics/mediametrics.proto b/core/proto/android/stats/mediametrics/mediametrics.proto
index 34ed90a..e1af962 100644
--- a/core/proto/android/stats/mediametrics/mediametrics.proto
+++ b/core/proto/android/stats/mediametrics/mediametrics.proto
@@ -154,6 +154,8 @@
optional int64 latency_avg = 18;
optional int64 latency_count = 19;
optional int64 latency_unknown = 20;
+ optional int32 queue_input_buffer_error = 21;
+ optional int32 queue_secure_input_buffer_error = 22;
}
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d1031f4..60b3a19 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1640,7 +1640,7 @@
<!-- Allows network stack services (Connectivity and Wifi) to coordinate
<p>Not for use by third-party or privileged applications.
- @SystemApi
+ @SystemApi @TestApi
@hide This should only be used by Connectivity and Wifi Services.
-->
<permission android:name="android.permission.NETWORK_STACK"
@@ -4943,6 +4943,10 @@
<permission android:name="android.permission.ACCESS_TV_TUNER"
android:protectionLevel="signature|privileged" />
+ <!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
+ <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
+ android:protectionLevel="signature|appPredictor" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 39cd00c..e6a93e5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1483,6 +1483,8 @@
<string name="fingerprint_error_no_fingerprints">No fingerprints enrolled.</string>
<!-- Generic error message shown when the app requests fingerprint authentication on a device without a sensor -->
<string name="fingerprint_error_hw_not_present">This device does not have a fingerprint sensor.</string>
+ <!-- Generic error message shown when fingerprint is not available due to a security vulnerability. [CHAR LIMIT=50] -->
+ <string name="fingerprint_error_security_update_required">Sensor temporarily disabled.</string>
<!-- Template to be used to name enrolled fingerprints by default. -->
<string name="fingerprint_name_template">Finger <xliff:g id="fingerId" example="1">%d</xliff:g></string>
@@ -1574,6 +1576,8 @@
<string name="face_error_not_enrolled">You haven\u2019t set up face unlock.</string>
<!-- Generic error message shown when the app requests face unlock on a device without a sensor. [CHAR LIMIT=61] -->
<string name="face_error_hw_not_present">Face unlock is not supported on this device.</string>
+ <!-- Generic error message shown when face unlock is not available due to a security vulnerability. [CHAR LIMIT=50] -->
+ <string name="face_error_security_update_required">Sensor temporarily disabled.</string>
<!-- Template to be used to name enrolled faces by default. [CHAR LIMIT=10] -->
<string name="face_name_template">Face <xliff:g id="faceId" example="1">%d</xliff:g></string>
@@ -4372,10 +4376,7 @@
service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_toogle_warning">
When the shortcut is on, pressing both volume buttons for 3 seconds will start an
- accessibility feature.\n\n
- Current accessibility feature:\n
- <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g>\n\n
- You can change the feature in Settings > Accessibility.
+ accessibility feature.
</string>
<!-- Text in button that edit the accessibility shortcut menu, user can delete
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8581084..5aefe11 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2458,6 +2458,7 @@
<java-symbol type="string" name="fingerprint_authenticated" />
<java-symbol type="string" name="fingerprint_error_no_fingerprints" />
<java-symbol type="string" name="fingerprint_error_hw_not_present" />
+ <java-symbol type="string" name="fingerprint_error_security_update_required" />
<!-- Fingerprint config -->
<java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/>
@@ -2502,6 +2503,7 @@
<java-symbol type="string" name="face_name_template" />
<java-symbol type="string" name="face_authenticated_no_confirmation_required" />
<java-symbol type="string" name="face_authenticated_confirmation_required" />
+ <java-symbol type="string" name="face_error_security_update_required" />
<java-symbol type="array" name="config_biometric_sensors" />
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
index 9e94bdc..afe9d7f 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
@@ -44,7 +44,7 @@
testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}")
assetsProvider = DirectoryAssetsProvider(testDir)
loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
}
@After
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
index e3ba93d..da5092d 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
@@ -119,7 +119,7 @@
val loader = ResourcesLoader()
loader.providers = listOf(one, two)
- resources.addLoader(loader)
+ resources.addLoaders(loader)
assertOpenedAsset()
inOrder(two.assetsProvider, one.assetsProvider).apply {
@@ -149,7 +149,7 @@
val loader2 = ResourcesLoader()
loader2.addProvider(two)
- resources.loaders = listOf(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
assertOpenedAsset()
inOrder(two.assetsProvider, one.assetsProvider).apply {
@@ -170,7 +170,7 @@
val loader = ResourcesLoader()
val one = ResourcesProvider.empty(assetsProvider1)
val two = ResourcesProvider.empty(assetsProvider2)
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.providers = listOf(one, two)
assertOpenedAsset()
@@ -186,7 +186,7 @@
val loader = ResourcesLoader()
val one = ResourcesProvider.empty(assetsProvider1)
val two = ResourcesProvider.empty(assetsProvider2)
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.providers = listOf(one, two)
assertOpenedAsset()
@@ -202,7 +202,7 @@
val loader = ResourcesLoader()
val one = ResourcesProvider.empty(assetsProvider1)
val two = ResourcesProvider.empty(assetsProvider2)
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.providers = listOf(one, two)
assertOpenedAsset()
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
index 0cc56d7..16eafcd 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
@@ -192,13 +192,13 @@
}
@Test
- fun addMultipleProviders() {
+ fun addProvidersRepeatedly() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.addProvider(testOne)
assertEquals(valueOne, getValue())
@@ -213,25 +213,25 @@
}
@Test
- fun addMultipleLoaders() {
+ fun addLoadersRepeatedly() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader1 = ResourcesLoader()
val loader2 = ResourcesLoader()
- resources.addLoader(loader1)
+ resources.addLoaders(loader1)
loader1.addProvider(testOne)
assertEquals(valueOne, getValue())
- resources.addLoader(loader2)
+ resources.addLoaders(loader2)
loader2.addProvider(testTwo)
assertEquals(valueTwo, getValue())
- resources.removeLoader(loader1)
+ resources.removeLoaders(loader1)
assertEquals(valueTwo, getValue())
- resources.removeLoader(loader2)
+ resources.removeLoaders(loader2)
assertEquals(originalValue, getValue())
}
@@ -242,7 +242,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.providers = listOf(testOne, testTwo)
assertEquals(valueTwo, getValue())
@@ -254,20 +254,20 @@
}
@Test
- fun setMultipleLoaders() {
+ fun addMultipleLoaders() {
val originalValue = getValue()
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
- resources.loaders = listOf(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
assertEquals(valueTwo, getValue())
- resources.removeLoader(loader2)
+ resources.removeLoaders(loader2)
assertEquals(valueOne, getValue())
- resources.loaders = Collections.emptyList()
+ resources.removeLoaders(loader1)
assertEquals(originalValue, getValue())
}
@@ -291,7 +291,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.addProvider(testOne)
loader.addProvider(testTwo)
loader.addProvider(testOne)
@@ -308,9 +308,9 @@
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
- resources.addLoader(loader1)
- resources.addLoader(loader2)
- resources.addLoader(loader1)
+ resources.addLoaders(loader1)
+ resources.addLoaders(loader2)
+ resources.addLoaders(loader1)
assertEquals(2, resources.loaders.size)
assertEquals(resources.loaders[0], loader1)
@@ -323,7 +323,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.addProvider(testOne)
loader.addProvider(testTwo)
@@ -341,12 +341,16 @@
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
- resources.loaders = listOf(loader1, loader2)
- resources.removeLoader(loader1)
- resources.removeLoader(loader1)
+ resources.addLoaders(loader1, loader2)
+ resources.removeLoaders(loader1)
+ resources.removeLoaders(loader1)
assertEquals(1, resources.loaders.size)
assertEquals(resources.loaders[0], loader2)
+
+ resources.removeLoaders(loader2, loader2)
+
+ assertEquals(0, resources.loaders.size)
}
@Test
@@ -355,7 +359,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.providers = listOf(testOne, testTwo)
loader.providers = listOf(testOne, testTwo)
@@ -365,14 +369,14 @@
}
@Test
- fun repeatedSetLoaders() {
+ fun repeatedAddMultipleLoaders() {
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
- resources.loaders = listOf(loader1, loader2)
- resources.loaders = listOf(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
assertEquals(2, resources.loaders.size)
assertEquals(resources.loaders[0], loader1)
@@ -386,7 +390,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.addProvider(testOne)
loader.addProvider(testTwo)
assertEquals(valueTwo, getValue())
@@ -414,20 +418,20 @@
val loader2 = ResourcesLoader()
loader2.addProvider(testTwo)
- resources.addLoader(loader1)
- resources.addLoader(loader2)
+ resources.addLoaders(loader1)
+ resources.addLoaders(loader2)
assertEquals(valueTwo, getValue())
- resources.removeLoader(loader1)
+ resources.removeLoaders(loader1)
assertEquals(valueTwo, getValue())
- resources.addLoader(loader1)
+ resources.addLoaders(loader1)
assertEquals(valueOne, getValue())
- resources.removeLoader(loader2)
+ resources.removeLoaders(loader2)
assertEquals(valueOne, getValue())
- resources.removeLoader(loader1)
+ resources.removeLoaders(loader1)
assertEquals(originalValue, getValue())
}
@@ -444,10 +448,11 @@
val loader2 = ResourcesLoader()
loader2.providers = listOf(testThree, testFour)
- resources.loaders = listOf(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
assertEquals(valueFour, getValue())
- resources.loaders = listOf(loader2, loader1)
+ resources.removeLoaders(loader1)
+ resources.addLoaders(loader1)
assertEquals(valueTwo, getValue())
loader1.removeProvider(testTwo)
@@ -471,7 +476,7 @@
val loader2 = ResourcesLoader()
loader2.addProvider(openTwo())
- resources.loaders = listOf(loader1)
+ resources.addLoaders(loader1)
assertEquals(valueOne, getValue())
// The child context should include the loaders of the original context.
@@ -479,12 +484,12 @@
assertEquals(valueOne, getValue(childContext))
// Changing the loaders of the child context should not affect the original context.
- childContext.resources.loaders = listOf(loader1, loader2)
+ childContext.resources.addLoaders(loader2)
assertEquals(valueOne, getValue())
assertEquals(valueTwo, getValue(childContext))
// Changing the loaders of the original context should not affect the child context.
- resources.removeLoader(loader1)
+ resources.removeLoaders(loader1)
assertEquals(originalValue, getValue())
assertEquals(valueTwo, getValue(childContext))
@@ -506,7 +511,7 @@
val testTwo = openTwo()
val loader = ResourcesLoader()
- resources.addLoader(loader)
+ resources.addLoaders(loader)
loader.addProvider(testOne)
assertEquals(valueOne, getValue())
@@ -527,7 +532,7 @@
assertEquals(originalValue, getValue())
assertEquals(originalValue, getValue(childContext2))
- childContext2.resources.addLoader(loader)
+ childContext2.resources.addLoaders(loader)
assertEquals(originalValue, getValue())
assertEquals(valueTwo, getValue(childContext))
assertEquals(valueTwo, getValue(childContext2))
@@ -539,7 +544,7 @@
loader.addProvider(openOne())
val applicationContext = context.applicationContext
- applicationContext.resources.addLoader(loader)
+ applicationContext.resources.addLoaders(loader)
assertEquals(valueOne, getValue(applicationContext))
val activity = mTestActivityRule.launchActivity(Intent())
@@ -556,7 +561,7 @@
loader2.addProvider(openTwo())
val applicationContext = context.applicationContext
- applicationContext.resources.addLoader(loader1)
+ applicationContext.resources.addLoaders(loader1)
assertEquals(valueOne, getValue(applicationContext))
var token: IBinder? = null
@@ -569,7 +574,7 @@
assertEquals(valueOne, getValue(applicationContext))
assertEquals(valueOne, getValue(activity))
- activity.resources.addLoader(loader2)
+ activity.resources.addLoaders(loader2)
assertEquals(valueOne, getValue(applicationContext))
assertEquals(valueTwo, getValue(activity))
@@ -598,10 +603,11 @@
loader2.addProvider(provider1)
loader2.addProvider(openTwo())
- resources.loaders = listOf(loader1, loader2)
+ resources.addLoaders(loader1, loader2)
assertEquals(valueTwo, getValue())
- resources.loaders = listOf(loader2, loader1)
+ resources.removeLoaders(loader1)
+ resources.addLoaders(loader1)
assertEquals(valueOne, getValue())
assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader })
diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
index c897ace..693d4ca 100644
--- a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
@@ -16,15 +16,20 @@
package android.content.integrity;
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
+
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap;
-import org.testng.annotations.Test;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
import java.util.Arrays;
import java.util.Collections;
+@RunWith(JUnit4.class)
public class InstallerAllowedByManifestFormulaTest {
private static final InstallerAllowedByManifestFormula
@@ -70,7 +75,7 @@
}
@Test
- public void testFormulaMatches_certificateNotInManifest() {
+ public void testFormulaMatches_certificateDoesNotMatchManifest() {
AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
.setInstallerName("installer1")
.setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
@@ -92,6 +97,19 @@
assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
}
+ @Test
+ public void testFormulaMatches_certificateNotSpecifiedInManifest() {
+ AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
+ .setInstallerName("installer1")
+ .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
+ .setAllowedInstallersAndCert(ImmutableMap.of(
+ "installer1", INSTALLER_CERTIFICATE_NOT_EVALUATED,
+ "installer2", "installer_cert1"
+ )).build();
+
+ assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
+ }
+
/** Returns a builder with all fields filled with some dummy data. */
private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
return new AppInstallMetadata.Builder()
diff --git a/media/java/android/media/tv/tuner/CasSessionRequest.aidl b/media/java/android/media/tv/tuner/CasSessionRequest.aidl
new file mode 100644
index 0000000..3dbf3d8
--- /dev/null
+++ b/media/java/android/media/tv/tuner/CasSessionRequest.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * A wrapper of a cas session requests that contains all the request info of the client.
+ *
+ * @hide
+ */
+parcelable CasSessionRequest;
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/CasSessionRequest.java b/media/java/android/media/tv/tuner/CasSessionRequest.java
new file mode 100644
index 0000000..0f6a885
--- /dev/null
+++ b/media/java/android/media/tv/tuner/CasSessionRequest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Information required to request a Cas Session.
+ *
+ * @hide
+ */
+public final class CasSessionRequest implements Parcelable {
+ static final String TAG = "CasSessionRequest";
+
+ public static final
+ @NonNull
+ Parcelable.Creator<CasSessionRequest> CREATOR =
+ new Parcelable.Creator<CasSessionRequest>() {
+ @Override
+ public CasSessionRequest createFromParcel(Parcel source) {
+ try {
+ return new CasSessionRequest(source);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating CasSessionRequest from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public CasSessionRequest[] newArray(int size) {
+ return new CasSessionRequest[size];
+ }
+ };
+
+ /**
+ * Client id of the client that sends the request.
+ */
+ private final int mClientId;
+
+ /**
+ * System id of the requested cas.
+ */
+ private final int mCasSystemId;
+
+ private CasSessionRequest(@NonNull Parcel source) {
+ mClientId = source.readInt();
+ mCasSystemId = source.readInt();
+ }
+
+ /**
+ * Constructs a new {@link CasSessionRequest} with the given parameters.
+ *
+ * @param clientId id of the client.
+ * @param casSystemId the cas system id that the client is requesting.
+ */
+ public CasSessionRequest(int clientId,
+ int casSystemId) {
+ mClientId = clientId;
+ mCasSystemId = casSystemId;
+ }
+
+ /**
+ * Returns the id of the client.
+ */
+ public int getClientId() {
+ return mClientId;
+ }
+
+ /**
+ * Returns the cas system id requested.
+ */
+ public int getCasSystemId() {
+ return mCasSystemId;
+ }
+
+ // Parcelable
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+ b.append("CasSessionRequest {clientId=").append(mClientId);
+ b.append(", casSystemId=").append(mCasSystemId);
+ b.append("}");
+ return b.toString();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mClientId);
+ dest.writeInt(mCasSystemId);
+ }
+}
diff --git a/media/java/android/media/tv/tuner/ITunerResourceManager.aidl b/media/java/android/media/tv/tuner/ITunerResourceManager.aidl
new file mode 100644
index 0000000..758c689
--- /dev/null
+++ b/media/java/android/media/tv/tuner/ITunerResourceManager.aidl
@@ -0,0 +1,244 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.media.tv.tuner.CasSessionRequest;
+import android.media.tv.tuner.ITunerResourceManagerListener;
+import android.media.tv.tuner.ResourceClientProfile;
+import android.media.tv.tuner.TunerFrontendInfo;
+import android.media.tv.tuner.TunerFrontendRequest;
+import android.media.tv.tuner.TunerLnbRequest;
+
+/**
+ * Interface of the Tuner Resource Manager. It manages resources used by TV Tuners.
+ * <p>Resources include:
+ * <ul>
+ * <li>TunerFrontend {@link android.media.tv.tuner.frontend}.
+ * <li>TunerLnb {@link android.media.tv.tuner.Lnb}.
+ * <li>MediaCas {@link android.media.MediaCas}.
+ * <li>TvInputHardware {@link android.media.tv.TvInputHardwareInfo}.
+ * <ul>
+ *
+ * <p>Expected workflow is:
+ * <ul>
+ * <li>Tuner Java/MediaCas/TIF update resources of the current device with TRM.
+ * <li>Client registers its profile through {@link #registerClientProfile(ResourceClientProfile,
+ * ITunerResourceManagerListener, int[])}.
+ * <li>Client requests resources through request APIs.
+ * <li>If the resource needs to be handed to a higher priority client from a lower priority
+ * one, TRM calls ITunerResourceManagerListener registered by the lower priority client to release
+ * the resource.
+ * <ul>
+ *
+ * @hide
+ */
+interface ITunerResourceManager {
+ /*
+ * This API is used by the client to register their profile with the Tuner Resource manager.
+ *
+ * <p>The profile contains information that can show the base priority score of the client.
+ *
+ * @param profile {@link ResourceClientProfile} profile of the current client
+ * @param listener {@link ITunerResourceManagerListener} a callback to
+ * reclaim clients' resources when needed.
+ * @param clientId returns a clientId from the resource manager when the
+ * the client registers its profile.
+ */
+ void registerClientProfile(in ResourceClientProfile profile,
+ ITunerResourceManagerListener listener, out int[] clientId);
+
+ /*
+ * This API is used by the client to unregister their profile with the Tuner Resource manager.
+ *
+ * @param clientId the client id that needs to be unregistered.
+ */
+ void unregisterClientProfile(in int clientId);
+
+ /*
+ * Updates a registered client's priority and niceValue.
+ *
+ * @param clientId the id of the client that is updating its profile.
+ * @param priority the priority that the client would like to update to.
+ * @param niceValue the nice value that the client would like to update to.
+ *
+ * @return true if the update is successful.
+ */
+ boolean updateClientPriority(in int clientId, in int priority, in int niceValue);
+
+ /*
+ * Updates the available Frontend resources information on the current device.
+ *
+ * <p><strong>Note:</strong> This update must happen before the first
+ * {@link #requestFrontend(TunerFrontendRequest,int[])} and {@link #releaseFrontend(int)} call.
+ *
+ * @param infos an array of the available {@link TunerFrontendInfo} information.
+ */
+ void setFrontendInfoList(in TunerFrontendInfo[] infos);
+
+ /*
+ * Updates the available Cas resource information on the current device.
+ *
+ * <p><strong>Note:</strong> This update must happen before the first
+ * {@link #requestCasSession(CasSessionRequest, int[])} and {@link #releaseCasSession(int)} call.
+ *
+ * @param casSystemId id of the updating CAS system.
+ * @param maxSessionNum the max session number of the CAS system that is updated.
+ */
+ void updateCasInfo(in int casSystemId, in int maxSessionNum);
+
+ /*
+ * Updates the available Lnb resource information on the current device.
+ *
+ * <p><strong>Note:</strong> This update must happen before the first
+ * {@link #requestLnb(TunerLnbRequest, int[])} and {@link #releaseLnb(int)} call.
+ *
+ * @param lnbIds ids of the updating lnbs.
+ */
+ void setLnbInfoList(in int[] lnbIds);
+
+ /*
+ * This API is used by the Tuner framework to request an available frontend from the TunerHAL.
+ *
+ * <p>There are three possible scenarios:
+ * <ul>
+ * <li>If there is frontend available, the API would send the id back.
+ *
+ * <li>If no Frontend is available but the current request info can show higher priority than
+ * other uses of Frontend, the API will send
+ * {@link ITunerResourceManagerListener#onResourcesReclaim()} to the {@link Tuner}. Tuner would
+ * handle the resource reclaim on the holder of lower priority and notify the holder of its
+ * resource loss.
+ *
+ * <li>If no frontend can be granted, the API would return false.
+ * <ul>
+ *
+ * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+ * before this request.
+ *
+ * @param request {@link TunerFrontendRequest} information of the current request.
+ * @param frontendId a one-element array to return the granted frontendId.
+ *
+ * @return true if there is frontend granted.
+ */
+ boolean requestFrontend(in TunerFrontendRequest request, out int[] frontendId);
+
+ /*
+ * Requests to share frontend with an existing client.
+ *
+ * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+ * before this request.
+ *
+ * @param selfClientId the id of the client that sends the request.
+ * @param targetClientId the id of the client to share the frontend with.
+ */
+ void shareFrontend(in int selfClientId, in int targetClientId);
+
+ /*
+ * This API is used by the Tuner framework to request an available Cas session. This session
+ * needs to be under the CAS system with the id indicated in the {@code request}.
+ *
+ * <p>There are three possible scenarios:
+ * <ul>
+ * <li>If there is Cas session available, the API would send the id back.
+ *
+ * <li>If no Cas session is available but the current request info can show higher priority than
+ * other uses of the sessions under the requested CAS system, the API will send
+ * {@link ITunerResourceManagerCallback#onResourcesReclaim()} to the {@link Tuner}. Tuner would
+ * handle the resource reclaim on the holder of lower priority and notify the holder of its
+ * resource loss.
+ *
+ * <li>If no Cas session can be granted, the API would return false.
+ * <ul>
+ *
+ * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this request.
+ *
+ * @param request {@link CasSessionRequest} information of the current request.
+ * @param sessionResourceId a one-element array to return the granted cas session id.
+ *
+ * @return true if there is CAS session granted.
+ */
+ boolean requestCasSession(in CasSessionRequest request, out int[] sessionResourceId);
+
+ /*
+ * This API is used by the Tuner framework to request an available Lnb from the TunerHAL.
+ *
+ * <p>There are three possible scenarios:
+ * <ul>
+ * <li>If there is Lnb available, the API would send the id back.
+ *
+ * <li>If no Lnb is available but the current request has a higher priority than other uses of
+ * lnbs, the API will send {@link ITunerResourceManagerCallback#onResourcesReclaim()} to the
+ * {@link Tuner}. Tuner would handle the resource reclaim on the holder of lower priority and
+ * notify the holder of its resource loss.
+ *
+ * <li>If no Lnb system can be granted, the API would return false.
+ * <ul>
+ *
+ * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this request.
+ *
+ * @param request {@link TunerLnbRequest} information of the current request.
+ * @param lnbId a one-element array to return the granted Lnb id.
+ *
+ * @return true if there is Lnb granted.
+ */
+ boolean requestLnb(in TunerLnbRequest request, out int[] lnbId);
+
+ /*
+ * Notifies the TRM that the given frontend has been released.
+ *
+ * <p>Client must call this whenever it releases a Tuner frontend.
+ *
+ * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+ * before this release.
+ *
+ * @param frontendId the id of the released frontend.
+ */
+ void releaseFrontend(in int frontendId);
+
+ /*
+ * Notifies the TRM that the given Cas session has been released.
+ *
+ * <p>Client must call this whenever it releases a Cas session.
+ *
+ * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this release.
+ *
+ * @param sessionResourceId the id of the released CAS session.
+ */
+ void releaseCasSession(in int sessionResourceId);
+
+ /*
+ * Notifies the TRM that the Lnb with the given id was released.
+ *
+ * <p>Client must call this whenever it releases an Lnb.
+ *
+ * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this release.
+ *
+ * @param lnbId the id of the released Tuner Lnb.
+ */
+ void releaseLnb(in int lnbId);
+
+ /*
+ * Compare two clients' priority.
+ *
+ * @param challengerProfile the {@link ResourceClientProfile} of the challenger.
+ * @param holderProfile the {@link ResourceClientProfile} of the holder of the resource.
+ *
+ * @return true if the challenger has higher priority than the holder.
+ */
+ boolean isHigherPriority(in ResourceClientProfile challengerProfile,
+ in ResourceClientProfile holderProfile);
+}
diff --git a/media/java/android/media/tv/tuner/ITunerResourceManagerListener.aidl b/media/java/android/media/tv/tuner/ITunerResourceManagerListener.aidl
new file mode 100644
index 0000000..557032c
--- /dev/null
+++ b/media/java/android/media/tv/tuner/ITunerResourceManagerListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * Interface to receive callbacks from ITunerResourceManager.
+ *
+ * @hide
+ */
+oneway interface ITunerResourceManagerListener {
+ /*
+ * TRM invokes this method when the client's resources need to be reclaimed.
+ *
+ * <p>This method is implemented in Tuner Framework to take the reclaiming
+ * actions. It's a synchonized call. TRM would wait on the call to finish
+ * then grant the resource.
+ */
+ void onResourcesReclaim();
+}
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/ResourceClientProfile.aidl b/media/java/android/media/tv/tuner/ResourceClientProfile.aidl
new file mode 100644
index 0000000..da3c5c4
--- /dev/null
+++ b/media/java/android/media/tv/tuner/ResourceClientProfile.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * A profile of a resource client. This profile is used to register the client info
+ * with the Tuner Resource Manager.
+ *
+ * @hide
+ */
+parcelable ResourceClientProfile;
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/ResourceClientProfile.java b/media/java/android/media/tv/tuner/ResourceClientProfile.java
new file mode 100644
index 0000000..e203135
--- /dev/null
+++ b/media/java/android/media/tv/tuner/ResourceClientProfile.java
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A profile of a resource client. This profile is used to register the client info
+ * with the Tuner Resource Manager(TRM).
+ *
+ * @hide
+ */
+public final class ResourceClientProfile implements Parcelable {
+ static final String TAG = "ResourceClientProfile";
+
+ public static final
+ @NonNull
+ Parcelable.Creator<ResourceClientProfile> CREATOR =
+ new Parcelable.Creator<ResourceClientProfile>() {
+ @Override
+ public ResourceClientProfile createFromParcel(Parcel source) {
+ try {
+ return new ResourceClientProfile(source);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating ResourceClientProfile from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public ResourceClientProfile[] newArray(int size) {
+ return new ResourceClientProfile[size];
+ }
+ };
+
+ /**
+ * This is used by TRM to get TV App’s processId from TIF.
+ * The processId will be used to identify foreground applications.
+ *
+ * <p>MediaCas, Tuner and TvInputHardwareManager get tvInputSessionId from TIS.
+ * If mTvInputSessionId is UNKNOWN, the client is always background.
+ */
+ private final String mTvInputSessionId;
+
+ /**
+ * Usage of the client.
+ */
+ private final int mUseCase;
+
+ private ResourceClientProfile(@NonNull Parcel source) {
+ mTvInputSessionId = source.readString();
+ mUseCase = source.readInt();
+ }
+
+ /**
+ * Constructs a new {@link ResourceClientProfile} with the given parameters.
+ *
+ * @param tvInputSessionId the unique id of the session owned by the client.
+ * @param useCase the usage of the client. Suggested priority hints are
+ * {@link android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK}
+ * {@link android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE}
+ * {@link android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD}.
+ * New [use case : priority value] pair can be defined in the manifest by the
+ * OEM. Any undefined use case would cause IllegalArgumentException.
+ */
+ public ResourceClientProfile(@NonNull String tvInputSessionId,
+ int useCase) {
+ mTvInputSessionId = tvInputSessionId;
+ mUseCase = useCase;
+ }
+
+ /**
+ * Returns the tv input session id of the client.
+ *
+ * @return the value of the tv input session id.
+ */
+ @NonNull
+ public String getTvInputSessionId() {
+ return mTvInputSessionId;
+ }
+
+ /**
+ * Returns the user usage of the client.
+ *
+ * @return the value of use case.
+ */
+ public int getUseCase() {
+ return mUseCase;
+ }
+
+ // Parcelable
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+ b.append("ResourceClientProfile {tvInputSessionId=").append(mTvInputSessionId);
+ b.append(", useCase=").append(mUseCase);
+ b.append("}");
+ return b.toString();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mTvInputSessionId);
+ dest.writeInt(mUseCase);
+ }
+}
diff --git a/media/java/android/media/tv/tuner/TunerFrontendInfo.aidl b/media/java/android/media/tv/tuner/TunerFrontendInfo.aidl
new file mode 100644
index 0000000..012e051
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerFrontendInfo.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * Simple container of the FrontendInfo struct defined in the TunerHAL 1.0 interface.
+ *
+ * @hide
+ */
+parcelable TunerFrontendInfo;
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/TunerFrontendInfo.java b/media/java/android/media/tv/tuner/TunerFrontendInfo.java
new file mode 100644
index 0000000..a62ecb3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerFrontendInfo.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.frontend.FrontendSettings.Type;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Simple container of the FrontendInfo struct defined in the TunerHAL 1.0 interface.
+ *
+ * <p>Note that this object is defined to pass necessary frontend info between the
+ * Tuner Resource Manager and the client. It includes partial information in
+ * {@link FrontendInfo}.
+ *
+ * @hide
+ */
+public final class TunerFrontendInfo implements Parcelable {
+ static final String TAG = "TunerFrontendInfo";
+
+ public static final
+ @NonNull
+ Parcelable.Creator<TunerFrontendInfo> CREATOR =
+ new Parcelable.Creator<TunerFrontendInfo>() {
+ @Override
+ public TunerFrontendInfo createFromParcel(Parcel source) {
+ try {
+ return new TunerFrontendInfo(source);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating TunerFrontendInfo from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public TunerFrontendInfo[] newArray(int size) {
+ return new TunerFrontendInfo[size];
+ }
+ };
+
+ private final int mId;
+
+ @Type
+ private final int mFrontendType;
+
+ /**
+ * Frontends are assigned with the same exclusiveGroupId if they can't
+ * function at same time. For instance, they share same hardware module.
+ */
+ private final int mExclusiveGroupId;
+
+ private TunerFrontendInfo(@NonNull Parcel source) {
+ mId = source.readInt();
+ mFrontendType = source.readInt();
+ mExclusiveGroupId = source.readInt();
+ }
+
+ /**
+ * Constructs a new {@link TunerFrontendInfo} with the given parameters.
+ *
+ * @param frontendType the type of the frontend.
+ * @param exclusiveGroupId the group id of the frontend. FE with the same
+ group id can't function at the same time.
+ */
+ public TunerFrontendInfo(int id,
+ @Type int frontendType,
+ int exclusiveGroupId) {
+ mId = id;
+ mFrontendType = frontendType;
+ mExclusiveGroupId = exclusiveGroupId;
+ }
+
+ /**
+ * Returns the frontend id.
+ *
+ * @return the value of the frontend id.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the application id that requests the tuner frontend resource.
+ *
+ * @return the value of the frontend type.
+ */
+ @Type
+ public int getFrontendType() {
+ return mFrontendType;
+ }
+
+ /**
+ * Returns the exclusiveGroupId. Frontends with the same exclusiveGroupId
+ * can't function at same time.
+ *
+ * @return the value of the exclusive group id.
+ */
+ public int getExclusiveGroupId() {
+ return mExclusiveGroupId;
+ }
+
+ // Parcelable
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+ b.append("TunerFrontendInfo {id=").append(mId);
+ b.append(", frontendType=").append(mFrontendType);
+ b.append(", exclusiveGroupId=").append(mExclusiveGroupId);
+ b.append("}");
+ return b.toString();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mId);
+ dest.writeInt(mFrontendType);
+ dest.writeInt(mExclusiveGroupId);
+ }
+}
diff --git a/media/java/android/media/tv/tuner/TunerFrontendRequest.aidl b/media/java/android/media/tv/tuner/TunerFrontendRequest.aidl
new file mode 100644
index 0000000..25c298f
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerFrontendRequest.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * Information required to request a Tuner Frontend.
+ *
+ * @hide
+ */
+parcelable TunerFrontendRequest;
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/TunerFrontendRequest.java b/media/java/android/media/tv/tuner/TunerFrontendRequest.java
new file mode 100644
index 0000000..01a0a09
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerFrontendRequest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.frontend.FrontendSettings.Type;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Information required to request a Tuner Frontend.
+ *
+ * @hide
+ */
+public final class TunerFrontendRequest implements Parcelable {
+ static final String TAG = "TunerFrontendRequest";
+
+ public static final
+ @NonNull
+ Parcelable.Creator<TunerFrontendRequest> CREATOR =
+ new Parcelable.Creator<TunerFrontendRequest>() {
+ @Override
+ public TunerFrontendRequest createFromParcel(Parcel source) {
+ try {
+ return new TunerFrontendRequest(source);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating TunerFrontendRequest from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public TunerFrontendRequest[] newArray(int size) {
+ return new TunerFrontendRequest[size];
+ }
+ };
+
+ private final int mClientId;
+ @Type
+ private final int mFrontendType;
+
+ private TunerFrontendRequest(@NonNull Parcel source) {
+ mClientId = source.readInt();
+ mFrontendType = source.readInt();
+ }
+
+ /**
+ * Constructs a new {@link TunerFrontendRequest} with the given parameters.
+ *
+ * @param clientId the unique id of the client returned when registering profile.
+ * @param frontendType the type of the requested frontend.
+ */
+ public TunerFrontendRequest(int clientId,
+ @Type int frontendType) {
+ mClientId = clientId;
+ mFrontendType = frontendType;
+ }
+
+ /**
+ * Returns the client id that requests the tuner frontend resource.
+ *
+ * @return the value of the client id.
+ */
+ public int getClientId() {
+ return mClientId;
+ }
+
+ /**
+ * Returns the frontend type that the client requests for.
+ *
+ * @return the value of the requested frontend type.
+ */
+ @Type
+ public int getFrontendType() {
+ return mFrontendType;
+ }
+
+ // Parcelable
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+ b.append("TunerFrontendRequest {clientId=").append(mClientId);
+ b.append(", frontendType=").append(mFrontendType);
+ b.append("}");
+ return b.toString();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mClientId);
+ dest.writeInt(mFrontendType);
+ }
+}
diff --git a/media/java/android/media/tv/tuner/TunerLnbRequest.aidl b/media/java/android/media/tv/tuner/TunerLnbRequest.aidl
new file mode 100644
index 0000000..b811e39
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerLnbRequest.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+/**
+ * Information required to request a Tuner Lnb.
+ *
+ * @hide
+ */
+parcelable TunerLnbRequest;
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/TunerLnbRequest.java b/media/java/android/media/tv/tuner/TunerLnbRequest.java
new file mode 100644
index 0000000..60cd790
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerLnbRequest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Information required to request a Tuner Lnb.
+ *
+ * @hide
+ */
+public final class TunerLnbRequest implements Parcelable {
+ static final String TAG = "TunerLnbRequest";
+
+ public static final
+ @NonNull
+ Parcelable.Creator<TunerLnbRequest> CREATOR =
+ new Parcelable.Creator<TunerLnbRequest>() {
+ @Override
+ public TunerLnbRequest createFromParcel(Parcel source) {
+ try {
+ return new TunerLnbRequest(source);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating TunerLnbRequest from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public TunerLnbRequest[] newArray(int size) {
+ return new TunerLnbRequest[size];
+ }
+ };
+
+ /**
+ * Client id of the client that sends the request.
+ */
+ private final int mClientId;
+
+ private TunerLnbRequest(@NonNull Parcel source) {
+ mClientId = source.readInt();
+ }
+
+ /**
+ * Constructs a new {@link TunerLnbRequest} with the given parameters.
+ *
+ * @param clientId the id of the client.
+ */
+ public TunerLnbRequest(int clientId) {
+ mClientId = clientId;
+ }
+
+ /**
+ * Returns the id of the client
+ */
+ public int getClientId() {
+ return mClientId;
+ }
+
+ // Parcelable
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder(128);
+ b.append("TunerLnbRequest {clientId=").append(mClientId);
+ b.append("}");
+ return b.toString();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mClientId);
+ }
+}
diff --git a/media/java/android/media/tv/tuner/TunerResourceManager.java b/media/java/android/media/tv/tuner/TunerResourceManager.java
new file mode 100644
index 0000000..68ca572
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerResourceManager.java
@@ -0,0 +1,407 @@
+/*
+ * 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.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface of the Tuner Resource Manager(TRM). It manages resources used by TV Tuners.
+ * <p>Resources include:
+ * <ul>
+ * <li>TunerFrontend {@link android.media.tv.tuner.frontend}.
+ * <li>TunerLnb {@link android.media.tv.tuner.Lnb}.
+ * <li>MediaCas {@link android.media.MediaCas}.
+ * <ul>
+ *
+ * <p>Expected workflow is:
+ * <ul>
+ * <li>Tuner Java/MediaCas/TIF update resources of the current device with TRM.
+ * <li>Client registers its profile through {@link #registerClientProfile(ResourceClientProfile,
+ * Executor, ResourceListener, int[])}.
+ * <li>Client requests resources through request APIs.
+ * <li>If the resource needs to be handed to a higher priority client from a lower priority
+ * one, TRM calls ITunerResourceManagerListener registered by the lower priority client to release
+ * the resource.
+ * <ul>
+ *
+ * <p>TRM also exposes its priority comparison algorithm as a helping method to other services.
+ * {@see #isHigherPriority(ResourceClientProfile, ResourceClientProfile)}.
+ *
+ * @hide
+ */
+@RequiresFeature(PackageManager.FEATURE_LIVE_TV)
+@SystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE)
+public class TunerResourceManager {
+ private static final String TAG = "TunerResourceManager";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final int INVALID_FRONTEND_ID = -1;
+ public static final int INVALID_CAS_SESSION_RESOURCE_ID = -1;
+ public static final int INVALID_LNB_ID = -1;
+ public static final int INVALID_TV_INPUT_DEVICE_ID = -1;
+ public static final int INVALID_TV_INPUT_PORT_ID = -1;
+
+ private final ITunerResourceManager mService;
+ private final int mUserId;
+
+ /**
+ * @hide
+ */
+ public TunerResourceManager(ITunerResourceManager service, int userId) {
+ mService = service;
+ mUserId = userId;
+ }
+
+ /**
+ * This API is used by the client to register their profile with the Tuner Resource manager.
+ *
+ * <p>The profile contains information that can show the base priority score of the client.
+ *
+ * @param profile {@link ResourceClientProfile} profile of the current client. Undefined use
+ * case would cause IllegalArgumentException.
+ * @param executor the executor on which the listener would be invoked.
+ * @param listener {@link ResourceListener} callback to reclaim clients' resources when needed.
+ * @param clientId returned a clientId from the resource manager when the
+ * the client registeres.
+ * @throws IllegalArgumentException when {@code profile} contains undefined use case.
+ */
+ public void registerClientProfile(@NonNull ResourceClientProfile profile,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ResourceListener listener,
+ @NonNull int[] clientId) {
+ // TODO: throw new IllegalArgumentException("Unknown client use case")
+ // when the use case is not defined.
+ try {
+ mService.registerClientProfile(profile,
+ new ITunerResourceManagerListener.Stub() {
+ @Override
+ public void onResourcesReclaim() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onResourcesReclaim());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }, clientId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This API is used by the client to unregister their profile with the
+ * Tuner Resource manager.
+ *
+ * @param clientId the client id that needs to be unregistered.
+ */
+ public void unregisterClientProfile(int clientId) {
+ try {
+ mService.unregisterClientProfile(clientId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This API is used by client to update its registered {@link ResourceClientProfile}.
+ *
+ * <p>We recommend creating a new tuner instance for different use cases instead of using this
+ * API since different use cases may need different resources.
+ *
+ * <p>If TIS updates use case, it needs to ensure underneath resources are exchangeable between
+ * two different use cases.
+ *
+ * <p>Only the arbitrary priority and niceValue are allowed to be updated.
+ *
+ * @param clientId the id of the client that is updating its profile.
+ * @param priority the priority that the client would like to update to.
+ * @param niceValue the nice value that the client would like to update to.
+ *
+ * @return true if the update is successful.
+ */
+ public boolean updateClientPriority(int clientId, int priority, int niceValue) {
+ boolean result = false;
+ try {
+ result = mService.updateClientPriority(clientId, priority, niceValue);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
+ /**
+ * Updates the current TRM of the TunerHAL Frontend information.
+ *
+ * <p><strong>Note:</strong> This update must happen before the first
+ * {@link #requestFrontend(TunerFrontendRequest, int[])} and {@link #releaseFrontend(int)} call.
+ *
+ * @param infos an array of the available {@link TunerFrontendInfo} information.
+ */
+ public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) {
+ try {
+ mService.setFrontendInfoList(infos);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Updates the TRM of the current CAS information.
+ *
+ * <p><strong>Note:</strong> This update must happen before the first
+ * {@link #requestCasSession(CasSessionRequest, int[])} and {@link #releaseCasSession(int)}
+ * call.
+ *
+ * @param casSystemId id of the updating CAS system.
+ * @param maxSessionNum the max session number of the CAS system that is updated.
+ */
+ public void updateCasInfo(int casSystemId, int maxSessionNum) {
+ try {
+ mService.updateCasInfo(casSystemId, maxSessionNum);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Updates the TRM of the current Lnb information.
+ *
+ * <p><strong>Note:</strong> This update must happen before the first
+ * {@link #requestLnb(TunerLnbRequest, int[])} and {@link #releaseLnb(int)} call.
+ *
+ * @param lnbIds ids of the updating lnbs.
+ */
+ public void setLnbInfoList(int[] lnbIds) {
+ try {
+ mService.setLnbInfoList(lnbIds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests a frontend resource.
+ *
+ * <p>There are three possible scenarios:
+ * <ul>
+ * <li>If there is frontend available, the API would send the id back.
+ *
+ * <li>If no Frontend is available but the current request info can show higher priority than
+ * other uses of Frontend, the API will send
+ * {@link ITunerResourceManagerListener#onResourcesReclaim()} to the {@link Tuner}. Tuner would
+ * handle the resource reclaim on the holder of lower priority and notify the holder of its
+ * resource loss.
+ *
+ * <li>If no frontend can be granted, the API would return false.
+ * <ul>
+ *
+ * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+ * before this request.
+ *
+ * @param request {@link TunerFrontendRequest} information of the current request.
+ * @param frontendId a one-element array to return the granted frontendId. If
+ * no frontend granted, this will return {@link #INVALID_FRONTEND_ID}.
+ *
+ * @return true if there is frontend granted.
+ */
+ public boolean requestFrontend(@NonNull TunerFrontendRequest request,
+ @Nullable int[] frontendId) {
+ boolean result = false;
+ try {
+ result = mService.requestFrontend(request, frontendId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
+ /**
+ * Requests from the client to share frontend with an existing client.
+ *
+ * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+ * before this request.
+ *
+ * @param selfClientId the id of the client that sends the request.
+ * @param targetClientId the id of the client to share the frontend with.
+ */
+ public void shareFrontend(int selfClientId, int targetClientId) {
+ try {
+ mService.shareFrontend(selfClientId, targetClientId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests a CAS session resource.
+ *
+ * <p>There are three possible scenarios:
+ * <ul>
+ * <li>If there is Cas session available, the API would send the id back.
+ *
+ * <li>If no Cas system is available but the current request info can show higher priority than
+ * other uses of the cas sessions under the requested cas system, the API will send
+ * {@link ITunerResourceManagerListener#onResourcesReclaim()} to the {@link Tuner}. Tuner would
+ * handle the resource reclaim on the holder of lower priority and notify the holder of its
+ * resource loss.
+ *
+ * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this
+ * request.
+ *
+ * @param request {@link CasSessionRequest} information of the current request.
+ * @param sessionResourceId a one-element array to return the granted cas session id.
+ * If no CAS granted, this will return
+ * {@link #INVALID_CAS_SESSION_RESOURCE_ID}.
+ *
+ * @return true if there is CAS session granted.
+ */
+ public boolean requestCasSession(@NonNull CasSessionRequest request,
+ @NonNull int[] sessionResourceId) {
+ boolean result = false;
+ try {
+ result = mService.requestCasSession(request, sessionResourceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
+ /**
+ * Requests a Tuner Lnb resource.
+ *
+ * <p>There are three possible scenarios:
+ * <ul>
+ * <li>If there is Lnb available, the API would send the id back.
+ *
+ * <li>If no Lnb is available but the current request has a higher priority than other uses of
+ * lnbs, the API will send {@link ITunerResourceManagerListener#onResourcesReclaim()} to the
+ * {@link Tuner}. Tuner would handle the resource reclaim on the holder of lower priority and
+ * notify the holder of its resource loss.
+ *
+ * <li>If no Lnb system can be granted, the API would return false.
+ * <ul>
+ *
+ * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this request.
+ *
+ * @param request {@link TunerLnbRequest} information of the current request.
+ * @param lnbId a one-element array to return the granted Lnb id.
+ * If no Lnb granted, this will return {@link #INVALID_LNB_ID}.
+ *
+ * @return true if there is Lnb granted.
+ */
+ public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbId) {
+ boolean result = false;
+ try {
+ result = mService.requestLnb(request, lnbId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return result;
+ }
+
+ /**
+ * Notifies the TRM that the given frontend has been released.
+ *
+ * <p>Client must call this whenever it releases a Tuner frontend.
+ *
+ * <p><strong>Note:</strong> {@link #setFrontendInfoList(TunerFrontendInfo[])} must be called
+ * before this release.
+ *
+ * @param frontendId the id of the released frontend.
+ */
+ public void releaseFrontend(int frontendId) {
+ try {
+ mService.releaseFrontend(frontendId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies the TRM that the given Cas session has been released.
+ *
+ * <p>Client must call this whenever it releases a Cas session.
+ *
+ * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this
+ * release.
+ *
+ * @param sessionResourceId the id of the released CAS session.
+ */
+ public void releaseCasSession(int sessionResourceId) {
+ try {
+ mService.releaseCasSession(sessionResourceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies the TRM that the Lnb with the given id has been released.
+ *
+ * <p>Client must call this whenever it releases an Lnb.
+ *
+ * <p><strong>Note:</strong> {@link #setLnbInfos(int[])} must be called before this release.
+ *
+ * @param lnbId the id of the released Tuner Lnb.
+ */
+ public void releaseLnb(int lnbId) {
+ try {
+ mService.releaseLnb(lnbId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Compare two clients' priority.
+ *
+ * @param challengerProfile the {@link ResourceClientProfile} of the challenger.
+ * @param holderProfile the {@link ResourceClientProfile} of the holder of the resource.
+ *
+ * @return true if the challenger has higher priority than the holder.
+ */
+ public boolean isHigherPriority(ResourceClientProfile challengerProfile,
+ ResourceClientProfile holderProfile) {
+ try {
+ return mService.isHigherPriority(challengerProfile, holderProfile);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Interface used to receive events from TunerResourceManager.
+ */
+ public abstract static class ResourceListener {
+ /*
+ * To reclaim all the resources of the callack owner.
+ */
+ public abstract void onResourcesReclaim();
+ }
+}
diff --git a/packages/PrintSpooler/res/values-ja/donottranslate.xml b/packages/PrintSpooler/res/values-ja/donottranslate.xml
index d334ddd..6a0f768 100644
--- a/packages/PrintSpooler/res/values-ja/donottranslate.xml
+++ b/packages/PrintSpooler/res/values-ja/donottranslate.xml
@@ -16,7 +16,7 @@
<resources>
- <string name="mediasize_default">JIS_B5</string>
+ <string name="mediasize_default">ISO_A4</string>
<string name="mediasize_standard">@string/mediasize_standard_japan</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index a1342ec..984ab11 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -35,6 +35,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice.
@@ -53,7 +54,7 @@
int STATE_DISCONNECTED = 3;
}
- private final Collection<DeviceCallback> mCallbacks = new ArrayList<>();
+ private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
@VisibleForTesting
final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
@@ -73,18 +74,14 @@
* Register to start receiving callbacks for MediaDevice events.
*/
public void registerCallback(DeviceCallback callback) {
- synchronized (mCallbacks) {
- mCallbacks.add(callback);
- }
+ mCallbacks.add(callback);
}
/**
* Unregister to stop receiving callbacks for MediaDevice events
*/
public void unregisterCallback(DeviceCallback callback) {
- synchronized (mCallbacks) {
- mCallbacks.remove(callback);
- }
+ mCallbacks.remove(callback);
}
public LocalMediaManager(Context context, String packageName, Notification notification) {
@@ -152,10 +149,8 @@
}
void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
- synchronized (mCallbacks) {
- for (DeviceCallback callback : mCallbacks) {
- callback.onSelectedDeviceStateChanged(device, state);
- }
+ for (DeviceCallback callback : getCallbacks()) {
+ callback.onSelectedDeviceStateChanged(device, state);
}
}
@@ -169,19 +164,15 @@
}
void dispatchDeviceListUpdate() {
- synchronized (mCallbacks) {
- Collections.sort(mMediaDevices, COMPARATOR);
- for (DeviceCallback callback : mCallbacks) {
- callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
- }
+ Collections.sort(mMediaDevices, COMPARATOR);
+ for (DeviceCallback callback : getCallbacks()) {
+ callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
}
}
void dispatchDeviceAttributesChanged() {
- synchronized (mCallbacks) {
- for (DeviceCallback callback : mCallbacks) {
- callback.onDeviceAttributesChanged();
- }
+ for (DeviceCallback callback : getCallbacks()) {
+ callback.onDeviceAttributesChanged();
}
}
@@ -270,6 +261,10 @@
|| device.isActiveDevice(BluetoothProfile.HEARING_AID);
}
+ private Collection<DeviceCallback> getCallbacks() {
+ return new CopyOnWriteArrayList<>(mCallbacks);
+ }
+
class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
@Override
public void onDeviceAdded(MediaDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
index 7898982..73551f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
@@ -22,6 +22,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* MediaManager provide interface to get MediaDevice list.
@@ -30,7 +31,7 @@
private static final String TAG = "MediaManager";
- protected final Collection<MediaDeviceCallback> mCallbacks = new ArrayList<>();
+ protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
protected final List<MediaDevice> mMediaDevices = new ArrayList<>();
protected Context mContext;
@@ -42,18 +43,14 @@
}
protected void registerCallback(MediaDeviceCallback callback) {
- synchronized (mCallbacks) {
- if (!mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
- }
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(callback);
}
}
protected void unregisterCallback(MediaDeviceCallback callback) {
- synchronized (mCallbacks) {
- if (mCallbacks.contains(callback)) {
- mCallbacks.remove(callback);
- }
+ if (mCallbacks.contains(callback)) {
+ mCallbacks.remove(callback);
}
}
@@ -78,53 +75,45 @@
}
protected void dispatchDeviceAdded(MediaDevice mediaDevice) {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceAdded(mediaDevice);
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceAdded(mediaDevice);
}
}
protected void dispatchDeviceRemoved(MediaDevice mediaDevice) {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceRemoved(mediaDevice);
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceRemoved(mediaDevice);
}
}
protected void dispatchDeviceListAdded() {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
}
}
protected void dispatchDeviceListRemoved(List<MediaDevice> devices) {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceListRemoved(devices);
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceListRemoved(devices);
}
}
protected void dispatchConnectedDeviceChanged(String id) {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onConnectedDeviceChanged(id);
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onConnectedDeviceChanged(id);
}
}
protected void dispatchDataChanged() {
- synchronized (mCallbacks) {
- for (MediaDeviceCallback callback : mCallbacks) {
- callback.onDeviceAttributesChanged();
- }
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceAttributesChanged();
}
}
+ private Collection<MediaDeviceCallback> getCallbacks() {
+ return new CopyOnWriteArrayList<>(mCallbacks);
+ }
+
/**
* Callback for notifying device is added, removed and attributes changed.
*/
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 772f6e4..cc2c92b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -253,6 +253,9 @@
<!-- Permission required for CTS test - ShortcutManagerUsageTest -->
<uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/>
+ <!-- Permission required for CTS test - UsageStatsTest -->
+ <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"/>
+
<!-- Permissions required to test ambient display. -->
<uses-permission android:name="android.permission.READ_DREAM_STATE"/>
<uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
index 95f205a..481c4db 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_activity.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
@@ -14,8 +14,45 @@
~ limitations under the License
-->
-<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/bubble_overflow_recycler"
- android:layout_gravity="center_horizontal"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/bubble_overflow_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_gravity="center_horizontal">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/bubble_overflow_recycler"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:id="@+id/bubble_overflow_empty_state"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <TextView
+ android:id="@+id/bubble_overflow_empty_title"
+ android:text="@string/bubble_overflow_empty_title"
+ android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+ android:textColor="?android:attr/textColorSecondary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"/>
+
+ <TextView
+ android:id="@+id/bubble_overflow_empty_subtitle"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/bubble_overflow_empty_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml
index a36dd12..62056e6 100644
--- a/packages/SystemUI/res/layout/controls_management_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_management_favorites.xml
@@ -22,14 +22,31 @@
android:orientation="vertical">
<TextView
+ android:id="@+id/error_message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/controls_management_list_margin"
+ android:text="@string/controls_favorite_load_error"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:visibility="gone"
+ android:gravity="center_horizontal"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/text_favorites"
+ />
+
+ <TextView
android:id="@+id/text_favorites"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="FAVORITES"
+ android:layout_marginTop="@dimen/controls_management_list_margin"
+ android:text="@string/controls_favorite_header_favorites"
android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAllCaps="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/divider1"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/error_message"
/>
<View
@@ -60,8 +77,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/controls_management_list_margin"
- android:text="ALL"
+ android:text="@string/controls_favorite_header_all"
android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAllCaps="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/divider2"
app:layout_constraintTop_toBottomOf="@id/listFavorites"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d596a5d..ef9e705 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1780,6 +1780,12 @@
<!-- [CHAR LIMIT=150] Notification Importance title: bubble level summary -->
<string name="notification_channel_summary_bubble">Keeps your attention with a floating shortcut to this content.</string>
+ <!-- [CHAR LIMIT=NONE] Empty overflow title -->
+ <string name="bubble_overflow_empty_title">No recent bubbles</string>
+
+ <!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
+ <string name="bubble_overflow_empty_subtitle">Recently dismissed bubbles will appear here for easy retrieval.</string>
+
<!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
<string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
@@ -2595,4 +2601,10 @@
<string name="controls_favorite_default_title">Controls</string>
<!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] -->
<string name="controls_favorite_subtitle">Choose controls for quick access</string>
+ <!-- Controls management controls screen favorites header [CHAR LIMIT=50] -->
+ <string name="controls_favorite_header_favorites">Favorites</string>
+ <!-- Controls management controls screen all header [CHAR LIMIT=50] -->
+ <string name="controls_favorite_header_all">All</string>
+ <!-- Controls management controls screen error on load message [CHAR LIMIT=50] -->
+ <string name="controls_favorite_load_error">The list of all controls could not be loaded.</string>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ConfigurationCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ConfigurationCompat.java
new file mode 100644
index 0000000..d1c77a6
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ConfigurationCompat.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+package com.android.systemui.shared.system;
+
+import android.content.res.Configuration;
+
+/**
+ * Wraps the Configuration to access the window configuration.
+ */
+public class ConfigurationCompat {
+
+ public static int getWindowConfigurationRotation(Configuration c) {
+ return c.windowConfiguration.getRotation();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
index 0f7f1be..023b74b 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
@@ -19,7 +19,6 @@
import android.annotation.StringRes;
import android.content.Context;
-import android.view.WindowManager;
import android.widget.Toast;
public class SysUIToast {
@@ -29,10 +28,7 @@
}
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
- Toast toast = Toast.makeText(context, text, duration);
- toast.getWindowParams().privateFlags |=
- WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- return toast;
+ return Toast.makeText(context, text, duration);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java
new file mode 100644
index 0000000..e76a209
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java
@@ -0,0 +1,197 @@
+/*
+ * 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.accessibility;
+
+import static android.view.WindowManager.LayoutParams;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+
+/**
+ * Contains a movable control UI to manipulate mirrored window's position, size and scale. The
+ * window type of the UI is {@link LayoutParams#TYPE_APPLICATION_SUB_PANEL} and the window type
+ * of the window token should be {@link LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY} to
+ * ensure it is above all windows and won't be mirrored. It is not movable to the navigation bar.
+ */
+public abstract class MirrorWindowControl {
+ private static final String TAG = "MirrorWindowControl";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG) | false;
+
+ /**
+ * A delegate handling a mirrored window's offset.
+ */
+ public interface MirrorWindowDelegate {
+ /**
+ * Moves the window with specified offset.
+ *
+ * @param xOffset the amount in pixels to offset the window in the X coordinate, in current
+ * display pixels.
+ * @param yOffset the amount in pixels to offset the window in the Y coordinate, in current
+ * display pixels.
+ */
+ void move(int xOffset, int yOffset);
+ }
+
+ protected final Context mContext;
+ private final Rect mDraggableBound = new Rect();
+ final Point mTmpPoint = new Point();
+
+ @Nullable
+ protected MirrorWindowDelegate mMirrorWindowDelegate;
+ protected View mControlsView;
+ /**
+ * The left top position of the control UI. Initialized when the control UI is visible.
+ *
+ * @see #setDefaultPosition(LayoutParams)
+ */
+ private final Point mControlPosition = new Point();
+ private final WindowManager mWindowManager;
+
+ MirrorWindowControl(Context context) {
+ mContext = context;
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ public void setWindowDelegate(@Nullable MirrorWindowDelegate windowDelegate) {
+ mMirrorWindowDelegate = windowDelegate;
+ }
+
+ /**
+ * Shows the control UI.
+ *
+ * @param binder the window token of the
+ * {@link LayoutParams#TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY} window.
+ */
+ public final void showControl(IBinder binder) {
+ if (mControlsView != null) {
+ Log.w(TAG, "control view is visible");
+ return;
+ }
+ final Point viewSize = mTmpPoint;
+ mControlsView = onCreateView(LayoutInflater.from(mContext), viewSize);
+
+ final LayoutParams lp = new LayoutParams();
+ final int defaultSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_controls_size);
+ lp.width = viewSize.x <= 0 ? defaultSize : viewSize.x;
+ lp.height = viewSize.y <= 0 ? defaultSize : viewSize.y;
+ lp.token = binder;
+ setDefaultParams(lp);
+ setDefaultPosition(lp);
+ mWindowManager.addView(mControlsView, lp);
+ updateDraggableBound(lp.width, lp.height);
+ }
+
+ private void setDefaultParams(LayoutParams lp) {
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ lp.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | LayoutParams.FLAG_NOT_FOCUSABLE;
+ lp.type = LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+ lp.format = PixelFormat.RGBA_8888;
+ lp.setTitle(getWindowTitle());
+ }
+
+ private void setDefaultPosition(LayoutParams layoutParams) {
+ final Point displaySize = mTmpPoint;
+ mContext.getDisplay().getSize(displaySize);
+ layoutParams.x = displaySize.x - layoutParams.width;
+ layoutParams.y = displaySize.y - layoutParams.height;
+ mControlPosition.set(layoutParams.x, layoutParams.y);
+ }
+
+ /**
+ * Removes the UI from the scene.
+ */
+ public final void destroyControl() {
+ if (mControlsView != null) {
+ mWindowManager.removeView(mControlsView);
+ mControlsView = null;
+ }
+ }
+
+ /**
+ * Moves the control view with specified offset.
+ *
+ * @param xOffset the amount in pixels to offset the UI in the X coordinate, in current
+ * display pixels.
+ * @param yOffset the amount in pixels to offset the UI in the Y coordinate, in current
+ * display pixels.
+ */
+ public void move(int xOffset, int yOffset) {
+ if (mControlsView == null) {
+ Log.w(TAG, "control view is not available yet or destroyed");
+ return;
+ }
+ final Point nextPosition = mTmpPoint;
+ nextPosition.set(mControlPosition.x, mControlPosition.y);
+ mTmpPoint.offset(xOffset, yOffset);
+ setPosition(mTmpPoint);
+ }
+
+ private void setPosition(Point point) {
+ constrainFrameToDraggableBound(point);
+ if (point.equals(mControlPosition)) {
+ return;
+ }
+ mControlPosition.set(point.x, point.y);
+ LayoutParams lp = (LayoutParams) mControlsView.getLayoutParams();
+ lp.x = mControlPosition.x;
+ lp.y = mControlPosition.y;
+ mWindowManager.updateViewLayout(mControlsView, lp);
+ }
+
+ private void constrainFrameToDraggableBound(Point point) {
+ point.x = MathUtils.constrain(point.x, mDraggableBound.left, mDraggableBound.right);
+ point.y = MathUtils.constrain(point.y, mDraggableBound.top, mDraggableBound.bottom);
+ }
+
+ private void updateDraggableBound(int viewWidth, int viewHeight) {
+ final Point size = mTmpPoint;
+ mContext.getDisplay().getSize(size);
+ mDraggableBound.set(0, 0, size.x - viewWidth, size.y - viewHeight);
+ if (DBG) {
+ Log.d(TAG, "updateDraggableBound :" + mDraggableBound);
+ }
+ }
+
+ abstract String getWindowTitle();
+
+ /**
+ * Called when the UI is going to show.
+ *
+ * @param inflater The LayoutInflater object used to inflate the view.
+ * @param viewSize The {@link Point} to specify view's width with {@link Point#x)} and height
+ * with {@link Point#y)} .The value should be greater than 0, otherwise will
+ * fall back to the default size.
+ * @return the View for the control's UI.
+ */
+ @NonNull
+ abstract View onCreateView(@NonNull LayoutInflater inflater, @NonNull Point viewSize);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
new file mode 100644
index 0000000..2ba2bb6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.os.Handler;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * A basic control to move the mirror window.
+ */
+class SimpleMirrorWindowControl extends MirrorWindowControl implements View.OnClickListener,
+ View.OnTouchListener, View.OnLongClickListener {
+
+ private static final String TAG = "SimpleMirrorWindowControl";
+ private static final int MOVE_FRAME_DURATION_MS = 100;
+ private final int mMoveFrameAmountShort;
+ private final int mMoveFrameAmountLong;
+
+ private boolean mIsDragState;
+ private boolean mShouldSetTouchStart;
+
+ @Nullable private MoveWindowTask mMoveWindowTask;
+ private PointF mLastDrag = new PointF();
+ private final Handler mHandler;
+
+ SimpleMirrorWindowControl(Context context, Handler handler) {
+ super(context);
+ mHandler = handler;
+ final Resources resource = context.getResources();
+ mMoveFrameAmountShort = resource.getDimensionPixelSize(
+ R.dimen.magnification_frame_move_short);
+ mMoveFrameAmountLong = resource.getDimensionPixelSize(
+ R.dimen.magnification_frame_move_long);
+ }
+
+ @Override
+ String getWindowTitle() {
+ return mContext.getString(R.string.magnification_controls_title);
+ }
+
+ @Override
+ View onCreateView(LayoutInflater layoutInflater, Point viewSize) {
+ final View view = layoutInflater.inflate(R.layout.magnifier_controllers, null);
+ final View leftControl = view.findViewById(R.id.left_control);
+ final View upControl = view.findViewById(R.id.up_control);
+ final View rightControl = view.findViewById(R.id.right_control);
+ final View bottomControl = view.findViewById(R.id.down_control);
+
+ leftControl.setOnClickListener(this);
+ upControl.setOnClickListener(this);
+ rightControl.setOnClickListener(this);
+ bottomControl.setOnClickListener(this);
+
+ leftControl.setOnLongClickListener(this);
+ upControl.setOnLongClickListener(this);
+ rightControl.setOnLongClickListener(this);
+ bottomControl.setOnLongClickListener(this);
+
+ leftControl.setOnTouchListener(this);
+ upControl.setOnTouchListener(this);
+ rightControl.setOnTouchListener(this);
+ bottomControl.setOnTouchListener(this);
+
+ view.setOnTouchListener(this);
+ view.setOnLongClickListener(this::onViewRootLongClick);
+ return view;
+ }
+
+ private Point findOffset(View v, int moveFrameAmount) {
+ final Point offset = mTmpPoint;
+ offset.set(0, 0);
+ if (v.getId() == R.id.left_control) {
+ mTmpPoint.x = -moveFrameAmount;
+ } else if (v.getId() == R.id.up_control) {
+ mTmpPoint.y = -moveFrameAmount;
+ } else if (v.getId() == R.id.right_control) {
+ mTmpPoint.x = moveFrameAmount;
+ } else if (v.getId() == R.id.down_control) {
+ mTmpPoint.y = moveFrameAmount;
+ } else {
+ Log.w(TAG, "findOffset move is zero ");
+ }
+ return mTmpPoint;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mMirrorWindowDelegate != null) {
+ Point offset = findOffset(v, mMoveFrameAmountShort);
+ mMirrorWindowDelegate.move(offset.x, offset.y);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (handleDragState(event)) {
+ return true;
+ }
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mMoveWindowTask != null) {
+ mMoveWindowTask.cancel();
+ mMoveWindowTask = null;
+ }
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ Point offset = findOffset(v, mMoveFrameAmountLong);
+ mMoveWindowTask = new MoveWindowTask(mMirrorWindowDelegate, mHandler, offset.x, offset.y,
+ MOVE_FRAME_DURATION_MS);
+ mMoveWindowTask.schedule();
+ return true;
+ }
+
+ private boolean onViewRootLongClick(View view) {
+ mIsDragState = true;
+ mShouldSetTouchStart = true;
+ return true;
+ }
+
+ private boolean handleDragState(final MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_MOVE:
+ if (mIsDragState) {
+ if (mShouldSetTouchStart) {
+ mLastDrag.set(event.getRawX(), event.getRawY());
+ mShouldSetTouchStart = false;
+ }
+ int xDiff = (int) (event.getRawX() - mLastDrag.x);
+ int yDiff = (int) (event.getRawY() - mLastDrag.y);
+ move(xDiff, yDiff);
+ mLastDrag.set(event.getRawX(), event.getRawY());
+ return true;
+ }
+ return false;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsDragState) {
+ mIsDragState = false;
+ return true;
+ }
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * A timer task to move the mirror window periodically.
+ */
+ static class MoveWindowTask implements Runnable {
+ private final MirrorWindowDelegate mMirrorWindowDelegate;
+ private final int mXOffset;
+ private final int mYOffset;
+ private final Handler mHandler;
+ /** Time in milliseconds between successive task executions.*/
+ private long mPeriod;
+ private boolean mCancel;
+
+ MoveWindowTask(@NonNull MirrorWindowDelegate windowDelegate, Handler handler, int xOffset,
+ int yOffset, long period) {
+ mMirrorWindowDelegate = windowDelegate;
+ mXOffset = xOffset;
+ mYOffset = yOffset;
+ mHandler = handler;
+ mPeriod = period;
+ }
+
+ @Override
+ public void run() {
+ if (mCancel) {
+ return;
+ }
+ mMirrorWindowDelegate.move(mXOffset, mYOffset);
+ schedule();
+ }
+
+ /**
+ * Schedules the specified task periodically and immediately.
+ */
+ void schedule() {
+ mHandler.postDelayed(this, mPeriod);
+ mCancel = false;
+ }
+
+ void cancel() {
+ mHandler.removeCallbacks(this);
+ mCancel = true;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 898cd13..b3ce4a0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -86,7 +86,7 @@
private void enableMagnification() {
if (mWindowMagnificationController == null) {
- mWindowMagnificationController = new WindowMagnificationController(mContext, mHandler);
+ mWindowMagnificationController = new WindowMagnificationController(mContext, null);
}
mWindowMagnificationController.createWindowMagnification();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 581cf7a..7176490 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -18,6 +18,7 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
@@ -25,7 +26,6 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.Handler;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -44,16 +44,13 @@
/**
* Class to handle adding and removing a window magnification.
*/
-public class WindowMagnificationController implements View.OnClickListener,
- View.OnLongClickListener, View.OnTouchListener, SurfaceHolder.Callback {
+public class WindowMagnificationController implements View.OnTouchListener, SurfaceHolder.Callback,
+ MirrorWindowControl.MirrorWindowDelegate {
private final int mBorderSize;
- private final int mMoveFrameAmountShort;
- private final int mMoveFrameAmountLong;
private final Context mContext;
private final Point mDisplaySize = new Point();
private final int mDisplayId;
- private final Handler mHandler;
private final Rect mMagnificationFrame = new Rect();
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -66,13 +63,6 @@
// The root of the mirrored content
private SurfaceControl mMirrorSurface;
- private boolean mIsPressedDown;
-
- private View mLeftControl;
- private View mUpControl;
- private View mRightControl;
- private View mBottomControl;
-
private View mDragView;
private View mLeftDrag;
private View mTopDrag;
@@ -80,20 +70,18 @@
private View mBottomDrag;
private final PointF mLastDrag = new PointF();
- private final Point mMoveWindowOffset = new Point();
private View mMirrorView;
private SurfaceView mMirrorSurfaceView;
- private View mControlsView;
private View mOverlayView;
// The boundary of magnification frame.
private final Rect mMagnificationFrameBoundary = new Rect();
- private MoveMirrorRunnable mMoveMirrorRunnable = new MoveMirrorRunnable();
+ @Nullable
+ private MirrorWindowControl mMirrorWindowControl;
- WindowMagnificationController(Context context, Handler handler) {
+ WindowMagnificationController(Context context, MirrorWindowControl mirrorWindowControl) {
mContext = context;
- mHandler = handler;
Display display = mContext.getDisplay();
display.getRealSize(mDisplaySize);
mDisplayId = mContext.getDisplayId();
@@ -102,10 +90,12 @@
Resources r = context.getResources();
mBorderSize = (int) r.getDimension(R.dimen.magnification_border_size);
- mMoveFrameAmountShort = (int) r.getDimension(R.dimen.magnification_frame_move_short);
- mMoveFrameAmountLong = (int) r.getDimension(R.dimen.magnification_frame_move_long);
mScale = r.getInteger(R.integer.magnification_default_scale);
+ mMirrorWindowControl = mirrorWindowControl;
+ if (mMirrorWindowControl != null) {
+ mMirrorWindowControl.setWindowDelegate(this);
+ }
}
/**
@@ -176,9 +166,8 @@
mMirrorView = null;
}
- if (mControlsView != null) {
- mWm.removeView(mControlsView);
- mControlsView = null;
+ if (mMirrorWindowControl != null) {
+ mMirrorWindowControl.destroyControl();
}
}
@@ -238,40 +227,9 @@
}
private void createControls() {
- int controlsSize = (int) mContext.getResources().getDimension(
- R.dimen.magnification_controls_size);
-
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(controlsSize, controlsSize,
- WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.RGBA_8888);
- lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
- lp.token = mOverlayView.getWindowToken();
- lp.setTitle(mContext.getString(R.string.magnification_controls_title));
-
- mControlsView = LayoutInflater.from(mContext).inflate(R.layout.magnifier_controllers, null);
- mWm.addView(mControlsView, lp);
-
- mLeftControl = mControlsView.findViewById(R.id.left_control);
- mUpControl = mControlsView.findViewById(R.id.up_control);
- mRightControl = mControlsView.findViewById(R.id.right_control);
- mBottomControl = mControlsView.findViewById(R.id.down_control);
-
- mLeftControl.setOnClickListener(this);
- mUpControl.setOnClickListener(this);
- mRightControl.setOnClickListener(this);
- mBottomControl.setOnClickListener(this);
-
- mLeftControl.setOnLongClickListener(this);
- mUpControl.setOnLongClickListener(this);
- mRightControl.setOnLongClickListener(this);
- mBottomControl.setOnLongClickListener(this);
-
- mLeftControl.setOnTouchListener(this);
- mUpControl.setOnTouchListener(this);
- mRightControl.setOnTouchListener(this);
- mBottomControl.setOnTouchListener(this);
+ if (mMirrorWindowControl != null) {
+ mMirrorWindowControl.showControl(mOverlayView.getWindowToken());
+ }
}
private void setInitialStartBounds() {
@@ -331,40 +289,14 @@
}
@Override
- public void onClick(View v) {
- setMoveOffset(v, mMoveFrameAmountShort);
- moveMirrorWindow(mMoveWindowOffset.x, mMoveWindowOffset.y);
- }
-
- @Override
- public boolean onLongClick(View v) {
- mIsPressedDown = true;
- setMoveOffset(v, mMoveFrameAmountLong);
- mHandler.post(mMoveMirrorRunnable);
- return true;
- }
-
- @Override
public boolean onTouch(View v, MotionEvent event) {
- if (v == mLeftControl || v == mUpControl || v == mRightControl || v == mBottomControl) {
- return handleControlTouchEvent(event);
- } else if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
+ if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
|| v == mBottomDrag) {
return handleDragTouchEvent(event);
}
return false;
}
- private boolean handleControlTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mIsPressedDown = false;
- break;
- }
- return false;
- }
-
private boolean handleDragTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
@@ -380,20 +312,6 @@
return false;
}
- private void setMoveOffset(View v, int moveFrameAmount) {
- mMoveWindowOffset.set(0, 0);
-
- if (v == mLeftControl) {
- mMoveWindowOffset.x = -moveFrameAmount;
- } else if (v == mUpControl) {
- mMoveWindowOffset.y = -moveFrameAmount;
- } else if (v == mRightControl) {
- mMoveWindowOffset.x = moveFrameAmount;
- } else if (v == mBottomControl) {
- mMoveWindowOffset.y = moveFrameAmount;
- }
- }
-
private void moveMirrorWindow(int xOffset, int yOffset) {
if (updateMagnificationFramePosition(xOffset, yOffset)) {
modifyWindowMagnification(mTransaction);
@@ -461,6 +379,7 @@
}
return false;
}
+
@Override
public void surfaceCreated(SurfaceHolder holder) {
createMirror();
@@ -474,13 +393,13 @@
public void surfaceDestroyed(SurfaceHolder holder) {
}
- class MoveMirrorRunnable implements Runnable {
- @Override
- public void run() {
- if (mIsPressedDown) {
- moveMirrorWindow(mMoveWindowOffset.x, mMoveWindowOffset.y);
- mHandler.postDelayed(mMoveMirrorRunnable, 100);
- }
+ @Override
+ public void move(int xOffset, int yOffset) {
+ if (mMirrorSurfaceView == null) {
+ return;
}
+ mMagnificationFrame.offset(xOffset, yOffset);
+ modifyWindowMagnification(mTransaction);
+ mTransaction.apply();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 45705b7..1e39954 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -226,6 +226,10 @@
mIconView.update(this);
}
+ void setInflated(boolean inflated) {
+ mInflated = inflated;
+ }
+
/**
* Set visibility of bubble in the expanded state.
*
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 05838ab..762e5f2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -749,7 +749,8 @@
}
void promoteBubbleFromOverflow(Bubble bubble) {
- mBubbleData.promoteBubbleFromOverflow(bubble);
+ bubble.setInflateSynchronously(mInflateSynchronously);
+ mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 673121f..8a5aad8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -199,16 +199,21 @@
dispatchPendingChanges();
}
- public void promoteBubbleFromOverflow(Bubble bubble) {
+ public void promoteBubbleFromOverflow(Bubble bubble, BubbleStackView stack,
+ BubbleIconFactory factory) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "promoteBubbleFromOverflow: " + bubble);
}
- mOverflowBubbles.remove(bubble);
- doAdd(bubble);
- setSelectedBubbleInternal(bubble);
+
// Preserve new order for next repack, which sorts by last updated time.
bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
- trim();
+ setSelectedBubbleInternal(bubble);
+ mOverflowBubbles.remove(bubble);
+
+ bubble.inflate(
+ b -> notificationEntryUpdated(bubble, /* suppressFlyout */
+ false, /* showInShade */ true),
+ mContext, stack, factory);
dispatchPendingChanges();
}
@@ -445,6 +450,10 @@
mOverflowBubbles.add(0, bubbleToRemove);
if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
// Remove oldest bubble.
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "Overflow full. Remove bubble: " + mOverflowBubbles.get(
+ mOverflowBubbles.size() - 1));
+ }
mOverflowBubbles.remove(mOverflowBubbles.size() - 1);
}
}
@@ -511,7 +520,7 @@
if (Objects.equals(bubble, mSelectedBubble)) {
return;
}
- if (bubble != null && !mBubbles.contains(bubble)) {
+ if (bubble != null && !mBubbles.contains(bubble) && !mOverflowBubbles.contains(bubble)) {
Log.e(TAG, "Cannot select bubble which doesn't exist!"
+ " (" + bubble + ") bubbles=" + mBubbles);
return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 0d5261d..fe191f4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -294,7 +294,8 @@
ta.recycle();
mPointerDrawable.setTint(bgColor);
- if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(mContext.getResources())) {
+ if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+ mContext.getResources())) {
mActivityView.setCornerRadius(cornerRadius);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 2d55a1d..f3cfa83 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -26,7 +26,10 @@
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -46,6 +49,7 @@
public class BubbleOverflowActivity extends Activity {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
+ private LinearLayout mEmptyState;
private BubbleController mBubbleController;
private BubbleOverflowAdapter mAdapter;
private RecyclerView mRecyclerView;
@@ -64,6 +68,7 @@
setBackgroundColor();
mMaxBubbles = getResources().getInteger(R.integer.bubbles_max_rendered);
+ mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
mRecyclerView.setLayoutManager(
new GridLayoutManager(getApplicationContext(),
@@ -73,9 +78,9 @@
mBubbleController::promoteBubbleFromOverflow);
mRecyclerView.setAdapter(mAdapter);
- updateData(mBubbleController.getOverflowBubbles());
+ onDataChanged(mBubbleController.getOverflowBubbles());
mBubbleController.setOverflowCallback(() -> {
- updateData(mBubbleController.getOverflowBubbles());
+ onDataChanged(mBubbleController.getOverflowBubbles());
});
}
@@ -87,7 +92,7 @@
findViewById(android.R.id.content).setBackgroundColor(bgColor);
}
- void updateData(List<Bubble> bubbles) {
+ void onDataChanged(List<Bubble> bubbles) {
mOverflowBubbles.clear();
if (bubbles.size() > mMaxBubbles) {
mOverflowBubbles.addAll(bubbles.subList(mMaxBubbles, bubbles.size()));
@@ -96,6 +101,12 @@
}
mAdapter.notifyDataSetChanged();
+ if (mOverflowBubbles.isEmpty()) {
+ mEmptyState.setVisibility(View.VISIBLE);
+ } else {
+ mEmptyState.setVisibility(View.GONE);
+ }
+
if (DEBUG_OVERFLOW) {
Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
mOverflowBubbles, /*selected*/ null));
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index bce172b..cff78cf 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -528,6 +528,12 @@
mBubbleContainer.addView(mOverflowBtn, 0,
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ setOverflowBtnTheme();
+ mOverflowBtn.setVisibility(GONE);
+ }
+
+ // TODO(b/149146374) Propagate theme change to bubbles in overflow.
+ private void setOverflowBtnTheme() {
TypedArray ta = mContext.obtainStyledAttributes(
new int[]{android.R.attr.colorBackgroundFloating});
int bgColor = ta.getColor(0, Color.WHITE /* default */);
@@ -537,8 +543,6 @@
ColorDrawable bg = new ColorDrawable(bgColor);
AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg);
mOverflowBtn.setImageDrawable(adaptiveIcon);
-
- mOverflowBtn.setVisibility(GONE);
}
void showExpandedViewContents(int displayId) {
@@ -568,6 +572,9 @@
*/
public void onThemeChanged() {
setUpFlyout();
+ if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
+ setOverflowBtnTheme();
+ }
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -795,6 +802,7 @@
if (removedIndex >= 0) {
mBubbleContainer.removeViewAt(removedIndex);
bubble.cleanupExpandedState();
+ bubble.setInflated(false);
logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
} else {
Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt
index 4f39f22..d2776d2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt
@@ -18,8 +18,11 @@
import android.os.UserHandle
+/**
+ * An interface for controllers that keep track of the current user and can be notified of user
+ * changes.
+ */
interface UserAwareController {
-
fun changeUser(newUser: UserHandle) {}
val currentUserId: Int
}
\ 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
index b6cca3f..f624120 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
@@ -22,6 +22,15 @@
/**
* Stores basic information about a [Control] to persist and keep track of favorites.
+ *
+ * The identifier of this [Control] is the combination of [component] and [controlId]. The other
+ * two fields are there for persistence. In this way, basic information can be shown to the user
+ * before the service has to report on the status.
+ *
+ * @property component the name of the component that provides the [Control].
+ * @property controlId unique (for the given [component]) identifier for this [Control].
+ * @property controlTitle last title reported for this [Control].
+ * @property deviceType last reported type for this [Control].
*/
data class ControlInfo(
val component: ComponentName,
@@ -33,6 +42,14 @@
companion object {
private const val TAG = "ControlInfo"
private const val SEPARATOR = ":"
+
+ /**
+ * Creates a [ControlInfo] from a [SEPARATOR] separated list of fields.
+ *
+ * @param separator fields of a [ControlInfo] separated by [SEPARATOR]
+ * @return a [ControlInfo] or `null` if there was an error.
+ * @see [ControlInfo.toString]
+ */
fun createFromString(string: String): ControlInfo? {
val parts = string.split(SEPARATOR)
val component = ComponentName.unflattenFromString(parts[0])
@@ -53,6 +70,12 @@
if (DeviceTypes.validDeviceType(type)) type else DeviceTypes.TYPE_UNKNOWN)
}
}
+
+ /**
+ * Returns a [String] representation of the fields separated using [SEPARATOR].
+ *
+ * @return a [String] representation of `this`
+ */
override fun toString(): String {
return component.flattenToString() +
"$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
index 12c3ce9..7fae6a3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
@@ -18,13 +18,72 @@
import android.content.ComponentName
import android.service.controls.Control
+import android.service.controls.ControlsProviderService
import android.service.controls.actions.ControlAction
import com.android.systemui.controls.UserAwareController
+import java.util.function.Consumer
+/**
+ * Controller for keeping track of any [ControlsProviderService] that needs to be bound.
+ *
+ * This controller serves as an interface between [ControlsController] and the services.
+ *
+ * This controller being a [UserAwareController] means that all binding and requests will be
+ * performed on services bound as the current user.
+ */
interface ControlsBindingController : UserAwareController {
- fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit)
+
+ /**
+ * Request bind to a service and load all controls.
+ *
+ * @param component The [ComponentName] of the service to bind
+ * @param callback a callback to return the loaded controls to (or an error).
+ */
+ fun bindAndLoad(component: ComponentName, callback: LoadCallback)
+
+ /**
+ * Request to bind to the given services.
+ *
+ * @param components a list of [ComponentName] of the services to bind
+ */
fun bindServices(components: List<ComponentName>)
+
+ /**
+ * Send a subscribe message to retrieve status of a set of controls.
+ *
+ * The controls passed do not have to belong to a single [ControlsProviderService]. The
+ * corresponding service [ComponentName] is associated with each control.
+ *
+ * @param controls a list of controls with corresponding [ComponentName] to request status
+ * update
+ */
fun subscribe(controls: List<ControlInfo>)
+
+ /**
+ * Send an action performed on a [Control].
+ *
+ * @param controlInfo information about the actioned control, including the [ComponentName]
+ * @param action the action performed on the control
+ */
fun action(controlInfo: ControlInfo, action: ControlAction)
+
+ /**
+ * Unsubscribe from all services to stop status updates.
+ */
fun unsubscribe()
+
+ /**
+ * Consumer for load calls.
+ *
+ * Supports also sending error messages.
+ */
+ interface LoadCallback : Consumer<List<Control>> {
+
+ /**
+ * Indicates an error loading.
+ *
+ * @message an error message.
+ */
+ fun error(message: String)
+ }
}
\ 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
index 0a2a925..87bdfa8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -125,7 +125,10 @@
}
}
- override fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) {
+ override fun bindAndLoad(
+ component: ComponentName,
+ callback: ControlsBindingController.LoadCallback
+ ) {
val provider = retrieveLifecycleManager(component)
provider.maybeBindAndLoad(callback)
}
@@ -230,7 +233,7 @@
return
}
}
- provider.lastLoadCallback?.invoke(list) ?: run {
+ provider.lastLoadCallback?.accept(list) ?: run {
Log.w(TAG, "Null callback")
}
provider.unbindService()
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index fce5041..4b89fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -18,33 +18,194 @@
import android.content.ComponentName
import android.service.controls.Control
+import android.service.controls.ControlsProviderService
import android.service.controls.actions.ControlAction
import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.UserAwareController
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import java.util.function.Consumer
+/**
+ * Controller to handle communication between different parts of the controls system.
+ *
+ * This controller is in charge of:
+ * * Keeping track of favorites
+ * * Determining and keeping track of whether controls are enabled
+ * * Listening for user change and propagating that message in the system
+ * * Communicate between the UI and the [ControlsBindingController]
+ *
+ * This controller being a [UserAwareController] means that all operations will be conducted on
+ * information for the current user only.
+ */
interface ControlsController : UserAwareController {
+
+ /**
+ * Whether the controls system is available for the current user.
+ */
val available: Boolean
- fun getFavoriteControls(): List<ControlInfo>
+ // SERVICE COMMUNICATION
+
+ /**
+ * Load all available [Control] for a given service.
+ *
+ * @param componentName the [ComponentName] of the [ControlsProviderService] to load from
+ * @param dataCallback a callback in which to retrieve the result.
+ */
fun loadForComponent(
componentName: ComponentName,
- callback: (List<ControlStatus>, List<String>) -> Unit
+ dataCallback: Consumer<LoadData>
)
+ /**
+ * Request to subscribe for all favorite controls.
+ *
+ * @see [ControlsBindingController.subscribe]
+ */
fun subscribeToFavorites()
- fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
- fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>)
- fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo>
- fun countFavoritesForComponent(componentName: ComponentName): Int
-
+ /**
+ * Request to unsubscribe to all providers.
+ *
+ * @see [ControlsBindingController.unsubscribe]
+ */
fun unsubscribe()
+
+ /**
+ * Notify a [ControlsProviderService] that an action has been performed on a [Control].
+ *
+ * @param controlInfo information of the [Control] receiving the action
+ * @param action action performed on the [Control]
+ * @see [ControlsBindingController.action]
+ */
fun action(controlInfo: ControlInfo, action: ControlAction)
+
+ /**
+ * Refresh the status of a [Control] with information provided from the service.
+ *
+ * @param componentName the name of the service that provides the [Control]
+ * @param control a stateful [Control] with updated information
+ * @see [ControlsUiController.onRefreshState]
+ */
fun refreshStatus(componentName: ComponentName, control: Control)
+
+ /**
+ * Indicate the result of a [ControlAction] performed on a [Control].
+ *
+ * @param componentName the name of the service that provides the [Control]
+ * @param controlId the id of the [Control] the actioned was performed on
+ * @param response the result of the action.
+ * @see [ControlsUiController.onActionResponse]
+ */
fun onActionResponse(
componentName: ComponentName,
controlId: String,
@ControlAction.ResponseResult response: Int
)
+
+ // FAVORITE MANAGEMENT
+
+ /**
+ * Get a list of all favorite controls.
+ *
+ * @return a list of [ControlInfo] with persistent information about the controls, including
+ * their corresponding [ComponentName].
+ */
+ fun getFavoriteControls(): List<ControlInfo>
+
+ /**
+ * Get all the favorites for a given component.
+ *
+ * @param componentName the name of the component of the [ControlsProviderService] with
+ * which to filter the favorites.
+ * @return a list of the favorite controls for the given service. All the elements of the list
+ * will have the same [ControlInfo.component] matching the one requested.
+ */
+ fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo>
+
+ /**
+ * Replaces the favorites for the given component.
+ *
+ * Calling this method will eliminate the previous selection of favorites and replace it with a
+ * new one.
+ *
+ * @param componentName The name of the component for the [ControlsProviderService]
+ * @param favorites a list of [ControlInfo] to replace the previous favorites.
+ */
+ fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>)
+
+ /**
+ * Change the favorite status of a single [Control].
+ *
+ * If the control is added to favorites, it will be added to the end of the list for that
+ * particular component. Matching for removing the control will be done based on
+ * [ControlInfo.component] and [ControlInfo.controlId].
+ *
+ * Trying to add an already favorite control or trying to remove one that is not a favorite is
+ * a no-op.
+ *
+ * @param controlInfo persistent information about the [Control].
+ * @param state `true` to add to favorites and `false` to remove.
+ */
+ fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
+
+ /**
+ * Return the number of favorites for a given component.
+ *
+ * This call returns the same as `getFavoritesForComponent(componentName).size`.
+ *
+ * @param componentName the name of the component
+ * @return the number of current favorites for the given component
+ */
+ fun countFavoritesForComponent(componentName: ComponentName): Int
+
+ /**
+ * Clears the list of all favorites.
+ *
+ * To clear the list of favorites for a given service, call [replaceFavoritesForComponent] with
+ * an empty list.
+ */
fun clearFavorites()
+
+ /**
+ * Interface for structure to pass data to [ControlsFavoritingActivity].
+ */
+ interface LoadData {
+ /**
+ * All of the available controls for the loaded [ControlsProviderService].
+ *
+ * This will indicate if they are currently a favorite and whether they were removed (a
+ * favorite but not retrieved on load).
+ */
+ val allControls: List<ControlStatus>
+
+ /**
+ * Ordered list of ids of favorite controls.
+ */
+ val favoritesIds: List<String>
+
+ /**
+ * Whether there was an error in loading.
+ *
+ * In this case, [allControls] will only contain those that were favorited and will not be
+ * marked as removed.
+ */
+ val errorOnLoad: Boolean
+ }
+}
+
+/**
+ * Creates a basic implementation of a [LoadData].
+ */
+fun createLoadDataObject(
+ allControls: List<ControlStatus>,
+ favorites: List<String>,
+ error: Boolean = false
+): ControlsController.LoadData {
+ return object : ControlsController.LoadData {
+ override val allControls = allControls
+ override val favoritesIds = favorites
+ override val errorOnLoad = error
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index e611197..50ad515 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -38,7 +38,6 @@
import com.android.systemui.Dumpable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlStatus
-import com.android.systemui.controls.management.ControlsFavoritingActivity
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Background
@@ -47,6 +46,7 @@
import java.io.PrintWriter
import java.util.Optional
import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
import javax.inject.Inject
import javax.inject.Singleton
@@ -188,47 +188,74 @@
override fun loadForComponent(
componentName: ComponentName,
- callback: (List<ControlStatus>, List<String>) -> Unit
+ dataCallback: Consumer<ControlsController.LoadData>
) {
if (!confirmAvailability()) {
if (userChanging) {
// Try again later, userChanging should not last forever. If so, we have bigger
// problems
executor.executeDelayed(
- { loadForComponent(componentName, callback) },
+ { loadForComponent(componentName, dataCallback) },
USER_CHANGE_RETRY_DELAY,
TimeUnit.MILLISECONDS
)
} else {
- callback(emptyList(), emptyList())
+ dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true))
}
return
}
- bindingController.bindAndLoad(componentName) {
- synchronized(currentFavorites) {
- val favoritesForComponentKeys: List<String> =
- currentFavorites.getValue(componentName).map { it.controlId }
- val changed = updateFavoritesLocked(componentName, it, favoritesForComponentKeys)
- if (changed) {
- persistenceWrapper.storeFavorites(favoritesAsListLocked())
+ bindingController.bindAndLoad(
+ componentName,
+ object : ControlsBindingController.LoadCallback {
+ override fun accept(controls: List<Control>) {
+ val loadData = synchronized(currentFavorites) {
+ val favoritesForComponentKeys: List<String> =
+ currentFavorites.getValue(componentName).map { it.controlId }
+ val changed = updateFavoritesLocked(componentName, controls,
+ favoritesForComponentKeys)
+ if (changed) {
+ persistenceWrapper.storeFavorites(favoritesAsListLocked())
+ }
+ val removed = findRemovedLocked(favoritesForComponentKeys.toSet(),
+ controls)
+ val controlsWithFavorite = controls.map {
+ ControlStatus(it, it.controlId in favoritesForComponentKeys)
+ }
+ createLoadDataObject(
+ currentFavorites.getValue(componentName)
+ .filter { it.controlId in removed }
+ .map { createRemovedStatus(it) } +
+ controlsWithFavorite,
+ favoritesForComponentKeys
+ )
+ }
+ dataCallback.accept(loadData)
+ }
+
+ override fun error(message: String) {
+ val loadData = synchronized(currentFavorites) {
+ val favoritesForComponent = currentFavorites.getValue(componentName)
+ val favoritesForComponentKeys = favoritesForComponent
+ .map { it.controlId }
+ createLoadDataObject(
+ favoritesForComponent.map { createRemovedStatus(it, false) },
+ favoritesForComponentKeys,
+ true
+ )
+ }
+ dataCallback.accept(loadData)
+ }
}
- val removed = findRemovedLocked(favoritesForComponentKeys.toSet(), it)
- val controlsWithFavorite =
- it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) }
- callback(
- currentFavorites.getValue(componentName)
- .filter { it.controlId in removed }
- .map(::createRemovedStatus) + controlsWithFavorite,
- favoritesForComponentKeys
- )
- }
- }
+ )
}
- private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus {
- val intent = Intent(context, ControlsFavoritingActivity::class.java).apply {
- putExtra(Intent.EXTRA_COMPONENT_NAME, controlInfo.component)
- flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ private fun createRemovedStatus(
+ controlInfo: ControlInfo,
+ setRemoved: Boolean = true
+ ): ControlStatus {
+ val intent = Intent(Intent.ACTION_MAIN).apply {
+ addCategory(Intent.CATEGORY_LAUNCHER)
+ this.`package` = controlInfo.component.packageName
}
val pendingIntent = PendingIntent.getActivity(context,
controlInfo.component.hashCode(),
@@ -238,7 +265,7 @@
.setTitle(controlInfo.controlTitle)
.setDeviceType(controlInfo.deviceType)
.build()
- return ControlStatus(control, true, true)
+ return ControlStatus(control, true, setRemoved)
}
@GuardedBy("currentFavorites")
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
index 7d1df14..d7f3c73 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
@@ -19,9 +19,7 @@
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
@@ -29,10 +27,18 @@
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
+import java.util.concurrent.Executor
+/**
+ * Manages persistence of favorite controls.
+ *
+ * This class uses an [AtomicFile] to serialize the favorite controls to an xml.
+ * @property file a file location for storing/reading the favorites.
+ * @property executor an executor in which to execute storing the favorites.
+ */
class ControlsFavoritePersistenceWrapper(
private var file: File,
- private var executor: DelayableExecutor
+ private val executor: Executor
) {
companion object {
@@ -46,10 +52,20 @@
private const val TAG_TYPE = "type"
}
+ /**
+ * Change the file location for storing/reading the favorites
+ *
+ * @param fileName new location
+ */
fun changeFile(fileName: File) {
file = fileName
}
+ /**
+ * Stores the list of favorites in the corresponding file.
+ *
+ * @param list a list of favorite controls. The list will be stored in the same order.
+ */
fun storeFavorites(list: List<ControlInfo>) {
executor.execute {
Log.d(TAG, "Saving data to file: $file")
@@ -87,6 +103,12 @@
}
}
+ /**
+ * Stores the list of favorites in the corresponding file.
+ *
+ * @return a list of stored favorite controls. Return an empty list if the file is not found
+ * @throws [IllegalStateException] if there is an error while reading the file
+ */
fun readFavorites(): List<ControlInfo> {
if (!file.exists()) {
Log.d(TAG, "No favorites, returning empty list")
@@ -95,7 +117,7 @@
val reader = try {
FileInputStream(file)
} catch (fnfe: FileNotFoundException) {
- Slog.i(TAG, "No file found")
+ Log.i(TAG, "No file found")
return emptyList()
}
try {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index b4bd82c..e09d20b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -25,7 +25,7 @@
import android.os.IBinder
import android.os.RemoteException
import android.os.UserHandle
-import android.service.controls.Control
+import android.service.controls.ControlsProviderService
import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
import android.service.controls.IControlsActionCallback
@@ -40,7 +40,23 @@
import com.android.systemui.util.concurrency.DelayableExecutor
import java.util.concurrent.TimeUnit
-typealias LoadCallback = (List<Control>) -> Unit
+/**
+ * Manager for the lifecycle of the connection to a given [ControlsProviderService].
+ *
+ * This class handles binding and unbinding and requests to the service. The class will queue
+ * requests until the service is connected and dispatch them then.
+ *
+ * @property context A SystemUI context for binding to the services
+ * @property executor A delayable executor for posting timeouts
+ * @property loadCallbackService a callback interface to hand the remote service for loading
+ * controls
+ * @property actionCallbackService a callback interface to hand the remote service for sending
+ * action responses
+ * @property subscriberService an "subscriber" interface for requesting and accepting updates for
+ * controls from the service.
+ * @property user the user for whose this service should be bound.
+ * @property componentName the name of the component for the service.
+ */
class ControlsProviderLifecycleManager(
private val context: Context,
private val executor: DelayableExecutor,
@@ -51,7 +67,7 @@
val componentName: ComponentName
) : IBinder.DeathRecipient {
- var lastLoadCallback: LoadCallback? = null
+ var lastLoadCallback: ControlsBindingController.LoadCallback? = null
private set
val token: IBinder = Binder()
@GuardedBy("subscriptions")
@@ -187,7 +203,7 @@
}
}
- private fun invokeOrQueue(f: () -> Unit, msg: Message) {
+ private inline fun invokeOrQueue(f: () -> Unit, msg: Message) {
wrapper?.run {
f()
} ?: run {
@@ -196,18 +212,37 @@
}
}
- fun maybeBindAndLoad(callback: LoadCallback) {
+ /**
+ * Request a call to [ControlsProviderService.loadAvailableControls].
+ *
+ * If the service is not bound, the call will be queued and the service will be bound first.
+ * The service will be bound after the controls are returned or the call times out.
+ *
+ * @param callback a callback in which to return the result back. If the call times out
+ * [ControlsBindingController.LoadCallback.error] will be called instead.
+ */
+ fun maybeBindAndLoad(callback: ControlsBindingController.LoadCallback) {
unqueueMessage(Message.Unbind)
lastLoadCallback = callback
onLoadCanceller = executor.executeDelayed({
- // Didn't receive a response in time, log and send back empty list
+ // Didn't receive a response in time, log and send back error
Log.d(TAG, "Timeout waiting onLoad for $componentName")
- loadCallbackService.accept(token, emptyList())
+ callback.error("Timeout waiting onLoad")
+ // Don't accept load callbacks after this
+ lastLoadCallback = null
+ unbindService()
}, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
invokeOrQueue(::load, Message.Load)
}
+ /**
+ * Request a subscription to the [Publisher] returned by [ControlsProviderService.publisherFor]
+ *
+ * If the service is not bound, the call will be queued and the service will be bound first.
+ *
+ * @param controlIds a list of the ids of controls to send status back.
+ */
fun maybeBindAndSubscribe(controlIds: List<String>) {
invokeOrQueue({ subscribe(controlIds) }, Message.Subscribe(controlIds))
}
@@ -222,6 +257,14 @@
}
}
+ /**
+ * Request a call to [ControlsProviderService.performControlAction].
+ *
+ * If the service is not bound, the call will be queued and the service will be bound first.
+ *
+ * @param controlId the id of the [Control] the action is performed on
+ * @param action the action performed
+ */
fun maybeBindAndSendAction(controlId: String, action: ControlAction) {
invokeOrQueue({ action(controlId, action) }, Message.Action(controlId, action))
}
@@ -236,6 +279,12 @@
}
}
+ /**
+ * Starts the subscription to the [ControlsProviderService] and requests status of controls.
+ *
+ * @param subscription the subscriber to use to request controls
+ * @see maybeBindAndLoad
+ */
fun startSubscription(subscription: IControlsSubscription) {
synchronized(subscriptions) {
subscriptions.add(subscription)
@@ -243,6 +292,9 @@
wrapper?.request(subscription, MAX_CONTROLS_REQUEST)
}
+ /**
+ * Unsubscribe from this service, cancelling all status requests.
+ */
fun unsubscribe() {
if (DEBUG) {
Log.d(TAG, "unsubscribe $componentName")
@@ -260,11 +312,17 @@
}
}
+ /**
+ * Request bind to the service.
+ */
fun bindService() {
unqueueMessage(Message.Unbind)
bindService(true)
}
+ /**
+ * Request unbind from the service.
+ */
fun unbindService() {
lastLoadCallback = null
onLoadCanceller?.run()
@@ -281,6 +339,9 @@
}.toString()
}
+ /**
+ * Messages for the internal queue.
+ */
sealed class Message {
abstract val type: Int
object Load : Message() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
index 5c812b1..b90f892 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
@@ -25,12 +25,17 @@
import android.service.controls.actions.ControlActionWrapper
import android.util.Log
+/**
+ * Wrapper for the service calls.
+ *
+ * Calling all [IControlsProvider] methods through here will wrap them in a try/catch block.
+ */
class ServiceWrapper(val service: IControlsProvider) {
companion object {
private const val TAG = "ServiceWrapper"
}
- private fun callThroughService(block: () -> Unit): Boolean {
+ private inline fun callThroughService(block: () -> Unit): Boolean {
try {
block()
return true
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 1e52371..9952b97 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -21,6 +21,7 @@
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
@@ -34,6 +35,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.CurrentUserTracker
import java.util.concurrent.Executor
+import java.util.function.Consumer
import javax.inject.Inject
class ControlsFavoritingActivity @Inject constructor(
@@ -51,6 +53,7 @@
private lateinit var adapterAll: ControlAdapter
private lateinit var recyclerViewFavorites: RecyclerView
private lateinit var adapterFavorites: ControlAdapter
+ private lateinit var errorText: TextView
private var component: ComponentName? = null
private var currentModel: FavoriteModel? = null
@@ -96,6 +99,7 @@
val app = intent.getCharSequenceExtra(EXTRA_APP)
component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
+ errorText = requireViewById(R.id.error_message)
setUpRecyclerViews()
@@ -118,7 +122,10 @@
}
component?.let {
- controller.loadForComponent(it) { allControls, favoriteKeys ->
+ controller.loadForComponent(it, Consumer { data ->
+ val allControls = data.allControls
+ val favoriteKeys = data.favoritesIds
+ val error = data.errorOnLoad
executor.execute {
val favoriteModel = FavoriteModel(
allControls,
@@ -128,8 +135,9 @@
adapterAll.changeFavoritesModel(favoriteModel)
adapterFavorites.changeFavoritesModel(favoriteModel)
currentModel = favoriteModel
+ errorText.visibility = if (error) View.VISIBLE else View.GONE
}
- }
+ })
}
currentUserTracker.startTracking()
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
index 34db684..d893caa 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
@@ -21,11 +21,26 @@
import com.android.systemui.controls.UserAwareController
import com.android.systemui.statusbar.policy.CallbackController
+/**
+ * Controller for keeping track of services that can be bound given a particular [ServiceListing].
+ */
interface ControlsListingController :
CallbackController<ControlsListingController.ControlsListingCallback>,
UserAwareController {
+ /**
+ * @return the current list of services that satisfies the [ServiceListing].
+ */
fun getCurrentServices(): List<CandidateInfo>
+
+ /**
+ * Get the app label for a given component.
+ *
+ * This call may do Binder calls (to [PackageManager])
+ *
+ * @param name the component name to retrieve the label
+ * @return the label for the component
+ */
fun getAppLabel(name: ComponentName): CharSequence? = ""
@FunctionalInterface
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 ec3285f..91d2de7 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
@@ -44,12 +44,16 @@
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.Notification;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import android.util.Pair;
import androidx.annotation.NonNull;
@@ -191,59 +195,121 @@
}
/**
- * Dismiss a notification on behalf of the user.
+ * Dismisses multiple notifications on behalf of the user.
*/
- public void dismissNotification(NotificationEntry entry, @NonNull DismissedByUserStats stats) {
+ public void dismissNotifications(
+ List<Pair<NotificationEntry, DismissedByUserStats>> entriesToDismiss) {
Assert.isMainThread();
- requireNonNull(stats);
checkForReentrantCall();
- if (entry != mNotificationSet.get(entry.getKey())) {
- throw new IllegalStateException("Invalid entry: " + entry.getKey());
- }
+ final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
+ for (int i = 0; i < entriesToDismiss.size(); i++) {
+ NotificationEntry entry = entriesToDismiss.get(i).first;
+ DismissedByUserStats stats = entriesToDismiss.get(i).second;
- if (entry.getDismissState() == DISMISSED) {
- return;
- }
-
- updateDismissInterceptors(entry);
- if (isDismissIntercepted(entry)) {
- mLogger.logNotifDismissedIntercepted(entry.getKey());
- return;
- }
-
- // Optimistically mark the notification as dismissed -- we'll wait for the signal from
- // system server before removing it from our notification set.
- entry.setDismissState(DISMISSED);
- mLogger.logNotifDismissed(entry.getKey());
-
- List<NotificationEntry> canceledEntries = new ArrayList<>();
-
- if (isCanceled(entry)) {
- canceledEntries.add(entry);
- } else {
- // Ask system server to remove it for us
- try {
- mStatusBarService.onNotificationClear(
- entry.getSbn().getPackageName(),
- entry.getSbn().getTag(),
- entry.getSbn().getId(),
- entry.getSbn().getUser().getIdentifier(),
- entry.getSbn().getKey(),
- stats.dismissalSurface,
- stats.dismissalSentiment,
- stats.notificationVisibility);
- } catch (RemoteException e) {
- // system process is dead if we're here.
+ requireNonNull(stats);
+ if (entry != mNotificationSet.get(entry.getKey())) {
+ throw new IllegalStateException("Invalid entry: " + entry.getKey());
}
- // Also mark any children as dismissed as system server will auto-dismiss them as well
- if (entry.getSbn().getNotification().isGroupSummary()) {
- for (NotificationEntry otherEntry : mNotificationSet.values()) {
- if (shouldAutoDismiss(otherEntry, entry.getSbn().getGroupKey())) {
- otherEntry.setDismissState(PARENT_DISMISSED);
- if (isCanceled(otherEntry)) {
- canceledEntries.add(otherEntry);
+ if (entry.getDismissState() == DISMISSED) {
+ continue;
+ }
+
+ updateDismissInterceptors(entry);
+ if (isDismissIntercepted(entry)) {
+ mLogger.logNotifDismissedIntercepted(entry.getKey());
+ continue;
+ }
+
+ entriesToLocallyDismiss.add(entry);
+ if (!isCanceled(entry)) {
+ // send message to system server if this notification hasn't already been cancelled
+ try {
+ mStatusBarService.onNotificationClear(
+ entry.getSbn().getPackageName(),
+ entry.getSbn().getTag(),
+ entry.getSbn().getId(),
+ entry.getSbn().getUser().getIdentifier(),
+ entry.getSbn().getKey(),
+ stats.dismissalSurface,
+ stats.dismissalSentiment,
+ stats.notificationVisibility);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
+ }
+ }
+
+ locallyDismissNotifications(entriesToLocallyDismiss);
+ rebuildList();
+ }
+
+ /**
+ * Dismisses a single notification on behalf of the user.
+ */
+ public void dismissNotification(
+ NotificationEntry entry,
+ @NonNull DismissedByUserStats stats) {
+ dismissNotifications(List.of(
+ new Pair<NotificationEntry, DismissedByUserStats>(entry, stats)));
+ }
+
+ /**
+ * Dismisses all clearable notifications for a given userid on behalf of the user.
+ */
+ public void dismissAllNotifications(@UserIdInt int userId) {
+ Assert.isMainThread();
+ checkForReentrantCall();
+
+ try {
+ mStatusBarService.onClearAllNotifications(userId);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ }
+
+ final List<NotificationEntry> entries = new ArrayList(getActiveNotifs());
+ for (int i = entries.size() - 1; i >= 0; i--) {
+ NotificationEntry entry = entries.get(i);
+ if (!shouldDismissOnClearAll(entry, userId)) {
+ // system server won't be removing these notifications, but we still give dismiss
+ // interceptors the chance to filter the notification
+ updateDismissInterceptors(entry);
+ if (isDismissIntercepted(entry)) {
+ mLogger.logNotifClearAllDismissalIntercepted(entry.getKey());
+ }
+ entries.remove(i);
+ }
+ }
+
+ locallyDismissNotifications(entries);
+ rebuildList();
+ }
+
+ /**
+ * Optimistically marks the given notifications as dismissed -- we'll wait for the signal
+ * from system server before removing it from our notification set.
+ */
+ private void locallyDismissNotifications(List<NotificationEntry> entries) {
+ final List<NotificationEntry> canceledEntries = new ArrayList<>();
+
+ for (int i = 0; i < entries.size(); i++) {
+ NotificationEntry entry = entries.get(i);
+
+ entry.setDismissState(DISMISSED);
+ mLogger.logNotifDismissed(entry.getKey());
+
+ if (isCanceled(entry)) {
+ canceledEntries.add(entry);
+ } else {
+ // Mark any children as dismissed as system server will auto-dismiss them as well
+ if (entry.getSbn().getNotification().isGroupSummary()) {
+ for (NotificationEntry otherEntry : mNotificationSet.values()) {
+ if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
+ otherEntry.setDismissState(PARENT_DISMISSED);
+ if (isCanceled(otherEntry)) {
+ canceledEntries.add(otherEntry);
+ }
}
}
}
@@ -255,7 +321,6 @@
for (NotificationEntry canceledEntry : canceledEntries) {
tryRemoveNotification(canceledEntry);
}
- rebuildList();
}
private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
@@ -552,7 +617,7 @@
*
* See NotificationManager.cancelGroupChildrenByListLocked() for corresponding code.
*/
- private static boolean shouldAutoDismiss(
+ private static boolean shouldAutoDismissChildren(
NotificationEntry entry,
String dismissedGroupKey) {
return entry.getSbn().getGroupKey().equals(dismissedGroupKey)
@@ -562,10 +627,39 @@
&& entry.getDismissState() != DISMISSED;
}
+ /**
+ * When the user 'clears all notifications' through SystemUI, NotificationManager will not
+ * dismiss unclearable notifications.
+ * @return true if we think NotificationManager will dismiss the entry when asked to
+ * cancel this notification with {@link NotificationListenerService#REASON_CANCEL_ALL}
+ *
+ * See NotificationManager.cancelAllLocked for corresponding code.
+ */
+ private static boolean shouldDismissOnClearAll(
+ NotificationEntry entry,
+ @UserIdInt int userId) {
+ return userIdMatches(entry, userId)
+ && entry.isClearable()
+ && !hasFlag(entry, Notification.FLAG_BUBBLE)
+ && entry.getDismissState() != DISMISSED;
+ }
+
private static boolean hasFlag(NotificationEntry entry, int flag) {
return (entry.getSbn().getNotification().flags & flag) != 0;
}
+ /**
+ * Determine whether the userId applies to the notification in question, either because
+ * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
+ *
+ * See NotificationManager#notificationMatchesUserId
+ */
+ private static boolean userIdMatches(NotificationEntry entry, int userId) {
+ return userId == UserHandle.USER_ALL
+ || entry.getSbn().getUser().getIdentifier() == UserHandle.USER_ALL
+ || entry.getSbn().getUser().getIdentifier() == userId;
+ }
+
private void dispatchOnEntryInit(NotificationEntry entry) {
mAmDispatchingToOtherCode = true;
for (NotifCollectionListener listener : mNotifCollectionListeners) {
@@ -613,6 +707,7 @@
}
mAmDispatchingToOtherCode = false;
}
+
@Override
public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) {
final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 83f56cc..1f6413b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -44,6 +44,7 @@
private final IStatusBarService mStatusBarService;
private final NotifCollection mNotifCollection;
private final NotifInflationErrorManager mNotifErrorManager;
+ private final NotifPipeline mNotifPipeline;
private NotificationRowBinderImpl mNotificationRowBinder;
private InflationCallback mExternalInflationCallback;
@@ -52,10 +53,12 @@
public NotifInflaterImpl(
IStatusBarService statusBarService,
NotifCollection notifCollection,
- NotifInflationErrorManager errorManager) {
+ NotifInflationErrorManager errorManager,
+ NotifPipeline notifPipeline) {
mStatusBarService = statusBarService;
mNotifCollection = notifCollection;
mNotifErrorManager = errorManager;
+ mNotifPipeline = notifPipeline;
}
/**
@@ -110,7 +113,7 @@
DISMISS_SENTIMENT_NEUTRAL,
NotificationVisibility.obtain(entry.getKey(),
entry.getRanking().getRank(),
- mNotifCollection.getActiveNotifs().size(),
+ mNotifPipeline.getShadeListCount(),
true,
NotificationLogger.getNotificationLocation(entry))
));
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 d4d2369..44cec96 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
@@ -195,4 +195,27 @@
public List<ListEntry> getShadeList() {
return mShadeListBuilder.getShadeList();
}
+
+ /**
+ * Returns the number of notifications currently shown in the shade. This includes all
+ * children and summary notifications. If this method is called during pipeline execution it
+ * will return the number of notifications in its current state, which will likely be only
+ * partially-generated.
+ */
+ public int getShadeListCount() {
+ final List<ListEntry> entries = getShadeList();
+ int numNotifs = 0;
+ for (int i = 0; i < entries.size(); i++) {
+ final ListEntry entry = entries.get(i);
+ if (entry instanceof GroupEntry) {
+ final GroupEntry parentEntry = (GroupEntry) entry;
+ numNotifs++; // include the summary in the count
+ numNotifs += parentEntry.getChildren().size();
+ } else {
+ numNotifs++;
+ }
+ }
+
+ return numNotifs;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 116c70c..8b2a07d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import static android.service.notification.NotificationStats.DISMISSAL_OTHER;
-import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_UNKNOWN;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.bubbles.BubbleController;
@@ -153,10 +153,10 @@
private DismissedByUserStats createDismissedByUserStats(NotificationEntry entry) {
return new DismissedByUserStats(
DISMISSAL_OTHER,
- DISMISS_SENTIMENT_UNKNOWN,
+ DISMISS_SENTIMENT_NEUTRAL,
NotificationVisibility.obtain(entry.getKey(),
entry.getRanking().getRank(),
- mNotifPipeline.getActiveNotifs().size(),
+ mNotifPipeline.getShadeListCount(),
true, // was visible as a bubble
NotificationLogger.getNotificationLocation(entry))
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index dc7a50d..8675cca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -77,6 +77,14 @@
})
}
+ fun logNotifClearAllDismissalIntercepted(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "CLEAR ALL DISMISSAL INTERCEPTED $str1"
+ })
+ }
+
fun logRankingMissing(key: String, rankingMap: RankingMap) {
buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
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 0cc3371..b2b46d5 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
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.stack;
+import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
@@ -79,6 +82,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
import com.android.keyguard.KeyguardSliceView;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
@@ -98,6 +102,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -114,7 +119,11 @@
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -484,8 +493,10 @@
private NotificationIconAreaController mIconAreaController;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final Rect mTmpRect = new Rect();
- private final NotificationEntryManager mEntryManager =
- Dependency.get(NotificationEntryManager.class);
+ private final FeatureFlags mFeatureFlags;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
+ private final NotificationEntryManager mEntryManager;
private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@VisibleForTesting
@@ -529,7 +540,11 @@
ZenModeController zenController,
NotificationSectionsManager notificationSectionsManager,
ForegroundServiceSectionController fgsSectionController,
- ForegroundServiceDismissalFeatureController fgsFeatureController
+ ForegroundServiceDismissalFeatureController fgsFeatureController,
+ FeatureFlags featureFlags,
+ NotifPipeline notifPipeline,
+ NotificationEntryManager entryManager,
+ NotifCollection notifCollection
) {
super(context, attrs, 0, 0);
Resources res = getResources();
@@ -607,16 +622,26 @@
}
}, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- if (entry.rowExists() && !entry.getSbn().isClearable()) {
- // If the row already exists, the user may have performed a dismiss action on
- // the notification. Since it's not clearable we should snap it back.
- snapViewIfNeeded(entry);
+ mFeatureFlags = featureFlags;
+ mNotifPipeline = notifPipeline;
+ mEntryManager = entryManager;
+ mNotifCollection = notifCollection;
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ NotificationStackScrollLayout.this.onEntryUpdated(entry);
}
- }
- });
+ });
+ } else {
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ @Override
+ public void onPreEntryUpdated(NotificationEntry entry) {
+ NotificationStackScrollLayout.this.onEntryUpdated(entry);
+ }
+ });
+ }
+
dynamicPrivacyController.addListener(this);
mDynamicPrivacyController = dynamicPrivacyController;
mStatusbarStateController = statusBarStateController;
@@ -708,7 +733,7 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL);
- boolean showFooterView = (showDismissView || mEntryManager.hasActiveNotifications())
+ boolean showFooterView = (showDismissView || hasActiveNotifications())
&& mStatusBarState != StatusBarState.KEYGUARD
&& !mRemoteInputManager.getController().isRemoteInputActive();
@@ -5537,32 +5562,10 @@
return;
}
- performDismissAllAnimations(viewsToHide, closeShade, () -> {
- for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
- if (canChildBeDismissed(rowToRemove)) {
- if (selection == ROWS_ALL) {
- // TODO: This is a listener method; we shouldn't be calling it. Can we just
- // call performRemoveNotification as below?
- mEntryManager.removeNotification(
- rowToRemove.getEntry().getKey(),
- null /* ranking */,
- NotificationListenerService.REASON_CANCEL_ALL);
- } else {
- mEntryManager.performRemoveNotification(
- rowToRemove.getEntry().getSbn(),
- NotificationListenerService.REASON_CANCEL_ALL);
- }
- } else {
- rowToRemove.resetTranslation();
- }
- }
- if (selection == ROWS_ALL) {
- try {
- mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
- } catch (Exception ex) {
- }
- }
- });
+ performDismissAllAnimations(
+ viewsToHide,
+ closeShade,
+ () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
}
private boolean includeChildInDismissAll(
@@ -6407,6 +6410,83 @@
return false;
}
+ // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
+
+ private void onEntryUpdated(NotificationEntry entry) {
+ // If the row already exists, the user may have performed a dismiss action on the
+ // notification. Since it's not clearable we should snap it back.
+ if (entry.rowExists() && !entry.getSbn().isClearable()) {
+ snapViewIfNeeded(entry);
+ }
+ }
+
+ private boolean hasActiveNotifications() {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ return mNotifPipeline.getShadeList().isEmpty();
+ } else {
+ return mEntryManager.hasActiveNotifications();
+ }
+ }
+
+ /**
+ * Called after the animations for a "clear all notifications" action has ended.
+ */
+ private void onDismissAllAnimationsEnd(
+ List<ExpandableNotificationRow> viewsToRemove,
+ @SelectedRows int selectedRows) {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ if (selectedRows == ROWS_ALL) {
+ mNotifCollection.dismissAllNotifications(mLockscreenUserManager.getCurrentUserId());
+ } else {
+ final List<Pair<NotificationEntry, DismissedByUserStats>>
+ entriesWithRowsDismissedFromShade = new ArrayList<>();
+ final List<DismissedByUserStats> dismissalUserStats = new ArrayList<>();
+ final int numVisibleEntries = mNotifPipeline.getShadeListCount();
+ for (int i = 0; i < viewsToRemove.size(); i++) {
+ final NotificationEntry entry = viewsToRemove.get(i).getEntry();
+ final DismissedByUserStats stats =
+ new DismissedByUserStats(
+ DISMISSAL_SHADE,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(
+ entry.getKey(),
+ entry.getRanking().getRank(),
+ numVisibleEntries,
+ true,
+ NotificationLogger.getNotificationLocation(entry)));
+ entriesWithRowsDismissedFromShade.add(
+ new Pair<NotificationEntry, DismissedByUserStats>(entry, stats));
+ }
+ mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
+ }
+ } else {
+ for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+ if (canChildBeDismissed(rowToRemove)) {
+ if (selectedRows == ROWS_ALL) {
+ // TODO: This is a listener method; we shouldn't be calling it. Can we just
+ // call performRemoveNotification as below?
+ mEntryManager.removeNotification(
+ rowToRemove.getEntry().getKey(),
+ null /* ranking */,
+ NotificationListenerService.REASON_CANCEL_ALL);
+ } else {
+ mEntryManager.performRemoveNotification(
+ rowToRemove.getEntry().getSbn(),
+ NotificationListenerService.REASON_CANCEL_ALL);
+ }
+ } else {
+ rowToRemove.resetTranslation();
+ }
+ }
+ if (selectedRows == ROWS_ALL) {
+ try {
+ mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
+ } catch (Exception ex) {
+ }
+ }
+ }
+ }
+
// ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
@ShadeViewRefactor(RefactorComponent.INPUT)
@@ -6415,8 +6495,7 @@
/* Only ever called as a consequence of a lockscreen expansion gesture. */
@Override
public boolean onDraggedDown(View startingChild, int dragLengthY) {
- if (mStatusBarState == StatusBarState.KEYGUARD
- && mEntryManager.hasActiveNotifications()) {
+ if (mStatusBarState == StatusBarState.KEYGUARD && hasActiveNotifications()) {
mLockscreenGestureLogger.write(
MetricsEvent.ACTION_LS_SHADE,
(int) (dragLengthY / mDisplayMetrics.density),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index 40b1610..d016217 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -48,7 +48,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.RemoteInputController.Callback;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -96,15 +95,13 @@
mCallbacks = Lists.newArrayList();
private final SysuiColorExtractor mColorExtractor;
- private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
@Inject
public NotificationShadeWindowController(Context context, WindowManager windowManager,
IActivityManager activityManager, DozeParameters dozeParameters,
StatusBarStateController statusBarStateController,
ConfigurationController configurationController,
- KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor,
- SuperStatusBarViewFactory superStatusBarViewFactory) {
+ KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor) {
mContext = context;
mWindowManager = windowManager;
mActivityManager = activityManager;
@@ -114,8 +111,6 @@
mLpChanged = new LayoutParams();
mKeyguardBypassController = keyguardBypassController;
mColorExtractor = colorExtractor;
- mSuperStatusBarViewFactory = superStatusBarViewFactory;
- mNotificationShadeView = mSuperStatusBarViewFactory.getNotificationShadeWindowView();
mLockScreenDisplayTimeout = context.getResources()
.getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -194,6 +189,10 @@
onThemeChanged();
}
+ public void setNotificationShadeView(ViewGroup view) {
+ mNotificationShadeView = view;
+ }
+
public ViewGroup getNotificationShadeView() {
return mNotificationShadeView;
}
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 4f01cc1..823adff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1353,6 +1353,7 @@
.statusBarWindowView(mNotificationShadeWindowView).build();
mNotificationShadeWindowViewController = statusBarComponent
.getNotificationShadeWindowViewController();
+ mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowViewController.setupExpandedStatusBar();
mStatusBarWindowController = statusBarComponent.getStatusBarWindowController();
mPhoneStatusBarWindow = mSuperStatusBarViewFactory.getStatusBarWindowView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 0f3b5db..e1a20b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import static com.android.systemui.statusbar.phone.StatusBar.getActivityOptions;
@@ -35,6 +36,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.dreams.IDreamManager;
+import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.EventLog;
@@ -56,6 +58,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -66,7 +69,11 @@
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
@@ -97,6 +104,9 @@
private final KeyguardStateController mKeyguardStateController;
private final ActivityStarter mActivityStarter;
private final NotificationEntryManager mEntryManager;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
+ private final FeatureFlags mFeatureFlags;
private final StatusBarStateController mStatusBarStateController;
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private final MetricsLogger mMetricsLogger;
@@ -135,7 +145,9 @@
NotificationInterruptionStateProvider notificationInterruptionStateProvider,
MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
- ActivityIntentHelper activityIntentHelper, BubbleController bubbleController) {
+ ActivityIntentHelper activityIntentHelper, BubbleController bubbleController,
+ FeatureFlags featureFlags, NotifPipeline notifPipeline,
+ NotifCollection notifCollection) {
mContext = context;
mNotificationPanel = panel;
mPresenter = presenter;
@@ -162,12 +174,25 @@
mLockPatternUtils = lockPatternUtils;
mBackgroundHandler = backgroundHandler;
mUiBgExecutor = uiBgExecutor;
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- handleFullScreenIntent(entry);
- }
- });
+ mFeatureFlags = featureFlags;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
+ if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ @Override
+ public void onPendingEntryAdded(NotificationEntry entry) {
+ handleFullScreenIntent(entry);
+ }
+ });
+ } else {
+ mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryAdded(NotificationEntry entry) {
+ handleFullScreenIntent(entry);
+ }
+ });
+ }
+
mStatusBarRemoteInputCallback = remoteInputCallback;
mMainThreadHandler = mainThreadHandler;
mActivityIntentHelper = activityIntentHelper;
@@ -246,15 +271,14 @@
mHeadsUpManager.removeNotification(sbn.getKey(),
true /* releaseImmediately */);
}
- StatusBarNotification parentToCancel = null;
+ NotificationEntry parentToCancel = null;
if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
- StatusBarNotification summarySbn =
- mGroupManager.getLogicalGroupSummary(sbn).getSbn();
- if (shouldAutoCancel(summarySbn)) {
+ NotificationEntry summarySbn = mGroupManager.getLogicalGroupSummary(sbn);
+ if (shouldAutoCancel(summarySbn.getSbn())) {
parentToCancel = summarySbn;
}
}
- final StatusBarNotification parentToCancelFinal = parentToCancel;
+ final NotificationEntry parentToCancelFinal = parentToCancel;
final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed(
sbn, row, controller, intent,
isActivityIntent, wasOccluded, parentToCancelFinal);
@@ -279,7 +303,7 @@
PendingIntent intent,
boolean isActivityIntent,
boolean wasOccluded,
- StatusBarNotification parentToCancelFinal) {
+ NotificationEntry parentToCancelFinal) {
String notificationKey = sbn.getKey();
try {
// The intent we are sending is for the application, which
@@ -330,7 +354,7 @@
collapseOnMainThread();
}
- final int count = mEntryManager.getActiveNotificationsCount();
+ final int count = getVisibleNotificationsCount();
final int rank = entry.getRanking().getRank();
NotificationVisibility.NotificationLocation location =
NotificationLogger.getNotificationLocation(entry);
@@ -341,15 +365,19 @@
} catch (RemoteException ex) {
// system process is dead if we're here.
}
+
if (!isBubble) {
if (parentToCancelFinal != null) {
+ // TODO: (b/145659174) remove - this cancels the parent if the notification clicked
+ // on will auto-cancel and is the only child in the group. This won't be
+ // necessary in the new pipeline due to group pruning in ShadeListBuilder.
removeNotification(parentToCancelFinal);
}
if (shouldAutoCancel(sbn)
|| mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
notificationKey)) {
// Automatically remove all notifications that we may have kept around longer
- removeNotification(sbn);
+ removeNotification(row.getEntry());
}
}
mIsCollapsingToShowActivityOverLockscreen = false;
@@ -482,11 +510,10 @@
return entry.shouldSuppressFullScreenIntent();
}
- private void removeNotification(StatusBarNotification notification) {
+ private void removeNotification(NotificationEntry entry) {
// We have to post it to the UI thread for synchronization
mMainThreadHandler.post(() -> {
- Runnable removeRunnable =
- () -> mEntryManager.performRemoveNotification(notification, REASON_CLICK);
+ Runnable removeRunnable = createRemoveRunnable(entry);
if (mPresenter.isCollapsing()) {
// To avoid lags we're only performing the remove
// after the shade was collapsed
@@ -497,6 +524,53 @@
});
}
+ // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
+
+ private int getVisibleNotificationsCount() {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ return mNotifPipeline.getShadeListCount();
+ } else {
+ return mEntryManager.getActiveNotificationsCount();
+ }
+ }
+
+ private Runnable createRemoveRunnable(NotificationEntry entry) {
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ return new Runnable() {
+ @Override
+ public void run() {
+ // see NotificationLogger#logNotificationClear
+ int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+ if (mHeadsUpManager.isAlerting(entry.getKey())) {
+ dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+ } else if (mNotificationPanel.hasPulsingNotifications()) {
+ dismissalSurface = NotificationStats.DISMISSAL_AOD;
+ }
+
+ mNotifCollection.dismissNotification(
+ entry,
+ new DismissedByUserStats(
+ dismissalSurface,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(
+ entry.getKey(),
+ entry.getRanking().getRank(),
+ mNotifPipeline.getShadeListCount(),
+ true,
+ NotificationLogger.getNotificationLocation(entry))
+ ));
+ }
+ };
+ } else {
+ return new Runnable() {
+ @Override
+ public void run() {
+ mEntryManager.performRemoveNotification(entry.getSbn(), REASON_CLICK);
+ }
+ };
+ }
+ }
+
/**
* Public builder for {@link StatusBarNotificationActivityStarter}.
*/
@@ -506,6 +580,9 @@
private final CommandQueue mCommandQueue;
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationEntryManager mEntryManager;
+ private final FeatureFlags mFeatureFlags;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
private final HeadsUpManagerPhone mHeadsUpManager;
private final ActivityStarter mActivityStarter;
private final IStatusBarService mStatusBarService;
@@ -557,7 +634,10 @@
@UiBackground Executor uiBgExecutor,
ActivityIntentHelper activityIntentHelper,
BubbleController bubbleController,
- ShadeController shadeController) {
+ ShadeController shadeController,
+ FeatureFlags featureFlags,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection) {
mContext = context;
mCommandQueue = commandQueue;
mAssistManagerLazy = assistManagerLazy;
@@ -583,6 +663,9 @@
mActivityIntentHelper = activityIntentHelper;
mBubbleController = bubbleController;
mShadeController = shadeController;
+ mFeatureFlags = featureFlags;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
}
/** Sets the status bar to use as {@link StatusBar}. */
@@ -608,8 +691,6 @@
return this;
}
-
-
public StatusBarNotificationActivityStarter build() {
return new StatusBarNotificationActivityStarter(mContext,
mCommandQueue, mAssistManagerLazy,
@@ -638,7 +719,10 @@
mBackgroundHandler,
mUiBgExecutor,
mActivityIntentHelper,
- mBubbleController);
+ mBubbleController,
+ mFeatureFlags,
+ mNotifPipeline,
+ mNotifCollection);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index dea8c5d..edab4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -38,6 +38,7 @@
import android.widget.Toast;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.CommandQueue;
@@ -67,12 +68,21 @@
@Inject
public ToastUI(Context context, CommandQueue commandQueue) {
+ this(context, commandQueue,
+ (WindowManager) context.getSystemService(Context.WINDOW_SERVICE),
+ INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
+ AccessibilityManager.getInstance(context));
+ }
+
+ @VisibleForTesting
+ ToastUI(Context context, CommandQueue commandQueue, WindowManager windowManager,
+ INotificationManager notificationManager, AccessibilityManager accessibilityManager) {
super(context);
mCommandQueue = commandQueue;
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- mNotificationManager = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- mAccessibilityManager = AccessibilityManager.getInstance(context);
+ mWindowManager = windowManager;
+ mNotificationManager = notificationManager;
+ mAccessibilityManager = accessibilityManager;
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
new file mode 100644
index 0000000..fff7e4a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.accessibility;
+
+import static android.view.WindowManager.LayoutParams;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class MirrorWindowControlTest extends SysuiTestCase {
+
+ @Mock WindowManager mWindowManager;
+ @Mock IBinder mIBinder;
+ View mView;
+ int mViewWidth;
+ int mViewHeight;
+
+ StubMirrorWindowControl mStubMirrorWindowControl;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mView = new View(getContext());
+ mViewWidth = 10;
+ mViewHeight = 20;
+ getContext().addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ doAnswer(invocation -> {
+ View view = invocation.getArgument(0);
+ LayoutParams lp = invocation.getArgument(1);
+ view.setLayoutParams(lp);
+ return null;
+ }).when(mWindowManager).addView(any(View.class), any(LayoutParams.class));
+
+ mStubMirrorWindowControl = new StubMirrorWindowControl(getContext(), mView, mViewWidth,
+ mViewHeight);
+ }
+
+ @Test
+ public void showControl_createViewAndAddView() {
+ mStubMirrorWindowControl.showControl(mIBinder);
+
+ assertTrue(mStubMirrorWindowControl.mInvokeOnCreateView);
+ ArgumentCaptor<ViewGroup.LayoutParams> lpCaptor = ArgumentCaptor.forClass(
+ ViewGroup.LayoutParams.class);
+ verify(mWindowManager).addView(any(), lpCaptor.capture());
+ assertTrue(lpCaptor.getValue().width == mViewWidth);
+ assertTrue(lpCaptor.getValue().height == mViewHeight);
+ }
+
+ @Test
+ public void destroyControl_removeView() {
+ mStubMirrorWindowControl.showControl(mIBinder);
+ ArgumentCaptor<View> captor = ArgumentCaptor.forClass(View.class);
+ verify(mWindowManager).addView(captor.capture(), any(LayoutParams.class));
+
+ mStubMirrorWindowControl.destroyControl();
+
+ verify(mWindowManager).removeView(eq(captor.getValue()));
+ }
+
+ @Test
+ public void move_offsetIsCorrect() {
+ ArgumentCaptor<ViewGroup.LayoutParams> lpCaptor = ArgumentCaptor.forClass(
+ ViewGroup.LayoutParams.class);
+ mStubMirrorWindowControl.showControl(mIBinder);
+ verify(mWindowManager).addView(any(), lpCaptor.capture());
+ LayoutParams lp = (LayoutParams) lpCaptor.getValue();
+ Point startPosition = new Point(lp.x, lp.y);
+
+ mStubMirrorWindowControl.move(-10, -20);
+
+ verify(mWindowManager).updateViewLayout(eq(mView), lpCaptor.capture());
+ assertTrue(lpCaptor.getAllValues().size() == 2);
+ lp = (LayoutParams) lpCaptor.getValue();
+ Point currentPosition = new Point(lp.x, lp.y);
+ assertEquals(-10, currentPosition.x - startPosition.x);
+ assertEquals(-20, currentPosition.y - startPosition.y);
+ }
+
+ private static class StubMirrorWindowControl extends MirrorWindowControl {
+ private final int mWidth;
+ private final int mHeight;
+ private final View mView;
+
+ boolean mInvokeOnCreateView = false;
+
+ StubMirrorWindowControl(Context context, View view, int width, int height) {
+ super(context);
+ mView = view;
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public String getWindowTitle() {
+ return "StubMirrorWindowControl";
+ }
+
+ @Override
+ View onCreateView(LayoutInflater inflater, Point viewSize) {
+ mInvokeOnCreateView = true;
+ viewSize.x = mWidth;
+ viewSize.y = mHeight;
+ return mView;
+ }
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
new file mode 100644
index 0000000..08a6172
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.accessibility;
+
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class WindowMagnificationControllerTest extends SysuiTestCase {
+
+ @Mock
+ MirrorWindowControl mMirrorWindowControl;
+ private WindowMagnificationController mWindowMagnificationController;
+ private Instrumentation mInstrumentation;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mWindowMagnificationController = new WindowMagnificationController(getContext(),
+ mMirrorWindowControl);
+ verify(mMirrorWindowControl).setWindowDelegate(
+ any(MirrorWindowControl.MirrorWindowDelegate.class));
+ }
+
+ @After
+ public void tearDown() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.deleteWindowMagnification();
+ });
+ mInstrumentation.waitForIdleSync();
+ }
+
+ @Test
+ public void createWindowMagnification_showControl() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.createWindowMagnification();
+ });
+ mInstrumentation.waitForIdleSync();
+ verify(mMirrorWindowControl).showControl(any(IBinder.class));
+ }
+
+ @Test
+ public void deleteWindowMagnification_destroyControl() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.createWindowMagnification();
+ });
+ mInstrumentation.waitForIdleSync();
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.deleteWindowMagnification();
+ });
+ mInstrumentation.waitForIdleSync();
+
+ verify(mMirrorWindowControl).destroyControl();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index c3b55e2..7d47f6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -201,8 +201,9 @@
// Bubbles get added to status bar window view
mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController, mColorExtractor,
- mSuperStatusBarViewFactory);
+ mConfigurationController, mKeyguardBypassController, mColorExtractor);
+ mNotificationShadeWindowController.setNotificationShadeView(
+ mSuperStatusBarViewFactory.getNotificationShadeWindowView());
mNotificationShadeWindowController.attach();
// Need notifications for bubbles
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 72405fc..5a1bef9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -195,8 +195,9 @@
// Bubbles get added to status bar window view
mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController, mColorExtractor,
- mSuperStatusBarViewFactory);
+ mConfigurationController, mKeyguardBypassController, mColorExtractor);
+ mNotificationShadeWindowController.setNotificationShadeView(
+ mSuperStatusBarViewFactory.getNotificationShadeWindowView());
mNotificationShadeWindowController.attach();
// Need notifications for bubbles
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
index 89c1636..40075c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -86,7 +86,11 @@
@Test
fun testBindAndLoad() {
- val callback: (List<Control>) -> Unit = {}
+ val callback = object : ControlsBindingController.LoadCallback {
+ override fun error(message: String) {}
+
+ override fun accept(t: List<Control>) {}
+ }
controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
assertEquals(1, providers.size)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 751217f..e5ec2dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -56,6 +56,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.util.Optional
+import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -79,7 +80,8 @@
@Captor
private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
@Captor
- private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
+ private lateinit var controlLoadCallbackCaptor:
+ ArgumentCaptor<ControlsBindingController.LoadCallback>
@Captor
private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
@@ -226,13 +228,13 @@
val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
val control = builderFromInfo(newControlInfo).build()
- controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit }
+ controller.loadForComponent(TEST_COMPONENT, Consumer {})
reset(persistenceWrapper)
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
- controlLoadCallbackCaptor.value.invoke(listOf(control))
+ controlLoadCallbackCaptor.value.accept(listOf(control))
verify(persistenceWrapper).storeFavorites(listOf(newControlInfo))
}
@@ -294,19 +296,22 @@
var loaded = false
val control = builderFromInfo(TEST_CONTROL_INFO).build()
- controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
+ controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
+ val controls = data.allControls
+ val favorites = data.favoritesIds
loaded = true
assertEquals(1, controls.size)
val controlStatus = controls[0]
assertEquals(ControlStatus(control, false), controlStatus)
assertTrue(favorites.isEmpty())
- }
+ assertFalse(data.errorOnLoad)
+ })
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
- controlLoadCallbackCaptor.value.invoke(listOf(control))
+ controlLoadCallbackCaptor.value.accept(listOf(control))
assertTrue(loaded)
}
@@ -318,7 +323,9 @@
val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
- controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
+ controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
+ val controls = data.allControls
+ val favorites = data.favoritesIds
loaded = true
assertEquals(2, controls.size)
val controlStatus = controls.first { it.control.controlId == TEST_CONTROL_ID }
@@ -329,12 +336,13 @@
assertEquals(1, favorites.size)
assertEquals(TEST_CONTROL_ID, favorites[0])
- }
+ assertFalse(data.errorOnLoad)
+ })
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
- controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
+ controlLoadCallbackCaptor.value.accept(listOf(control, control2))
assertTrue(loaded)
}
@@ -344,7 +352,9 @@
var loaded = false
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
- controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
+ controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
+ val controls = data.allControls
+ val favorites = data.favoritesIds
loaded = true
assertEquals(1, controls.size)
val controlStatus = controls[0]
@@ -354,12 +364,41 @@
assertEquals(1, favorites.size)
assertEquals(TEST_CONTROL_ID, favorites[0])
- }
+ assertFalse(data.errorOnLoad)
+ })
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
- controlLoadCallbackCaptor.value.invoke(emptyList())
+ controlLoadCallbackCaptor.value.accept(emptyList())
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testErrorOnLoad_notRemoved() {
+ var loaded = false
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ controller.loadForComponent(TEST_COMPONENT, Consumer { data ->
+ val controls = data.allControls
+ val favorites = data.favoritesIds
+ loaded = true
+ assertEquals(1, controls.size)
+ val controlStatus = controls[0]
+ assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
+ assertTrue(controlStatus.favorite)
+ assertFalse(controlStatus.removed)
+
+ assertEquals(1, favorites.size)
+ assertEquals(TEST_CONTROL_ID, favorites[0])
+ assertTrue(data.errorOnLoad)
+ })
+
+ verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.error("")
assertTrue(loaded)
}
@@ -370,12 +409,12 @@
val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
val control = builderFromInfo(newControlInfo).build()
- controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit }
+ controller.loadForComponent(TEST_COMPONENT, Consumer {})
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
- controlLoadCallbackCaptor.value.invoke(listOf(control))
+ controlLoadCallbackCaptor.value.accept(listOf(control))
val favorites = controller.getFavoriteControls()
assertEquals(1, favorites.size)
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
index 40566dc..ddd6b12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -18,7 +18,6 @@
import android.content.ComponentName
import android.os.UserHandle
-import android.service.controls.Control
import android.service.controls.IControlsActionCallback
import android.service.controls.IControlsLoadCallback
import android.service.controls.IControlsProvider
@@ -28,7 +27,6 @@
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
@@ -41,6 +39,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
@@ -53,20 +52,22 @@
class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
@Mock
- private lateinit var actionCallback: IControlsActionCallback.Stub
+ private lateinit var actionCallbackService: IControlsActionCallback.Stub
@Mock
- private lateinit var loadCallback: IControlsLoadCallback.Stub
+ private lateinit var loadCallbackService: IControlsLoadCallback.Stub
@Mock
- private lateinit var subscriber: IControlsSubscriber.Stub
+ private lateinit var subscriberService: IControlsSubscriber.Stub
@Mock
private lateinit var service: IControlsProvider.Stub
+ @Mock
+ private lateinit var loadCallback: ControlsBindingController.LoadCallback
@Captor
private lateinit var wrapperCaptor: ArgumentCaptor<ControlActionWrapper>
private val componentName = ComponentName("test.pkg", "test.cls")
private lateinit var manager: ControlsProviderLifecycleManager
- private lateinit var executor: DelayableExecutor
+ private lateinit var executor: FakeExecutor
companion object {
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
@@ -84,9 +85,9 @@
manager = ControlsProviderLifecycleManager(
context,
executor,
- loadCallback,
- actionCallback,
- subscriber,
+ loadCallbackService,
+ actionCallbackService,
+ subscriberService,
UserHandle.of(0),
componentName
)
@@ -112,18 +113,17 @@
@Test
fun testMaybeBindAndLoad() {
- val callback: (List<Control>) -> Unit = {}
- manager.maybeBindAndLoad(callback)
+ manager.maybeBindAndLoad(loadCallback)
- verify(service).load(loadCallback)
+ verify(service).load(loadCallbackService)
assertTrue(mContext.isBound(componentName))
- assertEquals(callback, manager.lastLoadCallback)
+ assertEquals(loadCallback, manager.lastLoadCallback)
}
@Test
fun testMaybeUnbind_bindingAndCallback() {
- manager.maybeBindAndLoad {}
+ manager.maybeBindAndLoad(loadCallback)
manager.unbindService()
assertFalse(mContext.isBound(componentName))
@@ -131,12 +131,22 @@
}
@Test
+ fun testMaybeBindAndLoad_timeout() {
+ manager.maybeBindAndLoad(loadCallback)
+
+ executor.advanceClockToLast()
+ executor.runAllReady()
+
+ verify(loadCallback).error(anyString())
+ }
+
+ @Test
fun testMaybeBindAndSubscribe() {
val list = listOf("TEST_ID")
manager.maybeBindAndSubscribe(list)
assertTrue(mContext.isBound(componentName))
- verify(service).subscribe(list, subscriber)
+ verify(service).subscribe(list, subscriberService)
}
@Test
@@ -147,7 +157,7 @@
assertTrue(mContext.isBound(componentName))
verify(service).action(eq(controlId), capture(wrapperCaptor),
- eq(actionCallback))
+ eq(actionCallbackService))
assertEquals(action, wrapperCaptor.getValue().getWrappedAction())
}
}
\ 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 96db16a..12e9d31 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection;
+import static android.app.Notification.FLAG_NO_CLEAR;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
@@ -33,8 +34,8 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -50,12 +51,12 @@
import android.os.RemoteException;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import androidx.test.filters.SmallTest;
@@ -104,7 +105,6 @@
@Spy private RecordingCollectionListener mCollectionListener;
@Mock private CollectionReadyForBuildListener mBuildListener;
@Mock private FeatureFlags mFeatureFlags;
- @Mock private DismissedByUserStats mDismissedByUserStats;
@Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1");
@Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2");
@@ -424,13 +424,16 @@
public void testDismissingLifetimeExtendedSummaryDoesNotDismissChildren() {
// GIVEN A notif group with one summary and two children
mCollection.addNotificationLifetimeExtender(mExtender1);
- NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")
- .setGroup(mContext, GROUP_1)
- .setGroupSummary(mContext, true));
- NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")
- .setGroup(mContext, GROUP_1));
- NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3, "myTag")
- .setGroup(mContext, GROUP_1));
+ CollectionEvent notif1 = postNotif(
+ buildNotif(TEST_PACKAGE, 1, "myTag")
+ .setGroup(mContext, GROUP_1)
+ .setGroupSummary(mContext, true));
+ CollectionEvent notif2 = postNotif(
+ buildNotif(TEST_PACKAGE, 2, "myTag")
+ .setGroup(mContext, GROUP_1));
+ CollectionEvent notif3 = postNotif(
+ buildNotif(TEST_PACKAGE, 3, "myTag")
+ .setGroup(mContext, GROUP_1));
NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
@@ -456,7 +459,7 @@
}
@Test
- public void testDismissInterceptorsAreCalled() throws RemoteException {
+ public void testDismissNotificationCallsDismissInterceptors() throws RemoteException {
// GIVEN a collection with notifications with multiple dismiss interceptors
mInterceptor1.shouldInterceptDismissal = true;
mInterceptor2.shouldInterceptDismissal = true;
@@ -469,10 +472,7 @@
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// WHEN a notification is manually dismissed
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ DismissedByUserStats stats = defaultStats(entry);
mCollection.dismissNotification(entry, stats);
// THEN all interceptors get checked
@@ -506,10 +506,7 @@
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// WHEN a notification is manually dismissed and intercepted
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ DismissedByUserStats stats = defaultStats(entry);
mCollection.dismissNotification(entry, stats);
assertEquals(List.of(mInterceptor1, mInterceptor2), entry.mDismissInterceptors);
clearInvocations(mInterceptor1, mInterceptor2);
@@ -531,7 +528,7 @@
eq(notif.sbn.getKey()),
anyInt(),
anyInt(),
- anyObject());
+ eq(stats.notificationVisibility));
}
@Test
@@ -544,19 +541,16 @@
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// GIVEN a notification is manually dismissed
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ DismissedByUserStats stats = defaultStats(entry);
mCollection.dismissNotification(entry, stats);
// WHEN all interceptors end their interception dismissal
mInterceptor1.shouldInterceptDismissal = false;
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
- mDismissedByUserStats);
+ stats);
// THEN we send the dismissal to system server
- verify(mStatusBarService, times(1)).onNotificationClear(
+ verify(mStatusBarService).onNotificationClear(
eq(notif.sbn.getPackageName()),
eq(notif.sbn.getTag()),
eq(47),
@@ -564,7 +558,7 @@
eq(notif.sbn.getKey()),
anyInt(),
anyInt(),
- anyObject());
+ eq(stats.notificationVisibility));
}
@Test
@@ -581,16 +575,12 @@
NotificationEntry entry = mCollectionListener.getEntry(notif.key);
// GIVEN a notification is manually dismissed
- DismissedByUserStats stats = new DismissedByUserStats(
- NotificationStats.DISMISSAL_SHADE,
- NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
- mCollection.dismissNotification(entry, stats);
+ mCollection.dismissNotification(entry, defaultStats(entry));
// WHEN an interceptor ends its interception
mInterceptor1.shouldInterceptDismissal = false;
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
- mDismissedByUserStats);
+ defaultStats(entry));
// THEN all interceptors get checked
verify(mInterceptor1).shouldInterceptDismissal(entry);
@@ -613,7 +603,7 @@
// WHEN we try to end the dismissal of an interceptor that didn't intercept the notif
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
- mDismissedByUserStats);
+ defaultStats(entry));
// THEN an exception is thrown
}
@@ -636,11 +626,11 @@
@Test
public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
// GIVEN a collection with two grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -657,11 +647,11 @@
@Test
public void testUpdatingDismissedSummaryBringsChildrenBack() {
// GIVEN a collection with two grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -680,14 +670,14 @@
@Test
public void testDismissedChildrenAreNotResetByParentUpdate() {
// GIVEN a collection with three grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
- NotifEvent notif2 = mNoMan.postNotif(
+ CollectionEvent notif2 = postNotif(
buildNotif(TEST_PACKAGE, 2)
.setGroup(mContext, GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -709,11 +699,11 @@
@Test
public void testUpdatingGroupKeyOfDismissedSummaryBringsChildrenBack() {
// GIVEN a collection with two grouped notifs in it
- NotifEvent notif0 = mNoMan.postNotif(
+ CollectionEvent notif0 = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setOverrideGroupKey(GROUP_1)
.setGroupSummary(mContext, true));
- NotifEvent notif1 = mNoMan.postNotif(
+ CollectionEvent notif1 = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setOverrideGroupKey(GROUP_1));
NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
@@ -1055,6 +1045,213 @@
assertEquals(REASON_NOT_CANCELED, entry0.mCancellationReason);
}
+ @Test
+ public void testDismissNotificationsRebuildsOnce() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ clearInvocations(mBuildListener);
+
+ // WHEN both notifications are manually dismissed together
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN build list is only called one time
+ verify(mBuildListener).onBuildList(any(Collection.class));
+ }
+
+ @Test
+ public void testDismissNotificationsSentToSystemServer() throws RemoteException {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN both notifications are manually dismissed together
+ DismissedByUserStats stats1 = defaultStats(entry1);
+ DismissedByUserStats stats2 = defaultStats(entry2);
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN we send the dismissals to system server
+ verify(mStatusBarService).onNotificationClear(
+ notif1.sbn.getPackageName(),
+ notif1.sbn.getTag(),
+ 47,
+ notif1.sbn.getUser().getIdentifier(),
+ notif1.sbn.getKey(),
+ stats1.dismissalSurface,
+ stats1.dismissalSentiment,
+ stats1.notificationVisibility);
+
+ verify(mStatusBarService).onNotificationClear(
+ notif2.sbn.getPackageName(),
+ notif2.sbn.getTag(),
+ 88,
+ notif2.sbn.getUser().getIdentifier(),
+ notif2.sbn.getKey(),
+ stats2.dismissalSurface,
+ stats2.dismissalSentiment,
+ stats2.notificationVisibility);
+ }
+
+ @Test
+ public void testDismissNotificationsMarkedAsDismissed() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN both notifications are manually dismissed together
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN the entries are marked as dismissed
+ assertEquals(DISMISSED, entry1.getDismissState());
+ assertEquals(DISMISSED, entry2.getDismissState());
+ }
+
+ @Test
+ public void testDismissNotificationssCallsDismissInterceptors() {
+ // GIVEN a collection with notifications with multiple dismiss interceptors
+ mInterceptor1.shouldInterceptDismissal = true;
+ mInterceptor2.shouldInterceptDismissal = true;
+ mInterceptor3.shouldInterceptDismissal = false;
+ mCollection.addNotificationDismissInterceptor(mInterceptor1);
+ mCollection.addNotificationDismissInterceptor(mInterceptor2);
+ mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN both notifications are manually dismissed together
+ mCollection.dismissNotifications(
+ List.of(new Pair(entry1, defaultStats(entry1)),
+ new Pair(entry2, defaultStats(entry2))));
+
+ // THEN all interceptors get checked
+ verify(mInterceptor1).shouldInterceptDismissal(entry1);
+ verify(mInterceptor2).shouldInterceptDismissal(entry1);
+ verify(mInterceptor3).shouldInterceptDismissal(entry1);
+ verify(mInterceptor1).shouldInterceptDismissal(entry2);
+ verify(mInterceptor2).shouldInterceptDismissal(entry2);
+ verify(mInterceptor3).shouldInterceptDismissal(entry2);
+
+ assertEquals(List.of(mInterceptor1, mInterceptor2), entry1.mDismissInterceptors);
+ assertEquals(List.of(mInterceptor1, mInterceptor2), entry2.mDismissInterceptors);
+ }
+
+ @Test
+ public void testDismissAllNotificationsCallsRebuildOnce() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+ clearInvocations(mBuildListener);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+ // THEN build list is only called one time
+ verify(mBuildListener).onBuildList(any(Collection.class));
+ }
+
+ @Test
+ public void testDismissAllNotificationsSentToSystemServer() throws RemoteException {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+ // THEN we send the dismissal to system server
+ verify(mStatusBarService).onClearAllNotifications(
+ entry1.getSbn().getUser().getIdentifier());
+ }
+
+ @Test
+ public void testDismissAllNotificationsMarkedAsDismissed() {
+ // GIVEN a collection with a couple notifications
+ NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(entry1.getSbn().getUser().getIdentifier());
+
+ // THEN the entries are marked as dismissed
+ assertEquals(DISMISSED, entry1.getDismissState());
+ assertEquals(DISMISSED, entry2.getDismissState());
+ }
+
+ @Test
+ public void testDismissAllNotificationsDoesNotMarkDismissedUnclearableNotifs() {
+ // GIVEN a collection with one unclearable notification and one clearable notification
+ NotificationEntryBuilder notifEntryBuilder = buildNotif(TEST_PACKAGE, 47, "myTag");
+ notifEntryBuilder.modifyNotification(mContext)
+ .setFlag(FLAG_NO_CLEAR, true);
+ NotifEvent unclearabeNotif = mNoMan.postNotif(notifEntryBuilder);
+ NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag"));
+ NotificationEntry unclearableEntry = mCollectionListener.getEntry(unclearabeNotif.key);
+ NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key);
+
+ // WHEN all notifications are dismissed for the user who posted both notifs
+ mCollection.dismissAllNotifications(unclearableEntry.getSbn().getUser().getIdentifier());
+
+ // THEN only the clearable entry is marked as dismissed
+ assertEquals(NOT_DISMISSED, unclearableEntry.getDismissState());
+ assertEquals(DISMISSED, entry2.getDismissState());
+ }
+
+ @Test
+ public void testDismissAllNotificationsCallsDismissInterceptorsOnlyOnUnclearableNotifs() {
+ // GIVEN a collection with multiple dismiss interceptors
+ mInterceptor1.shouldInterceptDismissal = true;
+ mInterceptor2.shouldInterceptDismissal = true;
+ mInterceptor3.shouldInterceptDismissal = false;
+ mCollection.addNotificationDismissInterceptor(mInterceptor1);
+ mCollection.addNotificationDismissInterceptor(mInterceptor2);
+ mCollection.addNotificationDismissInterceptor(mInterceptor3);
+
+ // GIVEN a collection with one unclearable and one clearable notification
+ NotifEvent unclearableNotif = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 47, "myTag")
+ .setFlag(mContext, FLAG_NO_CLEAR, true));
+ NotificationEntry unclearable = mCollectionListener.getEntry(unclearableNotif.key);
+ NotifEvent clearableNotif = mNoMan.postNotif(
+ buildNotif(TEST_PACKAGE, 88, "myTag")
+ .setFlag(mContext, FLAG_NO_CLEAR, false));
+ NotificationEntry clearable = mCollectionListener.getEntry(clearableNotif.key);
+
+ // WHEN all notifications are dismissed for the user who posted the notif
+ mCollection.dismissAllNotifications(clearable.getSbn().getUser().getIdentifier());
+
+ // THEN all interceptors get checked for the unclearable notification
+ verify(mInterceptor1).shouldInterceptDismissal(unclearable);
+ verify(mInterceptor2).shouldInterceptDismissal(unclearable);
+ verify(mInterceptor3).shouldInterceptDismissal(unclearable);
+ assertEquals(List.of(mInterceptor1, mInterceptor2), unclearable.mDismissInterceptors);
+
+ // THEN no interceptors get checked for the clearable notification
+ verify(mInterceptor1, never()).shouldInterceptDismissal(clearable);
+ verify(mInterceptor2, never()).shouldInterceptDismissal(clearable);
+ verify(mInterceptor3, never()).shouldInterceptDismissal(clearable);
+ }
+
private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) {
return new NotificationEntryBuilder()
.setPkg(pkg)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b16e52c..9ccee75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -70,6 +70,8 @@
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.TestableNotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -133,6 +135,7 @@
@Mock private NotificationSectionsManager mNotificationSectionsManager;
@Mock private NotificationSection mNotificationSection;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock private FeatureFlags mFeatureFlags;
private UserChangedListener mUserChangedListener;
private TestableNotificationEntryManager mEntryManager;
private int mOriginalInterruptionModelSetting;
@@ -182,9 +185,8 @@
mock(LeakDetector.class),
mock(ForegroundServiceDismissalFeatureController.class)
);
- mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
-
+ when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
NotificationShelf notificationShelf = mock(NotificationShelf.class);
when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn(
@@ -208,7 +210,11 @@
mZenModeController,
mNotificationSectionsManager,
mock(ForegroundServiceSectionController.class),
- mock(ForegroundServiceDismissalFeatureController.class)
+ mock(ForegroundServiceDismissalFeatureController.class),
+ mFeatureFlags,
+ mock(NotifPipeline.class),
+ mEntryManager,
+ mock(NotifCollection.class)
);
verify(mLockscreenUserManager).addUserChangedListener(userChangedCaptor.capture());
mUserChangedListener = userChangedCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
index 40d3395..7d52df7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerTest.java
@@ -34,7 +34,6 @@
import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -52,14 +51,13 @@
@Mock private WindowManager mWindowManager;
@Mock private DozeParameters mDozeParameters;
- @Mock private NotificationShadeWindowView mStatusBarView;
+ @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private IActivityManager mActivityManager;
@Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private ConfigurationController mConfigurationController;
@Mock private KeyguardBypassController mKeyguardBypassController;
@Mock private SysuiColorExtractor mColorExtractor;
@Mock ColorExtractor.GradientColors mGradientColors;
- @Mock private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -68,13 +66,11 @@
MockitoAnnotations.initMocks(this);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
- when(mSuperStatusBarViewFactory.getNotificationShadeWindowView())
- .thenReturn(mStatusBarView);
mNotificationShadeWindowController = new NotificationShadeWindowController(mContext,
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController, mColorExtractor,
- mSuperStatusBarViewFactory);
+ mConfigurationController, mKeyguardBypassController, mColorExtractor);
+ mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowController.attach();
}
@@ -104,7 +100,7 @@
@Test
public void testAdd_updatesVisibilityFlags() {
- verify(mStatusBarView).setSystemUiVisibility(anyInt());
+ verify(mNotificationShadeWindowView).setSystemUiVisibility(anyInt());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 5027610..1e4df27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -58,6 +58,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -67,6 +68,8 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -114,6 +117,12 @@
private BubbleController mBubbleController;
@Mock
private ShadeControllerImpl mShadeController;
+ @Mock
+ private FeatureFlags mFeatureFlags;
+ @Mock
+ private NotifPipeline mNotifPipeline;
+ @Mock
+ private NotifCollection mNotifCollection;
@Mock
private ActivityIntentHelper mActivityIntentHelper;
@@ -162,6 +171,7 @@
mActiveNotifications.add(mBubbleNotificationRow.getEntry());
when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+ when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder(
getContext(), mock(CommandQueue.class), () -> mAssistManager,
@@ -175,11 +185,12 @@
mKeyguardStateController,
mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
- mActivityIntentHelper, mBubbleController, mShadeController))
+ mActivityIntentHelper, mBubbleController, mShadeController, mFeatureFlags,
+ mNotifPipeline, mNotifCollection)
.setStatusBar(mStatusBar)
.setNotificationPanelViewController(mock(NotificationPanelViewController.class))
.setNotificationPresenter(mock(NotificationPresenter.class))
- .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
+ .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class)))
.build();
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
new file mode 100644
index 0000000..d58f2c9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.toast;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.ITransientNotificationCallback;
+import android.os.Binder;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.CommandQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ToastUITest extends SysuiTestCase {
+ private static final String PACKAGE_NAME_1 = "com.example1.test";
+ private static final Binder TOKEN_1 = new Binder();
+ private static final Binder WINDOW_TOKEN_1 = new Binder();
+ private static final String PACKAGE_NAME_2 = "com.example2.test";
+ private static final Binder TOKEN_2 = new Binder();
+ private static final Binder WINDOW_TOKEN_2 = new Binder();
+ private static final String TEXT = "Hello World";
+ private static final int MESSAGE_RES_ID = R.id.message;
+
+ @Mock private CommandQueue mCommandQueue;
+ @Mock private WindowManager mWindowManager;
+ @Mock private INotificationManager mNotificationManager;
+ @Mock private AccessibilityManager mAccessibilityManager;
+ @Mock private ITransientNotificationCallback mCallback;
+ @Captor private ArgumentCaptor<View> mViewCaptor;
+ @Captor private ArgumentCaptor<ViewGroup.LayoutParams> mParamsCaptor;
+ private ToastUI mToastUI;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mToastUI = new ToastUI(mContext, mCommandQueue, mWindowManager, mNotificationManager,
+ mAccessibilityManager);
+ }
+
+ @Test
+ public void testStart_addToastUIAsCallbackToCommandQueue() throws Exception {
+ mToastUI.start();
+
+ verify(mCommandQueue).addCallback(mToastUI);
+ }
+
+ @Test
+ public void testShowToast_addsCorrectViewToWindowManager() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+ verify(mWindowManager).addView(mViewCaptor.capture(), any());
+ View view = mViewCaptor.getValue();
+ assertThat(((TextView) view.findViewById(MESSAGE_RES_ID)).getText()).isEqualTo(TEXT);
+ }
+
+ @Test
+ public void testShowToast_addsViewWithCorrectLayoutParamsToWindowManager() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+ verify(mWindowManager).addView(any(), mParamsCaptor.capture());
+ ViewGroup.LayoutParams params = mParamsCaptor.getValue();
+ assertThat(params).isInstanceOf(WindowManager.LayoutParams.class);
+ WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
+ assertThat(windowParams.packageName).isEqualTo(mContext.getPackageName());
+ assertThat(windowParams.getTitle()).isEqualTo("Toast");
+ assertThat(windowParams.token).isEqualTo(WINDOW_TOKEN_1);
+ }
+
+ @Test
+ public void testShowToast_callsCallback() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ verify(mCallback).onToastShown();
+ }
+
+ @Test
+ public void testShowToast_sendsAccessibilityEvent() throws Exception {
+ when(mAccessibilityManager.isEnabled()).thenReturn(true);
+
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, null);
+
+ ArgumentCaptor<AccessibilityEvent> eventCaptor = ArgumentCaptor.forClass(
+ AccessibilityEvent.class);
+ verify(mAccessibilityManager).sendAccessibilityEvent(eventCaptor.capture());
+ AccessibilityEvent event = eventCaptor.getValue();
+ assertThat(event.getEventType()).isEqualTo(
+ AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
+ assertThat(event.getClassName()).isEqualTo(Toast.class.getName());
+ assertThat(event.getPackageName()).isEqualTo(PACKAGE_NAME_1);
+ }
+
+ @Test
+ public void testHideToast_removesView() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+ View view = verifyWmAddViewAndAttachToParent();
+
+ mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+ verify(mWindowManager).removeViewImmediate(view);
+ }
+
+ @Test
+ public void testHideToast_finishesToken() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+ verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+ }
+
+ @Test
+ public void testHideToast_callsCallback() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+
+ verify(mCallback).onToastHidden();
+ }
+
+ @Test
+ public void testHideToast_whenNotCurrentToastToken_doesNotHideToast() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_2);
+
+ verify(mCallback, never()).onToastHidden();
+ }
+
+ @Test
+ public void testHideToast_whenNotCurrentToastPackage_doesNotHideToast() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ mToastUI.hideToast(PACKAGE_NAME_2, TOKEN_1);
+
+ verify(mCallback, never()).onToastHidden();
+ }
+
+ @Test
+ public void testShowToast_afterShowToast_hidesCurrentToast() throws Exception {
+ mToastUI.showToast(PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+ View view = verifyWmAddViewAndAttachToParent();
+
+ mToastUI.showToast(PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, null);
+
+ verify(mWindowManager).removeViewImmediate(view);
+ verify(mNotificationManager).finishToken(PACKAGE_NAME_1, WINDOW_TOKEN_1);
+ verify(mCallback).onToastHidden();
+ }
+
+ private View verifyWmAddViewAndAttachToParent() {
+ ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class);
+ verify(mWindowManager).addView(viewCaptor.capture(), any());
+ View view = viewCaptor.getValue();
+ // Simulate attaching to view hierarchy
+ ViewGroup parent = new FrameLayout(mContext);
+ parent.addView(view);
+ return view;
+ }
+}
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
index ab21a76..351e92c 100644
--- a/packages/services/PacProcessor/jni/Android.bp
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -37,7 +37,8 @@
"-Wunused",
"-Wunreachable-code",
],
- sanitize: {
- cfi: true,
- },
+ // Re-enable when b/145990493 is fixed
+ // sanitize: {
+ // cfi: true,
+ // },
}
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
index 89c4665..4dd00f1 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
@@ -19,8 +19,6 @@
import android.util.Log;
import android.webkit.PacProcessor;
-import com.android.internal.annotations.GuardedBy;
-
/**
* @hide
*/
@@ -28,10 +26,6 @@
private static final String TAG = "PacWebView";
private static final PacWebView sInstance = new PacWebView();
-
- private Object mLock = new Object();
-
- @GuardedBy("mLock")
private PacProcessor mProcessor = PacProcessor.getInstance();
public static PacWebView getInstance() {
@@ -39,20 +33,16 @@
}
@Override
- public boolean setCurrentProxyScript(String script) {
- synchronized (mLock) {
- if (!mProcessor.setProxyScript(script)) {
- Log.e(TAG, "Unable to parse proxy script.");
- return false;
- }
- return true;
+ public synchronized boolean setCurrentProxyScript(String script) {
+ if (!mProcessor.setProxyScript(script)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ return false;
}
+ return true;
}
@Override
- public String makeProxyRequest(String url, String host) {
- synchronized (mLock) {
- return mProcessor.findProxyForUrl(url);
- }
+ public synchronized String makeProxyRequest(String url, String host) {
+ return mProcessor.findProxyForUrl(url);
}
}
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 5c2cbfa..442c9e5 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -220,9 +220,13 @@
* result.
* @param hideShortcutInvocationEvents whether the {@link UsageEvents.Event#SHORTCUT_INVOCATION}
* events need to be excluded from the result.
+ * @param hideLocusIdEvents whether the {@link UsageEvents.Event#LOCUS_ID_SET}
+ * events need to be excluded from the result.
+ *
*/
public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime,
- long endTime, boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents);
+ long endTime, boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents,
+ boolean hideLocusIdEvents);
/**
* Used to persist the last time a job was run for this app, in order to make decisions later
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index c47cde3..b334b26 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3749,6 +3749,7 @@
if (nm == null) return;
if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
+ checkNetworkStackPermission();
nm.forceReevaluation(Binder.getCallingUid());
}
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index c987620..9540f43 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -1556,16 +1556,16 @@
}
Objects.requireNonNull(callingPackage, "Null calling package cannot create IpSec tunnels");
- switch (getAppOpsManager().noteOp(TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
- case AppOpsManager.MODE_DEFAULT:
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
- break;
- case AppOpsManager.MODE_ALLOWED:
- return;
- default:
- throw new SecurityException("Request to ignore AppOps for non-legacy API");
+
+ // OP_MANAGE_IPSEC_TUNNELS will return MODE_ERRORED by default, including for the system
+ // server. If the appop is not granted, require that the caller has the MANAGE_IPSEC_TUNNELS
+ // permission or is the System Server.
+ if (AppOpsManager.MODE_ALLOWED == getAppOpsManager().noteOpNoThrow(
+ TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
+ return;
}
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
}
private void createOrUpdateTransform(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c9ba988..b5b22f1 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1300,13 +1300,6 @@
vol.state = newState;
onVolumeStateChangedLocked(vol, oldState, newState);
}
- try {
- if (vol.type == VolumeInfo.TYPE_PRIVATE && state == VolumeInfo.STATE_MOUNTED) {
- mInstaller.onPrivateVolumeMounted(vol.getFsUuid());
- }
- } catch (Installer.InstallerException e) {
- Slog.i(TAG, "Failed when private volume mounted " + vol, e);
- }
}
}
@@ -3110,6 +3103,15 @@
try {
mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+ // After preparing user storage, we should check if we should mount data mirror again,
+ // and we do it for user 0 only as we only need to do once for all users.
+ if (volumeUuid != null) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ VolumeInfo info = storage.findVolumeByUuid(volumeUuid);
+ if (info != null && userId == 0 && info.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.tryMountDataMirror(volumeUuid);
+ }
+ }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3271,7 +3273,7 @@
+ " does not match calling user id " + userId);
}
try {
- mVold.setupAppDir(appPath, matcher.group(1), callingUid);
+ mVold.setupAppDir(appPath, callingUid);
} catch (RemoteException e) {
throw new IllegalStateException("Failed to prepare " + appPath + ": " + e);
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 0e5a6bb..f85fc28 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -61,6 +61,7 @@
import android.telephony.CellSignalStrengthWcdma;
import android.telephony.DataFailCause;
import android.telephony.DisconnectCause;
+import android.telephony.DisplayInfo;
import android.telephony.LocationAccessPolicy;
import android.telephony.PhoneCapability;
import android.telephony.PhoneStateListener;
@@ -205,6 +206,8 @@
private boolean[] mUserMobileDataState;
+ private DisplayInfo[] mDisplayInfos;
+
private SignalStrength[] mSignalStrength;
private boolean[] mMessageWaiting;
@@ -284,7 +287,8 @@
static final int ENFORCE_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
| PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
- | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST;
+ | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST
+ | PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED;
static final int ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_PRECISE_CALL_STATE
@@ -443,6 +447,7 @@
mCallAttributes = copyOf(mCallAttributes, mNumPhones);
mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
+ mDisplayInfos = copyOf(mDisplayInfos, mNumPhones);
// ds -> ss switch.
if (mNumPhones < oldNumPhones) {
@@ -482,6 +487,7 @@
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
mBarringInfo.add(i, new BarringInfo());
+ mDisplayInfos[i] = null;
}
}
@@ -540,6 +546,7 @@
mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
mBarringInfo = new ArrayList<>();
+ mDisplayInfos = new DisplayInfo[numPhones];
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
@@ -568,6 +575,7 @@
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
mBarringInfo.add(i, new BarringInfo());
+ mDisplayInfos[i] = null;
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -978,6 +986,15 @@
remove(r.binder);
}
}
+ if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
+ try {
+ if (mDisplayInfos[phoneId] != null) {
+ r.callback.onDisplayInfoChanged(mDisplayInfos[phoneId]);
+ }
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
if ((events & PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST) != 0) {
try {
r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
@@ -1501,6 +1518,45 @@
}
}
+ /**
+ * Notify display network info changed.
+ *
+ * @param phoneId Phone id
+ * @param subId Subscription id
+ * @param displayInfo Display network info
+ *
+ * @see PhoneStateListener#onDisplayInfoChanged(DisplayInfo)
+ */
+ public void notifyDisplayInfoChanged(int phoneId, int subId,
+ @NonNull DisplayInfo displayInfo) {
+ if (!checkNotifyPermission("notifyDisplayInfoChanged()")) {
+ return;
+ }
+ if (VDBG) {
+ log("notifyDisplayInfoChanged: PhoneId=" + phoneId
+ + " subId=" + subId + " displayInfo=" + displayInfo);
+ }
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ if (mDisplayInfos[phoneId] != null) {
+ mDisplayInfos[phoneId] = displayInfo;
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onDisplayInfoChanged(displayInfo);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
public void notifyCallForwardingChanged(boolean cfi) {
notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi);
}
@@ -2730,6 +2786,20 @@
}
}
+ if ((events & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0) {
+ try {
+ if (VDBG) {
+ log("checkPossibleMissNotify: onDisplayInfoChanged phoneId="
+ + phoneId + " dpi=" + mDisplayInfos[phoneId]);
+ }
+ if (mDisplayInfos[phoneId] != null) {
+ r.callback.onDisplayInfoChanged(mDisplayInfos[phoneId]);
+ }
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+
if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
try {
if (VDBG) {
diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java
new file mode 100644
index 0000000..74f113f
--- /dev/null
+++ b/services/core/java/com/android/server/UserspaceRebootLogger.java
@@ -0,0 +1,136 @@
+/*
+ * 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;
+
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED;
+
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class to help abstract logging {@code UserspaceRebootReported} atom.
+ */
+public final class UserspaceRebootLogger {
+
+ private static final String TAG = "UserspaceRebootLogger";
+
+ private static final String USERSPACE_REBOOT_SHOULD_LOG_PROPERTY =
+ "persist.sys.userspace_reboot.log.should_log";
+ private static final String USERSPACE_REBOOT_LAST_STARTED_PROPERTY =
+ "sys.userspace_reboot.log.last_started";
+ private static final String USERSPACE_REBOOT_LAST_FINISHED_PROPERTY =
+ "sys.userspace_reboot.log.last_finished";
+ private static final String BOOT_REASON_PROPERTY = "sys.boot.reason";
+
+ private UserspaceRebootLogger() {}
+
+ /**
+ * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be
+ * logged on the next successful boot.
+ */
+ public static void noteUserspaceRebootWasRequested() {
+ SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1");
+ SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY,
+ String.valueOf(SystemClock.elapsedRealtime()));
+ }
+
+ /**
+ * Updates internal state on boot after successful userspace reboot.
+ *
+ * <p>Should be called right before framework sets {@code sys.boot_completed} property.
+ */
+ public static void noteUserspaceRebootSuccess() {
+ SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY,
+ String.valueOf(SystemClock.elapsedRealtime()));
+ }
+
+ /**
+ * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged.
+ */
+ public static boolean shouldLogUserspaceRebootEvent() {
+ return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false);
+ }
+
+ /**
+ * Asynchronously logs {@code UserspaceRebootReported} on the given {@code executor}.
+ *
+ * <p>Should be called in the end of {@link
+ * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have
+ * tried to proactivelly unlock storage of the primary user.
+ */
+ public static void logEventAsync(boolean userUnlocked, Executor executor) {
+ final int outcome = computeOutcome();
+ final long durationMillis;
+ if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) {
+ durationMillis = SystemProperties.getLong(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, 0)
+ - SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, 0);
+ } else {
+ durationMillis = 0;
+ }
+ final int encryptionState =
+ userUnlocked
+ ? USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED
+ : USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+ executor.execute(
+ () -> {
+ Slog.i(TAG, "Logging UserspaceRebootReported atom: { outcome: " + outcome
+ + " durationMillis: " + durationMillis + " encryptionState: "
+ + encryptionState + " }");
+ FrameworkStatsLog.write(FrameworkStatsLog.USERSPACE_REBOOT_REPORTED, outcome,
+ durationMillis, encryptionState);
+ SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "");
+ });
+ }
+
+ private static int computeOutcome() {
+ if (SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, -1) != -1) {
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+ }
+ String reason = SystemProperties.get(BOOT_REASON_PROPERTY, "");
+ if (reason.startsWith("reboot,")) {
+ reason = reason.substring("reboot".length());
+ }
+ switch (reason) {
+ case "userspace_failed,watchdog_fork":
+ // Since fork happens before shutdown sequence, attribute it to
+ // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED.
+ case "userspace_failed,shutdown_aborted":
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+ case "userspace_failed,init_user0_failed":
+ // init_user0 will fail if userdata wasn't remounted correctly, attribute to
+ // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT.
+ case "mount_userdata_failed":
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+ case "userspace_failed,watchdog_triggered":
+ return
+ USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+ default:
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0852458..ad550c5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -278,6 +278,7 @@
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.server.ServerProtoEnums;
+import android.sysprop.InitProperties;
import android.sysprop.VoldProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -347,6 +348,7 @@
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
+import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
import com.android.server.appop.AppOpsService;
@@ -2282,6 +2284,20 @@
}
}
+ private void maybeLogUserspaceRebootEvent() {
+ if (!UserspaceRebootLogger.shouldLogUserspaceRebootEvent()) {
+ return;
+ }
+ final int userId = mUserController.getCurrentUserId();
+ if (userId != UserHandle.USER_SYSTEM) {
+ // Only log for user0.
+ return;
+ }
+ // TODO(b/148767783): should we check all profiles under user0?
+ UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+ BackgroundThread.getExecutor());
+ }
+
/**
* Encapsulates global settings related to hidden API enforcement behaviour, including tracking
* the latest value via a content observer.
@@ -5361,6 +5377,12 @@
// Start looking for apps that are abusing wake locks.
Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+ // Check if we are performing userspace reboot before setting sys.boot_completed to
+ // avoid race with init reseting sys.init.userspace_reboot.in_progress once sys
+ // .boot_completed is 1.
+ if (InitProperties.userspace_reboot_in_progress().orElse(false)) {
+ UserspaceRebootLogger.noteUserspaceRebootSuccess();
+ }
// Tell anyone interested that we are done booting!
SystemProperties.set("sys.boot_completed", "1");
@@ -5381,6 +5403,7 @@
}
}
});
+ maybeLogUserspaceRebootEvent();
mUserController.scheduleStartProfiles();
}
// UART is on if init's console service is running, send a warning notification.
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 06561f5..3ffe1be 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -56,7 +56,6 @@
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
-import static android.os.Process.STATSD_UID;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
@@ -411,9 +410,9 @@
Slog.e(TAG, "Bad app ops settings", e);
}
TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_TOP_STATE_SETTLE_TIME, 30 * 1000L);
+ KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_FG_SERVICE_STATE_SETTLE_TIME, 10 * 1000L);
+ KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
}
@@ -1890,9 +1889,9 @@
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
- boolean isCallerStatsCollector = Binder.getCallingUid() == STATSD_UID;
+ boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
- if (!isCallerStatsCollector && !isCallerInstrumented) {
+ if (!isCallerSystem && !isCallerInstrumented) {
mHandler.post(() -> callback.sendResult(new Bundle()));
return;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 97a8b87..67d7530 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -128,6 +128,7 @@
import android.util.MathUtils;
import android.util.PrintWriterPrinter;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
@@ -247,6 +248,7 @@
// AudioHandler messages
private static final int MSG_SET_DEVICE_VOLUME = 0;
private static final int MSG_PERSIST_VOLUME = 1;
+ private static final int MSG_PERSIST_VOLUME_GROUP = 2;
private static final int MSG_PERSIST_RINGER_MODE = 3;
private static final int MSG_AUDIO_SERVER_DIED = 4;
private static final int MSG_PLAY_SOUND_EFFECT = 5;
@@ -780,6 +782,10 @@
mSettingsObserver = new SettingsObserver();
createStreamStates();
+ // must be called after createStreamStates() as it uses MUSIC volume as default if no
+ // persistent data
+ initVolumeGroupStates();
+
// mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
// relies on audio policy having correct ranges for volume indexes.
mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
@@ -1018,6 +1024,9 @@
streamState.applyAllVolumes();
}
+ // Restore audio volume groups
+ restoreVolumeGroups();
+
// Restore mono mode
updateMasterMono(mContentResolver);
@@ -2288,20 +2297,20 @@
String callingPackage) {
enforceModifyAudioRoutingPermission();
Objects.requireNonNull(attr, "attr must not be null");
- // @todo not hold the caller context, post message
- int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
- final int device = getDeviceForStream(stream);
-
- int oldIndex = AudioSystem.getVolumeIndexForAttributes(attr, device);
-
- AudioSystem.setVolumeIndexForAttributes(attr, index, device);
-
final int volumeGroup = getVolumeGroupIdForAttributes(attr);
- final AudioVolumeGroup avg = getAudioVolumeGroupById(volumeGroup);
- if (avg == null) {
+ if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+ Log.e(TAG, ": no volume group found for attributes " + attr.toString());
return;
}
- for (final int groupedStream : avg.getLegacyStreamTypes()) {
+ final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
+ index/*val1*/, flags/*val2*/, callingPackage));
+
+ vgs.setVolumeIndex(index, flags);
+
+ // For legacy reason, propagate to all streams associated to this volume group
+ for (final int groupedStream : vgs.getLegacyStreamTypes()) {
setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
Binder.getCallingUid());
}
@@ -2323,10 +2332,12 @@
public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
enforceModifyAudioRoutingPermission();
Objects.requireNonNull(attr, "attr must not be null");
- int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
- final int device = getDeviceForStream(stream);
-
- return AudioSystem.getVolumeIndexForAttributes(attr, device);
+ final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+ if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+ throw new IllegalArgumentException("No volume group for attributes " + attr);
+ }
+ final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+ return vgs.getVolumeIndex();
}
/** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
@@ -3754,6 +3765,8 @@
enforceSafeMediaVolume(TAG);
}
}
+
+ readVolumeGroupsSettings();
}
/** @see AudioManager#setSpeakerphoneOn(boolean) */
@@ -4654,6 +4667,310 @@
///////////////////////////////////////////////////////////////////////////
// Inner classes
///////////////////////////////////////////////////////////////////////////
+ /**
+ * Key is the AudioManager VolumeGroupId
+ * Value is the VolumeGroupState
+ */
+ private static final SparseArray<VolumeGroupState> sVolumeGroupStates = new SparseArray<>();
+
+ private void initVolumeGroupStates() {
+ for (final AudioVolumeGroup avg : getAudioVolumeGroups()) {
+ try {
+ // if no valid attributes, this volume group is not controllable, throw exception
+ ensureValidAttributes(avg);
+ } catch (IllegalArgumentException e) {
+ // Volume Groups without attributes are not controllable through set/get volume
+ // using attributes. Do not append them.
+ Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+ continue;
+ }
+ sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
+ }
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.applyAllVolumes();
+ }
+ }
+
+ private void ensureValidAttributes(AudioVolumeGroup avg) {
+ boolean hasAtLeastOneValidAudioAttributes = avg.getAudioAttributes().stream()
+ .anyMatch(aa -> !aa.equals(AudioProductStrategy.sDefaultAttributes));
+ if (!hasAtLeastOneValidAudioAttributes) {
+ throw new IllegalArgumentException("Volume Group " + avg.name()
+ + " has no valid audio attributes");
+ }
+ }
+
+ private void readVolumeGroupsSettings() {
+ Log.v(TAG, "readVolumeGroupsSettings");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.readSettings();
+ vgs.applyAllVolumes();
+ }
+ }
+
+ // Called upon crash of AudioServer
+ private void restoreVolumeGroups() {
+ Log.v(TAG, "restoreVolumeGroups");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.applyAllVolumes();
+ }
+ }
+
+ private void dumpVolumeGroups(PrintWriter pw) {
+ pw.println("\nVolume Groups (device: index)");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.dump(pw);
+ pw.println("");
+ }
+ }
+
+ // NOTE: Locking order for synchronized objects related to volume management:
+ // 1 mSettingsLock
+ // 2 VolumeGroupState.class
+ private class VolumeGroupState {
+ private final AudioVolumeGroup mAudioVolumeGroup;
+ private final SparseIntArray mIndexMap = new SparseIntArray(8);
+ private int mIndexMin;
+ private int mIndexMax;
+ private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT;
+ private int mPublicStreamType = AudioSystem.STREAM_MUSIC;
+ private AudioAttributes mAudioAttributes = AudioProductStrategy.sDefaultAttributes;
+
+ // No API in AudioSystem to get a device from strategy or from attributes.
+ // Need a valid public stream type to use current API getDeviceForStream
+ private int getDeviceForVolume() {
+ return getDeviceForStream(mPublicStreamType);
+ }
+
+ private VolumeGroupState(AudioVolumeGroup avg) {
+ mAudioVolumeGroup = avg;
+ Log.v(TAG, "VolumeGroupState for " + avg.toString());
+ for (final AudioAttributes aa : avg.getAudioAttributes()) {
+ if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
+ mAudioAttributes = aa;
+ break;
+ }
+ }
+ final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+ if (streamTypes.length != 0) {
+ // Uses already initialized MIN / MAX if a stream type is attached to group
+ mLegacyStreamType = streamTypes[0];
+ for (final int streamType : streamTypes) {
+ if (streamType != AudioSystem.STREAM_DEFAULT
+ && streamType < AudioSystem.getNumStreamTypes()) {
+ mPublicStreamType = streamType;
+ break;
+ }
+ }
+ mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
+ mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
+ } else if (!avg.getAudioAttributes().isEmpty()) {
+ mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
+ mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
+ } else {
+ Log.e(TAG, "volume group: " + mAudioVolumeGroup.name()
+ + " has neither valid attributes nor valid stream types assigned");
+ return;
+ }
+ // Load volume indexes from data base
+ readSettings();
+ }
+
+ public @NonNull int[] getLegacyStreamTypes() {
+ return mAudioVolumeGroup.getLegacyStreamTypes();
+ }
+
+ public String name() {
+ return mAudioVolumeGroup.name();
+ }
+
+ public int getVolumeIndex() {
+ return getIndex(getDeviceForVolume());
+ }
+
+ public void setVolumeIndex(int index, int flags) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ setVolumeIndex(index, getDeviceForVolume(), flags);
+ }
+
+ private void setVolumeIndex(int index, int device, int flags) {
+ // Set the volume index
+ setVolumeIndexInt(index, device, flags);
+
+ // Update local cache
+ mIndexMap.put(device, index);
+
+ // update data base - post a persist volume group msg
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_VOLUME_GROUP,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ this,
+ PERSIST_DELAY);
+ }
+
+ private void setVolumeIndexInt(int index, int device, int flags) {
+ // Set the volume index
+ AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
+ }
+
+ public int getIndex(int device) {
+ synchronized (VolumeGroupState.class) {
+ int index = mIndexMap.get(device, -1);
+ // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+ return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
+ }
+ }
+
+ public boolean hasIndexForDevice(int device) {
+ synchronized (VolumeGroupState.class) {
+ return (mIndexMap.get(device, -1) != -1);
+ }
+ }
+
+ public int getMaxIndex() {
+ return mIndexMax;
+ }
+
+ public int getMinIndex() {
+ return mIndexMin;
+ }
+
+ public void applyAllVolumes() {
+ synchronized (VolumeGroupState.class) {
+ if (mLegacyStreamType != AudioSystem.STREAM_DEFAULT) {
+ // No-op to avoid regression with stream based volume management
+ return;
+ }
+ // apply device specific volumes first
+ int index;
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ final int device = mIndexMap.keyAt(i);
+ if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
+ index = mIndexMap.valueAt(i);
+ Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ setVolumeIndexInt(index, device, 0 /*flags*/);
+ }
+ }
+ // apply default volume last: by convention , default device volume will be used
+ index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
+ Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group "
+ + mAudioVolumeGroup.name());
+ setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+ }
+ }
+
+ private void persistVolumeGroup(int device) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ boolean success = Settings.System.putIntForUser(mContentResolver,
+ getSettingNameForDevice(device),
+ getIndex(device),
+ UserHandle.USER_CURRENT);
+ if (!success) {
+ Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name());
+ }
+ }
+
+ public void readSettings() {
+ synchronized (VolumeGroupState.class) {
+ // First clear previously loaded (previous user?) settings
+ mIndexMap.clear();
+ // force maximum volume on all streams if fixed volume property is set
+ if (mUseFixedVolume) {
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+ return;
+ }
+ for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+ // retrieve current volume for device
+ // if no volume stored for current volume group and device, use default volume
+ // if default device, continue otherwise
+ int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT)
+ ? AudioSystem.DEFAULT_STREAM_VOLUME[mPublicStreamType] : -1;
+ int index;
+ String name = getSettingNameForDevice(device);
+ index = Settings.System.getIntForUser(
+ mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+ if (index == -1) {
+ Log.e(TAG, "readSettings: No index stored for group "
+ + mAudioVolumeGroup.name() + ", device " + name);
+ continue;
+ }
+ Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
+ + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+ mIndexMap.put(device, getValidIndex(index));
+ }
+ }
+ }
+
+ private int getValidIndex(int index) {
+ if (index < mIndexMin) {
+ return mIndexMin;
+ } else if (mUseFixedVolume || index > mIndexMax) {
+ return mIndexMax;
+ }
+ return index;
+ }
+
+ public @NonNull String getSettingNameForDevice(int device) {
+ final String suffix = AudioSystem.getOutputDeviceName(device);
+ if (suffix.isEmpty()) {
+ return mAudioVolumeGroup.name();
+ }
+ return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device);
+ }
+
+ private void dump(PrintWriter pw) {
+ pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":");
+ pw.print(" Min: ");
+ pw.println(mIndexMin);
+ pw.print(" Max: ");
+ pw.println(mIndexMax);
+ pw.print(" Current: ");
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ if (i > 0) {
+ pw.print(", ");
+ }
+ final int device = mIndexMap.keyAt(i);
+ pw.print(Integer.toHexString(device));
+ final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+ : AudioSystem.getOutputDeviceName(device);
+ if (!deviceName.isEmpty()) {
+ pw.print(" (");
+ pw.print(deviceName);
+ pw.print(")");
+ }
+ pw.print(": ");
+ pw.print(mIndexMap.valueAt(i));
+ }
+ pw.println();
+ pw.print(" Devices: ");
+ int n = 0;
+ final int devices = getDeviceForVolume();
+ for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+ if ((devices & device) == device) {
+ if (n++ > 0) {
+ pw.print(", ");
+ }
+ pw.print(AudioSystem.getOutputDeviceName(device));
+ }
+ }
+ }
+ }
+
// NOTE: Locking order for synchronized objects related to volume or ringer mode management:
// 1 mScoclient OR mSafeMediaVolumeState
@@ -5298,6 +5615,11 @@
persistVolume((VolumeStreamState) msg.obj, msg.arg1);
break;
+ case MSG_PERSIST_VOLUME_GROUP:
+ final VolumeGroupState vgs = (VolumeGroupState) msg.obj;
+ vgs.persistVolumeGroup(msg.arg1);
+ break;
+
case MSG_PERSIST_RINGER_MODE:
// note that the value persisted is the current ringer mode, not the
// value of ringer mode as of the time the request was made to persist
@@ -6395,6 +6717,7 @@
}
mMediaFocusControl.dump(pw);
dumpStreamStates(pw);
+ dumpVolumeGroups(pw);
dumpRingerMode(pw);
pw.println("\nAudio routes:");
pw.print(" mMainType=0x"); pw.println(Integer.toHexString(
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index fcd8701..add620e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -16,6 +16,7 @@
package com.android.server.audio;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -97,12 +98,15 @@
static final int VOL_ADJUST_VOL_UID = 5;
static final int VOL_VOICE_ACTIVITY_HEARING_AID = 6;
static final int VOL_MODE_CHANGE_HEARING_AID = 7;
+ static final int VOL_SET_GROUP_VOL = 8;
final int mOp;
final int mStream;
final int mVal1;
final int mVal2;
final String mCaller;
+ final String mGroupName;
+ final AudioAttributes mAudioAttributes;
/** used for VOL_ADJUST_VOL_UID,
* VOL_ADJUST_SUGG_VOL,
@@ -114,6 +118,8 @@
mVal1 = val1;
mVal2 = val2;
mCaller = caller;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_SET_HEARING_AID_VOL*/
@@ -124,6 +130,8 @@
// unused
mStream = -1;
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_SET_AVRCP_VOL */
@@ -134,6 +142,8 @@
mVal2 = 0;
mStream = -1;
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_VOICE_ACTIVITY_HEARING_AID */
@@ -144,6 +154,8 @@
mVal2 = voiceActive ? 1 : 0;
// unused
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_MODE_CHANGE_HEARING_AID */
@@ -154,6 +166,19 @@
mVal2 = mode;
// unused
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
+ }
+
+ /** used for VOL_SET_GROUP_VOL */
+ VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) {
+ mOp = op;
+ mStream = -1;
+ mVal1 = index;
+ mVal2 = flags;
+ mCaller = caller;
+ mGroupName = group;
+ mAudioAttributes = aa;
}
@Override
@@ -208,6 +233,14 @@
.append(") causes setting HEARING_AID volume to idx:").append(mVal1)
.append(" stream:").append(AudioSystem.streamToString(mStream))
.toString();
+ case VOL_SET_GROUP_VOL:
+ return new StringBuilder("setVolumeIndexForAttributes(attr:")
+ .append(mAudioAttributes.toString())
+ .append(" group: ").append(mGroupName)
+ .append(" index:").append(mVal1)
+ .append(" flags:0x").append(Integer.toHexString(mVal2))
+ .append(") from ").append(mCaller)
+ .toString();
default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index df1b899..3f6e88d 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -782,6 +782,8 @@
}
mAuthenticators.add(new AuthenticatorWrapper(id, modality, strength, authenticator));
+
+ mBiometricStrengthController.updateStrengths();
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/biometrics/BiometricStrengthController.java b/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
index 4e16189..ca7ca5b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
+++ b/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
@@ -29,7 +29,7 @@
* Class for maintaining and updating the strengths for biometric sensors. Strengths can only
* be downgraded from the device's default, and never upgraded.
*/
-public class BiometricStrengthController implements DeviceConfig.OnPropertiesChangedListener {
+public class BiometricStrengthController {
private static final String TAG = "BiometricStrengthController";
private final BiometricService mService;
@@ -41,7 +41,7 @@
* "id1:strength1,id2:strength2,id3:strength3"
*
* where strength is one of the values defined in
- * {@link android.hardware.biometrics.Authenticators}
+ * {@link android.hardware.biometrics.BiometricManager.Authenticators}
*
* Both id and strength should be int, otherwise Exception will be thrown when parsing and the
* downgrade will fail.
@@ -53,30 +53,28 @@
*/
public static final String DEFAULT_BIOMETRIC_STRENGTHS = null;
- BiometricStrengthController(@NonNull BiometricService service) {
- mService = service;
- }
-
- void startListening() {
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BIOMETRICS,
- BackgroundThread.getExecutor(), this);
- updateStrengths();
- }
-
- @Override
- public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener = properties -> {
for (String name : properties.getKeyset()) {
if (KEY_BIOMETRIC_STRENGTHS.equals(name)) {
updateStrengths();
}
}
+ };
+
+ public BiometricStrengthController(@NonNull BiometricService service) {
+ mService = service;
+ }
+
+ public void startListening() {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BIOMETRICS,
+ BackgroundThread.getExecutor(), mDeviceConfigListener);
}
/**
* Updates the strengths of authenticators in BiometricService if a matching ID's configuration
* has been changed.
*/
- private void updateStrengths() {
+ public void updateStrengths() {
final Map<Integer, Integer> idToStrength = getIdToStrengthMap();
if (idToStrength == null) {
return;
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index 2eec419..8687f35 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -17,6 +17,7 @@
package com.android.server.compat;
import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.pm.ApplicationInfo;
@@ -39,6 +40,13 @@
public final class CompatChange extends CompatibilityChangeInfo {
/**
+ * A change ID to be used only in the CTS test for this SystemApi
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = 1234) // Needs to be > test APK targetSdkVersion.
+ private static final long CTS_SYSTEM_API_CHANGEID = 149391281; // This is a bug id.
+
+ /**
* Callback listener for when compat changes are updated for a package.
* See {@link #registerListener(ChangeListener)} for more details.
*/
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index cb88c4e..1a68f1b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -48,8 +48,12 @@
import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
import android.net.INetworkManagementEventObserver;
+import android.net.Ikev2VpnProfile;
import android.net.IpPrefix;
import android.net.IpSecManager;
+import android.net.IpSecManager.IpSecTunnelInterface;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalSocket;
@@ -65,6 +69,12 @@
import android.net.UidRange;
import android.net.VpnManager;
import android.net.VpnService;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionParams;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -113,6 +123,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -122,6 +133,9 @@
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -176,14 +190,14 @@
private final Context mContext;
private final NetworkInfo mNetworkInfo;
- private String mPackage;
+ @VisibleForTesting protected String mPackage;
private int mOwnerUID;
private boolean mIsPackageTargetingAtLeastQ;
private String mInterface;
private Connection mConnection;
/** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
- private VpnRunner mVpnRunner;
+ @VisibleForTesting protected VpnRunner mVpnRunner;
private PendingIntent mStatusIntent;
private volatile boolean mEnableTeardown = true;
@@ -196,6 +210,7 @@
@VisibleForTesting
protected final NetworkCapabilities mNetworkCapabilities;
private final SystemServices mSystemServices;
+ private final Ikev2SessionCreator mIkev2SessionCreator;
/**
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
@@ -238,17 +253,20 @@
public Vpn(Looper looper, Context context, INetworkManagementService netService,
@UserIdInt int userHandle) {
- this(looper, context, netService, userHandle, new SystemServices(context));
+ this(looper, context, netService, userHandle,
+ new SystemServices(context), new Ikev2SessionCreator());
}
@VisibleForTesting
protected Vpn(Looper looper, Context context, INetworkManagementService netService,
- int userHandle, SystemServices systemServices) {
+ int userHandle, SystemServices systemServices,
+ Ikev2SessionCreator ikev2SessionCreator) {
mContext = context;
mNetd = netService;
mUserHandle = userHandle;
mLooper = looper;
mSystemServices = systemServices;
+ mIkev2SessionCreator = ikev2SessionCreator;
mPackage = VpnConfig.LEGACY_VPN;
mOwnerUID = getAppUid(mPackage, mUserHandle);
@@ -749,8 +767,9 @@
private boolean isCurrentPreparedPackage(String packageName) {
// We can't just check that packageName matches mPackage, because if the app was uninstalled
- // and reinstalled it will no longer be prepared. Instead check the UID.
- return getAppUid(packageName, mUserHandle) == mOwnerUID;
+ // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
+ // calling package may not be the same as the prepared package. Check both UID and package.
+ return getAppUid(packageName, mUserHandle) == mOwnerUID && mPackage.equals(packageName);
}
/** Prepare the VPN for the given package. Does not perform permission checks. */
@@ -979,7 +998,11 @@
}
lp.setDomains(buffer.toString().trim());
- // TODO: Stop setting the MTU in jniCreate and set it here.
+ if (mConfig.mtu > 0) {
+ lp.setMtu(mConfig.mtu);
+ }
+
+ // TODO: Stop setting the MTU in jniCreate
return lp;
}
@@ -2004,30 +2027,369 @@
protected abstract void exit();
}
- private class IkeV2VpnRunner extends VpnRunner {
- private static final String TAG = "IkeV2VpnRunner";
+ interface IkeV2VpnRunnerCallback {
+ void onDefaultNetworkChanged(@NonNull Network network);
- private final IpSecManager mIpSecManager;
- private final VpnProfile mProfile;
+ void onChildOpened(
+ @NonNull Network network, @NonNull ChildSessionConfiguration childConfig);
- IkeV2VpnRunner(VpnProfile profile) {
+ void onChildTransformCreated(
+ @NonNull Network network, @NonNull IpSecTransform transform, int direction);
+
+ void onSessionLost(@NonNull Network network);
+ }
+
+ /**
+ * Internal class managing IKEv2/IPsec VPN connectivity
+ *
+ * <p>The IKEv2 VPN will listen to, and run based on the lifecycle of Android's default Network.
+ * As a new default is selected, old IKE sessions will be torn down, and a new one will be
+ * started.
+ *
+ * <p>This class uses locking minimally - the Vpn instance lock is only ever held when fields of
+ * the outer class are modified. As such, care must be taken to ensure that no calls are added
+ * that might modify the outer class' state without acquiring a lock.
+ *
+ * <p>The overall structure of the Ikev2VpnRunner is as follows:
+ *
+ * <ol>
+ * <li>Upon startup, a NetworkRequest is registered with ConnectivityManager. This is called
+ * any time a new default network is selected
+ * <li>When a new default is connected, an IKE session is started on that Network. If there
+ * were any existing IKE sessions on other Networks, they are torn down before starting
+ * the new IKE session
+ * <li>Upon establishment, the onChildTransformCreated() callback is called twice, one for
+ * each direction, and finally onChildOpened() is called
+ * <li>Upon the onChildOpened() call, the VPN is fully set up.
+ * <li>Subsequent Network changes result in new onDefaultNetworkChanged() callbacks. See (2).
+ * </ol>
+ */
+ class IkeV2VpnRunner extends VpnRunner implements IkeV2VpnRunnerCallback {
+ @NonNull private static final String TAG = "IkeV2VpnRunner";
+
+ @NonNull private final IpSecManager mIpSecManager;
+ @NonNull private final Ikev2VpnProfile mProfile;
+ @NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
+
+ /**
+ * Executor upon which ALL callbacks must be run.
+ *
+ * <p>This executor MUST be a single threaded executor, in order to ensure the consistency
+ * of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
+ * virtue of everything being serialized on this executor.
+ */
+ @NonNull private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+ /** Signal to ensure shutdown is honored even if a new Network is connected. */
+ private boolean mIsRunning = true;
+
+ @Nullable private UdpEncapsulationSocket mEncapSocket;
+ @Nullable private IpSecTunnelInterface mTunnelIface;
+ @Nullable private IkeSession mSession;
+ @Nullable private Network mActiveNetwork;
+
+ IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
super(TAG);
mProfile = profile;
-
- // TODO: move this to startVpnRunnerPrivileged()
- mConfig = new VpnConfig();
- mIpSecManager = mContext.getSystemService(IpSecManager.class);
+ mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this);
}
@Override
public void run() {
- // TODO: Build IKE config, start IKE session
+ // Explicitly use only the network that ConnectivityService thinks is the "best." In
+ // other words, only ever use the currently selected default network. This does mean
+ // that in both onLost() and onConnected(), any old sessions MUST be torn down. This
+ // does NOT include VPNs.
+ final ConnectivityManager cm = ConnectivityManager.from(mContext);
+ cm.requestNetwork(cm.getDefaultRequest(), mNetworkCallback);
+ }
+
+ private boolean isActiveNetwork(@Nullable Network network) {
+ return Objects.equals(mActiveNetwork, network) && mIsRunning;
+ }
+
+ /**
+ * Called when an IKE Child session has been opened, signalling completion of the startup.
+ *
+ * <p>This method is only ever called once per IkeSession, and MUST run on the mExecutor
+ * thread in order to ensure consistency of the Ikev2VpnRunner fields.
+ */
+ public void onChildOpened(
+ @NonNull Network network, @NonNull ChildSessionConfiguration childConfig) {
+ if (!isActiveNetwork(network)) {
+ Log.d(TAG, "onOpened called for obsolete network " + network);
+
+ // Do nothing; this signals that either: (1) a new/better Network was found,
+ // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+ // IKE session was already shut down (exited, or an error was encountered somewhere
+ // else). In both cases, all resources and sessions are torn down via
+ // resetIkeState().
+ return;
+ }
+
+ try {
+ final String interfaceName = mTunnelIface.getInterfaceName();
+ final int maxMtu = mProfile.getMaxMtu();
+ final List<LinkAddress> internalAddresses = childConfig.getInternalAddresses();
+
+ final Collection<RouteInfo> newRoutes = VpnIkev2Utils.getRoutesFromTrafficSelectors(
+ childConfig.getOutboundTrafficSelectors());
+ for (final LinkAddress address : internalAddresses) {
+ mTunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
+ }
+
+ final NetworkAgent networkAgent;
+ final LinkProperties lp;
+
+ synchronized (Vpn.this) {
+ mInterface = interfaceName;
+ mConfig.mtu = maxMtu;
+ mConfig.interfaze = mInterface;
+
+ mConfig.addresses.clear();
+ mConfig.addresses.addAll(internalAddresses);
+
+ mConfig.routes.clear();
+ mConfig.routes.addAll(newRoutes);
+
+ // TODO: Add DNS servers from negotiation
+
+ networkAgent = mNetworkAgent;
+
+ // The below must be done atomically with the mConfig update, otherwise
+ // isRunningLocked() will be racy.
+ if (networkAgent == null) {
+ if (isSettingsVpnLocked()) {
+ prepareStatusIntent();
+ }
+ agentConnect();
+ return; // Link properties are already sent.
+ }
+
+ lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
+ }
+
+ networkAgent.sendLinkProperties(lp);
+ } catch (Exception e) {
+ Log.d(TAG, "Error in ChildOpened for network " + network, e);
+ onSessionLost(network);
+ }
+ }
+
+ /**
+ * Called when an IPsec transform has been created, and should be applied.
+ *
+ * <p>This method is called multiple times over the lifetime of an IkeSession (or default
+ * network), and is MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ public void onChildTransformCreated(
+ @NonNull Network network, @NonNull IpSecTransform transform, int direction) {
+ if (!isActiveNetwork(network)) {
+ Log.d(TAG, "ChildTransformCreated for obsolete network " + network);
+
+ // Do nothing; this signals that either: (1) a new/better Network was found,
+ // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+ // IKE session was already shut down (exited, or an error was encountered somewhere
+ // else). In both cases, all resources and sessions are torn down via
+ // resetIkeState().
+ return;
+ }
+
+ try {
+ // Transforms do not need to be persisted; the IkeSession will keep
+ // them alive for us
+ mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
+ } catch (IOException e) {
+ Log.d(TAG, "Transform application failed for network " + network, e);
+ onSessionLost(network);
+ }
+ }
+
+ /**
+ * Called when a new default network is connected.
+ *
+ * <p>The Ikev2VpnRunner will unconditionally switch to the new network, killing the old IKE
+ * state in the process, and starting a new IkeSession instance.
+ *
+ * <p>This method is called multiple times over the lifetime of the Ikev2VpnRunner, and is
+ * called on the ConnectivityService thread. Thus, the actual work MUST be proxied to the
+ * mExecutor thread in order to ensure consistency of the Ikev2VpnRunner fields.
+ */
+ public void onDefaultNetworkChanged(@NonNull Network network) {
+ Log.d(TAG, "Starting IKEv2/IPsec session on new network: " + network);
+
+ // Proxy to the Ikev2VpnRunner (single-thread) executor to ensure consistency in lieu
+ // of locking.
+ mExecutor.execute(() -> {
+ try {
+ if (!mIsRunning) {
+ Log.d(TAG, "onDefaultNetworkChanged after exit");
+ return; // VPN has been shut down.
+ }
+
+ // Without MOBIKE, we have no way to seamlessly migrate. Close on old
+ // (non-default) network, and start the new one.
+ resetIkeState();
+ mActiveNetwork = network;
+
+ // TODO(b/149356682): Update this based on new IKE API
+ mEncapSocket = mIpSecManager.openUdpEncapsulationSocket();
+
+ // TODO(b/149356682): Update this based on new IKE API
+ final IkeSessionParams ikeSessionParams =
+ VpnIkev2Utils.buildIkeSessionParams(mProfile, mEncapSocket);
+ final ChildSessionParams childSessionParams =
+ VpnIkev2Utils.buildChildSessionParams();
+
+ // TODO: Remove the need for adding two unused addresses with
+ // IPsec tunnels.
+ mTunnelIface =
+ mIpSecManager.createIpSecTunnelInterface(
+ ikeSessionParams.getServerAddress() /* unused */,
+ ikeSessionParams.getServerAddress() /* unused */,
+ network);
+ mNetd.setInterfaceUp(mTunnelIface.getInterfaceName());
+
+ // Socket must be bound to prevent network switches from causing
+ // the IKE teardown to fail/timeout.
+ // TODO(b/149356682): Update this based on new IKE API
+ network.bindSocket(mEncapSocket.getFileDescriptor());
+
+ mSession = mIkev2SessionCreator.createIkeSession(
+ mContext,
+ ikeSessionParams,
+ childSessionParams,
+ mExecutor,
+ new VpnIkev2Utils.IkeSessionCallbackImpl(
+ TAG, IkeV2VpnRunner.this, network),
+ new VpnIkev2Utils.ChildSessionCallbackImpl(
+ TAG, IkeV2VpnRunner.this, network));
+ Log.d(TAG, "Ike Session started for network " + network);
+ } catch (Exception e) {
+ Log.i(TAG, "Setup failed for network " + network + ". Aborting", e);
+ onSessionLost(network);
+ }
+ });
+ }
+
+ /**
+ * Handles loss of a session
+ *
+ * <p>The loss of a session might be due to an onLost() call, the IKE session getting torn
+ * down for any reason, or an error in updating state (transform application, VPN setup)
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ public void onSessionLost(@NonNull Network network) {
+ if (!isActiveNetwork(network)) {
+ Log.d(TAG, "onSessionLost() called for obsolete network " + network);
+
+ // Do nothing; this signals that either: (1) a new/better Network was found,
+ // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
+ // IKE session was already shut down (exited, or an error was encountered somewhere
+ // else). In both cases, all resources and sessions are torn down via
+ // onSessionLost() and resetIkeState().
+ return;
+ }
+
+ mActiveNetwork = null;
+
+ // Close all obsolete state, but keep VPN alive incase a usable network comes up.
+ // (Mirrors VpnService behavior)
+ Log.d(TAG, "Resetting state for network: " + network);
+
+ synchronized (Vpn.this) {
+ // Since this method handles non-fatal errors only, set mInterface to null to
+ // prevent the NetworkManagementEventObserver from killing this VPN based on the
+ // interface going down (which we expect).
+ mInterface = null;
+ mConfig.interfaze = null;
+
+ // Set as unroutable to prevent traffic leaking while the interface is down.
+ if (mConfig != null && mConfig.routes != null) {
+ final List<RouteInfo> oldRoutes = new ArrayList<>(mConfig.routes);
+
+ mConfig.routes.clear();
+ for (final RouteInfo route : oldRoutes) {
+ mConfig.routes.add(new RouteInfo(route.getDestination(), RTN_UNREACHABLE));
+ }
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendLinkProperties(makeLinkProperties());
+ }
+ }
+ }
+
+ resetIkeState();
+ }
+
+ /**
+ * Cleans up all IKE state
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ private void resetIkeState() {
+ if (mTunnelIface != null) {
+ // No need to call setInterfaceDown(); the IpSecInterface is being fully torn down.
+ mTunnelIface.close();
+ mTunnelIface = null;
+ }
+ if (mSession != null) {
+ mSession.kill(); // Kill here to make sure all resources are released immediately
+ mSession = null;
+ }
+
+ // TODO(b/149356682): Update this based on new IKE API
+ if (mEncapSocket != null) {
+ try {
+ mEncapSocket.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close encap socket", e);
+ }
+ mEncapSocket = null;
+ }
+ }
+
+ /**
+ * Triggers cleanup of outer class' state
+ *
+ * <p>Can be called from any thread, as it does not mutate state in the Ikev2VpnRunner.
+ */
+ private void cleanupVpnState() {
+ synchronized (Vpn.this) {
+ agentDisconnect();
+ }
+ }
+
+ /**
+ * Cleans up all Ikev2VpnRunner internal state
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ private void shutdownVpnRunner() {
+ mActiveNetwork = null;
+ mIsRunning = false;
+
+ resetIkeState();
+
+ final ConnectivityManager cm = ConnectivityManager.from(mContext);
+ cm.unregisterNetworkCallback(mNetworkCallback);
+
+ mExecutor.shutdown();
}
@Override
public void exit() {
- // TODO: Teardown IKE session & any resources.
- agentDisconnect();
+ // Cleanup outer class' state immediately, otherwise race conditions may ensue.
+ cleanupVpnState();
+
+ mExecutor.execute(() -> {
+ shutdownVpnRunner();
+ });
}
}
@@ -2488,12 +2850,46 @@
throw new IllegalArgumentException("No profile found for " + packageName);
}
- startVpnProfilePrivileged(profile);
+ startVpnProfilePrivileged(profile, packageName);
});
}
- private void startVpnProfilePrivileged(@NonNull VpnProfile profile) {
- // TODO: Start PlatformVpnRunner
+ private void startVpnProfilePrivileged(
+ @NonNull VpnProfile profile, @NonNull String packageName) {
+ // Ensure that no other previous instance is running.
+ if (mVpnRunner != null) {
+ mVpnRunner.exit();
+ mVpnRunner = null;
+ }
+ updateState(DetailedState.CONNECTING, "startPlatformVpn");
+
+ try {
+ // Build basic config
+ mConfig = new VpnConfig();
+ mConfig.user = packageName;
+ mConfig.isMetered = profile.isMetered;
+ mConfig.startTime = SystemClock.elapsedRealtime();
+ mConfig.proxyInfo = profile.proxy;
+
+ switch (profile.type) {
+ case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+ case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+ mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
+ mVpnRunner.start();
+ break;
+ default:
+ updateState(DetailedState.FAILED, "Invalid platform VPN type");
+ Log.d(TAG, "Unknown VPN profile type: " + profile.type);
+ break;
+ }
+ } catch (IOException | GeneralSecurityException e) {
+ // Reset mConfig
+ mConfig = null;
+
+ updateState(DetailedState.FAILED, "VPN startup failed");
+ throw new IllegalArgumentException("VPN startup failed", e);
+ }
}
/**
@@ -2507,13 +2903,37 @@
public synchronized void stopVpnProfile(@NonNull String packageName) {
checkNotNull(packageName, "No package name provided");
- // To stop the VPN profile, the caller must be the current prepared package. Otherwise,
- // the app is not prepared, and we can just return.
- if (!isCurrentPreparedPackage(packageName)) {
- // TODO: Also check to make sure that the running VPN is a VPN profile.
+ // To stop the VPN profile, the caller must be the current prepared package and must be
+ // running an Ikev2VpnProfile.
+ if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
return;
}
prepareInternal(VpnConfig.LEGACY_VPN);
}
+
+ /**
+ * Proxy to allow testing
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Ikev2SessionCreator {
+ /** Creates a IKE session */
+ public IkeSession createIkeSession(
+ @NonNull Context context,
+ @NonNull IkeSessionParams ikeSessionParams,
+ @NonNull ChildSessionParams firstChildSessionParams,
+ @NonNull Executor userCbExecutor,
+ @NonNull IkeSessionCallback ikeSessionCallback,
+ @NonNull ChildSessionCallback firstChildSessionCallback) {
+ return new IkeSession(
+ context,
+ ikeSessionParams,
+ firstChildSessionParams,
+ userCbExecutor,
+ ikeSessionCallback,
+ firstChildSessionCallback);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
new file mode 100644
index 0000000..33fc32b
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
@@ -0,0 +1,390 @@
+/*
+ * 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.connectivity;
+
+import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_1024_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_192;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1;
+
+import android.annotation.NonNull;
+import android.net.Ikev2VpnProfile;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
+import android.net.Network;
+import android.net.RouteInfo;
+import android.net.eap.EapSessionConfig;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.util.IpRange;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.HexDump;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Utility class to build and convert IKEv2/IPsec parameters.
+ *
+ * @hide
+ */
+public class VpnIkev2Utils {
+ static IkeSessionParams buildIkeSessionParams(
+ @NonNull Ikev2VpnProfile profile, @NonNull UdpEncapsulationSocket socket) {
+ // TODO(b/149356682): Update this based on new IKE API. Only numeric addresses supported
+ // until then. All others throw IAE (caught by caller).
+ final InetAddress serverAddr = InetAddresses.parseNumericAddress(profile.getServerAddr());
+ final IkeIdentification localId = parseIkeIdentification(profile.getUserIdentity());
+ final IkeIdentification remoteId = parseIkeIdentification(profile.getServerAddr());
+
+ // TODO(b/149356682): Update this based on new IKE API.
+ final IkeSessionParams.Builder ikeOptionsBuilder =
+ new IkeSessionParams.Builder()
+ .setServerAddress(serverAddr)
+ .setUdpEncapsulationSocket(socket)
+ .setLocalIdentification(localId)
+ .setRemoteIdentification(remoteId);
+ setIkeAuth(profile, ikeOptionsBuilder);
+
+ for (final IkeSaProposal ikeProposal : getIkeSaProposals()) {
+ ikeOptionsBuilder.addSaProposal(ikeProposal);
+ }
+
+ return ikeOptionsBuilder.build();
+ }
+
+ static ChildSessionParams buildChildSessionParams() {
+ final TunnelModeChildSessionParams.Builder childOptionsBuilder =
+ new TunnelModeChildSessionParams.Builder();
+
+ for (final ChildSaProposal childProposal : getChildSaProposals()) {
+ childOptionsBuilder.addSaProposal(childProposal);
+ }
+
+ childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET);
+ childOptionsBuilder.addInternalAddressRequest(OsConstants.AF_INET6);
+ childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET);
+ childOptionsBuilder.addInternalDnsServerRequest(OsConstants.AF_INET6);
+
+ return childOptionsBuilder.build();
+ }
+
+ private static void setIkeAuth(
+ @NonNull Ikev2VpnProfile profile, @NonNull IkeSessionParams.Builder builder) {
+ switch (profile.getType()) {
+ case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+ final EapSessionConfig eapConfig =
+ new EapSessionConfig.Builder()
+ .setEapMsChapV2Config(profile.getUsername(), profile.getPassword())
+ .build();
+ builder.setAuthEap(profile.getServerRootCaCert(), eapConfig);
+ break;
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+ builder.setAuthPsk(profile.getPresharedKey());
+ break;
+ case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+ builder.setAuthDigitalSignature(
+ profile.getServerRootCaCert(),
+ profile.getUserCert(),
+ profile.getRsaPrivateKey());
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown auth method set");
+ }
+ }
+
+ private static List<IkeSaProposal> getIkeSaProposals() {
+ // TODO: filter this based on allowedAlgorithms
+ final List<IkeSaProposal> proposals = new ArrayList<>();
+
+ // Encryption Algorithms: Currently only AES_CBC is supported.
+ final IkeSaProposal.Builder normalModeBuilder = new IkeSaProposal.Builder();
+
+ // Currently only AES_CBC is supported.
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
+
+ // Authentication/Integrity Algorithms
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_AES_XCBC_96);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+ // Add AEAD options
+ final IkeSaProposal.Builder aeadBuilder = new IkeSaProposal.Builder();
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
+
+ // Add dh, prf for both builders
+ for (final IkeSaProposal.Builder builder : Arrays.asList(normalModeBuilder, aeadBuilder)) {
+ builder.addDhGroup(DH_GROUP_2048_BIT_MODP);
+ builder.addDhGroup(DH_GROUP_1024_BIT_MODP);
+ builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_AES128_XCBC);
+ builder.addPseudorandomFunction(PSEUDORANDOM_FUNCTION_HMAC_SHA1);
+ }
+
+ proposals.add(normalModeBuilder.build());
+ proposals.add(aeadBuilder.build());
+ return proposals;
+ }
+
+ private static List<ChildSaProposal> getChildSaProposals() {
+ // TODO: filter this based on allowedAlgorithms
+ final List<ChildSaProposal> proposals = new ArrayList<>();
+
+ // Add non-AEAD options
+ final ChildSaProposal.Builder normalModeBuilder = new ChildSaProposal.Builder();
+
+ // Encryption Algorithms: Currently only AES_CBC is supported.
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192);
+ normalModeBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128);
+
+ // Authentication/Integrity Algorithms
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+ normalModeBuilder.addIntegrityAlgorithm(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+ // Add AEAD options
+ final ChildSaProposal.Builder aeadBuilder = new ChildSaProposal.Builder();
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_256);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_192);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_128);
+ aeadBuilder.addEncryptionAlgorithm(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128);
+
+ proposals.add(normalModeBuilder.build());
+ proposals.add(aeadBuilder.build());
+ return proposals;
+ }
+
+ static class IkeSessionCallbackImpl implements IkeSessionCallback {
+ private final String mTag;
+ private final Vpn.IkeV2VpnRunnerCallback mCallback;
+ private final Network mNetwork;
+
+ IkeSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+ mTag = tag;
+ mCallback = callback;
+ mNetwork = network;
+ }
+
+ @Override
+ public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
+ Log.d(mTag, "IkeOpened for network " + mNetwork);
+ // Nothing to do here.
+ }
+
+ @Override
+ public void onClosed() {
+ Log.d(mTag, "IkeClosed for network " + mNetwork);
+ mCallback.onSessionLost(mNetwork); // Server requested session closure. Retry?
+ }
+
+ @Override
+ public void onClosedExceptionally(@NonNull IkeException exception) {
+ Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception);
+ mCallback.onSessionLost(mNetwork);
+ }
+
+ @Override
+ public void onError(@NonNull IkeProtocolException exception) {
+ Log.d(mTag, "IkeError for network " + mNetwork, exception);
+ // Non-fatal, log and continue.
+ }
+ }
+
+ static class ChildSessionCallbackImpl implements ChildSessionCallback {
+ private final String mTag;
+ private final Vpn.IkeV2VpnRunnerCallback mCallback;
+ private final Network mNetwork;
+
+ ChildSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+ mTag = tag;
+ mCallback = callback;
+ mNetwork = network;
+ }
+
+ @Override
+ public void onOpened(@NonNull ChildSessionConfiguration childConfig) {
+ Log.d(mTag, "ChildOpened for network " + mNetwork);
+ mCallback.onChildOpened(mNetwork, childConfig);
+ }
+
+ @Override
+ public void onClosed() {
+ Log.d(mTag, "ChildClosed for network " + mNetwork);
+ mCallback.onSessionLost(mNetwork);
+ }
+
+ @Override
+ public void onClosedExceptionally(@NonNull IkeException exception) {
+ Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception);
+ mCallback.onSessionLost(mNetwork);
+ }
+
+ @Override
+ public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
+ Log.d(mTag, "ChildTransformCreated; Direction: " + direction + "; network " + mNetwork);
+ mCallback.onChildTransformCreated(mNetwork, transform, direction);
+ }
+
+ @Override
+ public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) {
+ // Nothing to be done; no references to the IpSecTransform are held by the
+ // Ikev2VpnRunner (or this callback class), and this transform will be closed by the
+ // IKE library.
+ Log.d(mTag,
+ "ChildTransformDeleted; Direction: " + direction + "; for network " + mNetwork);
+ }
+ }
+
+ static class Ikev2VpnNetworkCallback extends NetworkCallback {
+ private final String mTag;
+ private final Vpn.IkeV2VpnRunnerCallback mCallback;
+
+ Ikev2VpnNetworkCallback(String tag, Vpn.IkeV2VpnRunnerCallback callback) {
+ mTag = tag;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ Log.d(mTag, "Starting IKEv2/IPsec session on new network: " + network);
+ mCallback.onDefaultNetworkChanged(network);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ Log.d(mTag, "Tearing down; lost network: " + network);
+ mCallback.onSessionLost(network);
+ }
+ }
+
+ /**
+ * Identity parsing logic using similar logic to open source implementations of IKEv2
+ *
+ * <p>This method does NOT support using type-prefixes (eg 'fqdn:' or 'keyid'), or ASN.1 encoded
+ * identities.
+ */
+ private static IkeIdentification parseIkeIdentification(@NonNull String identityStr) {
+ // TODO: Add identity formatting to public API javadocs.
+ if (identityStr.contains("@")) {
+ if (identityStr.startsWith("@#")) {
+ // KEY_ID
+ final String hexStr = identityStr.substring(2);
+ return new IkeKeyIdIdentification(HexDump.hexStringToByteArray(hexStr));
+ } else if (identityStr.startsWith("@@")) {
+ // RFC822 (USER_FQDN)
+ return new IkeRfc822AddrIdentification(identityStr.substring(2));
+ } else if (identityStr.startsWith("@")) {
+ // FQDN
+ return new IkeFqdnIdentification(identityStr.substring(1));
+ } else {
+ // RFC822 (USER_FQDN)
+ return new IkeRfc822AddrIdentification(identityStr);
+ }
+ } else if (InetAddresses.isNumericAddress(identityStr)) {
+ final InetAddress addr = InetAddresses.parseNumericAddress(identityStr);
+ if (addr instanceof Inet4Address) {
+ // IPv4
+ return new IkeIpv4AddrIdentification((Inet4Address) addr);
+ } else if (addr instanceof Inet6Address) {
+ // IPv6
+ return new IkeIpv6AddrIdentification((Inet6Address) addr);
+ } else {
+ throw new IllegalArgumentException("IP version not supported");
+ }
+ } else {
+ if (identityStr.contains(":")) {
+ // KEY_ID
+ return new IkeKeyIdIdentification(identityStr.getBytes());
+ } else {
+ // FQDN
+ return new IkeFqdnIdentification(identityStr);
+ }
+ }
+ }
+
+ static Collection<RouteInfo> getRoutesFromTrafficSelectors(
+ List<IkeTrafficSelector> trafficSelectors) {
+ final HashSet<RouteInfo> routes = new HashSet<>();
+
+ for (final IkeTrafficSelector selector : trafficSelectors) {
+ for (final IpPrefix prefix :
+ new IpRange(selector.startingAddress, selector.endingAddress).asIpPrefixes()) {
+ routes.add(new RouteInfo(prefix, null));
+ }
+ }
+
+ return routes;
+ }
+}
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index dea47db..afdcda9 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -1738,7 +1738,7 @@
/**
* Ensure the old pending.bin is deleted, as it has been changed to pending.xml.
- * pending.xml was used starting in KLP.
+ * pending.xml was used starting in KitKat.
* @param syncDir directory where the sync files are located.
*/
private void maybeDeleteLegacyPendingInfoLocked(File syncDir) {
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 68ced79..b9a30bb 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -23,6 +23,7 @@
import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
import static android.content.integrity.IntegrityUtils.getHexDigest;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
@@ -95,7 +96,7 @@
* This string will be used as the "installer" for formula evaluation when the app is being
* installed via ADB.
*/
- private static final String ADB_INSTALLER = "adb";
+ public static final String ADB_INSTALLER = "adb";
private static final String TAG = "AppIntegrityManagerServiceImpl";
@@ -106,8 +107,6 @@
private static final String ALLOWED_INSTALLER_DELIMITER = ",";
private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|";
- private static final String INSTALLER_CERT_NOT_APPLICABLE = "";
-
// Access to files inside mRulesDir is protected by mRulesLock;
private final Context mContext;
private final Handler mHandler;
@@ -282,15 +281,16 @@
builder.setInstallerName(getPackageNameNormalized(installerPackageName));
builder.setInstallerCertificates(installerCertificates);
builder.setIsPreInstalled(isSystemApp(packageName));
+ builder.setAllowedInstallersAndCert(getAllowedInstallers(packageInfo));
AppInstallMetadata appInstallMetadata = builder.build();
- Map<String, String> allowedInstallers = getAllowedInstallers(packageInfo);
Slog.i(
TAG,
- "To be verified: " + appInstallMetadata + " installers " + allowedInstallers);
+ "To be verified: " + appInstallMetadata + " installers " + getAllowedInstallers(
+ packageInfo));
IntegrityCheckResult result =
- mEvaluationEngine.evaluate(appInstallMetadata, allowedInstallers);
+ mEvaluationEngine.evaluate(appInstallMetadata);
Slog.i(
TAG,
"Integrity check result: "
@@ -449,9 +449,9 @@
String packageName = getPackageNameNormalized(packageAndCert[0]);
String cert = packageAndCert[1];
packageCertMap.put(packageName, cert);
- } else if (packageAndCert.length == 1
- && packageAndCert[0].equals(ADB_INSTALLER)) {
- packageCertMap.put(ADB_INSTALLER, INSTALLER_CERT_NOT_APPLICABLE);
+ } else if (packageAndCert.length == 1) {
+ packageCertMap.put(getPackageNameNormalized(packageAndCert[0]),
+ INSTALLER_CERTIFICATE_NOT_EVALUATED);
}
}
}
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
index 79e69e1..61da45d 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
@@ -17,9 +17,6 @@
package com.android.server.integrity.engine;
import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
import android.content.integrity.Rule;
import android.util.Slog;
@@ -28,10 +25,8 @@
import com.android.server.integrity.model.IntegrityCheckResult;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.Optional;
/**
* The engine used to evaluate rules against app installs.
@@ -69,16 +64,15 @@
* @return result of the integrity check
*/
public IntegrityCheckResult evaluate(
- AppInstallMetadata appInstallMetadata, Map<String, String> allowedInstallers) {
+ AppInstallMetadata appInstallMetadata) {
List<Rule> rules = loadRules(appInstallMetadata);
- allowedInstallersRule(allowedInstallers).ifPresent(rules::add);
return RuleEvaluator.evaluateRules(rules, appInstallMetadata);
}
private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
if (!mIntegrityFileManager.initialized()) {
- Slog.w(TAG, "Integrity rule files are not available. Evaluating only manifest rules.");
- return new ArrayList<>();
+ Slog.w(TAG, "Integrity rule files are not available.");
+ return Collections.emptyList();
}
try {
@@ -88,41 +82,4 @@
return new ArrayList<>();
}
}
-
- private static Optional<Rule> allowedInstallersRule(Map<String, String> allowedInstallers) {
- if (allowedInstallers.isEmpty()) {
- return Optional.empty();
- }
-
- List<IntegrityFormula> formulas = new ArrayList<>(allowedInstallers.size());
- allowedInstallers.forEach(
- (installer, cert) -> {
- formulas.add(allowedInstallerFormula(installer, cert));
- });
-
- // We need this special case since OR-formulas require at least two operands.
- IntegrityFormula allInstallersFormula =
- formulas.size() == 1
- ? formulas.get(0)
- : new CompoundFormula(CompoundFormula.OR, formulas);
-
- return Optional.of(
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT, Arrays.asList(allInstallersFormula)),
- Rule.DENY));
- }
-
- private static IntegrityFormula allowedInstallerFormula(String installer, String cert) {
- return new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- installer,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE, cert, /* isHashedValue= */
- false)));
- }
}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index e991f96..c9e356e 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -150,6 +150,7 @@
private RebootEscrowKey getAndClearRebootEscrowKey() {
IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
if (rebootEscrow == null) {
+ Slog.w(TAG, "Had reboot escrow data for users, but RebootEscrow HAL is unavailable");
return null;
}
@@ -197,11 +198,12 @@
mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
escrowData.getSyntheticPassword(), userId);
+ Slog.i(TAG, "Restored reboot escrow data for user " + userId);
return true;
} catch (IOException e) {
Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
+ return false;
}
- return false;
}
void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion,
@@ -212,16 +214,13 @@
IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
if (rebootEscrow == null) {
- mRebootEscrowWanted = false;
- setRebootEscrowReady(false);
+ Slog.w(TAG, "Reboot escrow requested, but RebootEscrow HAL is unavailable");
return;
}
RebootEscrowKey escrowKey = generateEscrowKeyIfNeeded();
if (escrowKey == null) {
Slog.e(TAG, "Could not generate escrow key");
- mRebootEscrowWanted = false;
- setRebootEscrowReady(false);
return;
}
@@ -250,6 +249,7 @@
try {
key = RebootEscrowKey.generate();
} catch (IOException e) {
+ Slog.w(TAG, "Could not generate reboot escrow key");
return null;
}
@@ -286,6 +286,7 @@
IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
if (rebootEscrow == null) {
+ Slog.w(TAG, "Escrow marked as ready, but RebootEscrow HAL is unavailable");
return false;
}
@@ -295,6 +296,7 @@
}
if (escrowKey == null) {
+ Slog.e(TAG, "Escrow key is null, but escrow was marked as ready");
return false;
}
@@ -302,8 +304,9 @@
try {
rebootEscrow.storeKey(escrowKey.getKeyBytes());
armedRebootEscrow = true;
+ Slog.i(TAG, "Reboot escrow key stored with RebootEscrow HAL");
} catch (RemoteException e) {
- Slog.w(TAG, "Failed escrow secret to RebootEscrow HAL", e);
+ Slog.e(TAG, "Failed escrow secret to RebootEscrow HAL", e);
}
return armedRebootEscrow;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 916b63b..a570502 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -932,7 +932,7 @@
StatusBarNotification sbn = r.getSbn();
cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
sbn.getId(), Notification.FLAG_AUTO_CANCEL,
- FLAG_FOREGROUND_SERVICE, false, r.getUserId(),
+ FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, false, r.getUserId(),
REASON_CLICK, nv.rank, nv.count, null);
nv.recycle();
reportUserInteraction(r);
@@ -7986,7 +7986,8 @@
FlagChecker flagChecker = (int flags) -> {
int flagsToCheck = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
- if (REASON_LISTENER_CANCEL_ALL == reason) {
+ if (REASON_LISTENER_CANCEL_ALL == reason
+ || REASON_CANCEL_ALL == reason) {
flagsToCheck |= FLAG_BUBBLE;
}
if ((flags & flagsToCheck) != 0) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index b98bb08..8ad3e9d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -611,10 +611,10 @@
/**
* Bind mount private volume CE and DE mirror storage.
*/
- public void onPrivateVolumeMounted(String volumeUuid) throws InstallerException {
+ public void tryMountDataMirror(String volumeUuid) throws InstallerException {
if (!checkBeforeRemote()) return;
try {
- mInstalld.onPrivateVolumeMounted(volumeUuid);
+ mInstalld.tryMountDataMirror(volumeUuid);
} catch (Exception e) {
throw InstallerException.from(e);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2c85d06..064fd3f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4600,7 +4600,7 @@
synchronized (mLock) {
final AndroidPackage p = mPackages.get(packageName);
if (p != null && p.isMatch(flags)) {
- PackageSetting ps = getPackageSetting(p.getPackageName());
+ PackageSetting ps = getPackageSettingInternal(p.getPackageName(), callingUid);
if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
return -1;
}
@@ -5924,7 +5924,10 @@
*/
@Override
public String[] getPackagesForUid(int uid) {
- final int callingUid = Binder.getCallingUid();
+ return getPackagesForUidInternal(uid, Binder.getCallingUid());
+ }
+
+ private String[] getPackagesForUidInternal(int uid, int callingUid) {
final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
final int userId = UserHandle.getUserId(uid);
final int appId = UserHandle.getAppId(uid);
@@ -17380,6 +17383,13 @@
@GuardedBy("mLock")
private String resolveInternalPackageNameLPr(String packageName, long versionCode) {
+ final int callingUid = Binder.getCallingUid();
+ return resolveInternalPackageNameInternalLocked(packageName, versionCode,
+ callingUid);
+ }
+
+ private String resolveInternalPackageNameInternalLocked(
+ String packageName, long versionCode, int callingUid) {
// Handle renamed packages
String normalizedPackageName = mSettings.getRenamedPackageLPr(packageName);
packageName = normalizedPackageName != null ? normalizedPackageName : packageName;
@@ -17393,12 +17403,12 @@
// Figure out which lib versions the caller can see
LongSparseLongArray versionsCallerCanSee = null;
- final int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+ final int callingAppId = UserHandle.getAppId(callingUid);
if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.SHELL_UID
&& callingAppId != Process.ROOT_UID) {
versionsCallerCanSee = new LongSparseLongArray();
String libName = versionedLib.valueAt(0).getName();
- String[] uidPackages = getPackagesForUid(Binder.getCallingUid());
+ String[] uidPackages = getPackagesForUidInternal(callingUid, callingUid);
if (uidPackages != null) {
for (String uidPackage : uidPackages) {
PackageSetting ps = mSettings.getPackageLPr(uidPackage);
@@ -23003,7 +23013,7 @@
@Override
public AndroidPackage getPackage(int uid) {
synchronized (mLock) {
- final String[] packageNames = getPackagesForUid(uid);
+ final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID);
AndroidPackage pkg = null;
final int numPackages = packageNames == null ? 0 : packageNames.length;
for (int i = 0; pkg == null && i < numPackages; i++) {
@@ -24017,9 +24027,13 @@
@Nullable
public PackageSetting getPackageSetting(String packageName) {
+ return getPackageSettingInternal(packageName, Binder.getCallingUid());
+ }
+
+ private PackageSetting getPackageSettingInternal(String packageName, int callingUid) {
synchronized (mLock) {
- packageName = resolveInternalPackageNameLPr(
- packageName, PackageManager.VERSION_CODE_HIGHEST);
+ packageName = resolveInternalPackageNameInternalLocked(
+ packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid);
return mSettings.mPackages.get(packageName);
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 002ab9c..a7b0d84 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -25,6 +25,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.SynchronousUserSwitchObserver;
@@ -95,6 +96,7 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.UiThread;
+import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.BatteryStatsService;
import com.android.server.lights.LightsManager;
@@ -2486,7 +2488,8 @@
boolean changed = false;
if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED
| DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE
- | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE)) != 0) {
+ | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE | DIRTY_SETTINGS
+ | DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) {
if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) {
if (DEBUG_SPEW) {
Slog.d(TAG, "updateWakefulnessLocked: Bed time...");
@@ -3153,7 +3156,10 @@
}
private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
- final String reason, boolean wait) {
+ @Nullable final String reason, boolean wait) {
+ if (PowerManager.REBOOT_USERSPACE.equals(reason)) {
+ UserspaceRebootLogger.noteUserspaceRebootWasRequested();
+ }
if (mHandler == null || !mSystemReady) {
if (RescueParty.isAttemptingFactoryReset()) {
// If we're stuck in a really low-level reboot loop, and a
@@ -5038,7 +5044,7 @@
* @param wait If true, this call waits for the reboot to complete and does not return.
*/
@Override // Binder call
- public void reboot(boolean confirm, String reason, boolean wait) {
+ public void reboot(boolean confirm, @Nullable String reason, boolean wait) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
if (PowerManager.REBOOT_RECOVERY.equals(reason)
|| PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 7164a30..e0701e8 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -359,20 +359,23 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
if (!mPreparedForReboot) {
+ Slog.i(TAG, "Reboot requested before prepare completed");
return false;
}
- if (updateToken != null && updateToken.equals(mUnattendedRebootToken)) {
- if (!mInjector.getLockSettingsService().armRebootEscrow()) {
- return false;
- }
-
- PowerManager pm = mInjector.getPowerManager();
- pm.reboot(reason);
- return true;
+ if (updateToken != null && !updateToken.equals(mUnattendedRebootToken)) {
+ Slog.i(TAG, "Reboot requested after preparation, but with mismatching token");
+ return false;
}
- return false;
+ if (!mInjector.getLockSettingsService().armRebootEscrow()) {
+ Slog.w(TAG, "Failure to escrow key for reboot");
+ return false;
+ }
+
+ PowerManager pm = mInjector.getPowerManager();
+ pm.reboot(reason);
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 91e7cc9..9e150fd 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -869,14 +869,6 @@
return false;
}
- ApplicationInfo appInfo = pkgInfo.applicationInfo;
- boolean success = rollback.enableForPackage(packageName, newPackage.versionCode,
- pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
- appInfo.splitSourceDirs, session.rollbackDataPolicy);
- if (!success) {
- return success;
- }
-
if (isApex) {
// Check if this apex contains apks inside it. If true, then they should be added as
// a RollbackPackageInfo into this rollback
@@ -894,12 +886,24 @@
Slog.e(TAG, apkInApex + " is not installed");
return false;
}
- success = rollback.enableForPackageInApex(
- apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy);
- if (!success) return success;
+ if (!rollback.enableForPackageInApex(
+ apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy)) {
+ return false;
+ }
}
}
- return true;
+
+ /**
+ * The order is important here! Always enable the embedded apk-in-apex (if any) before
+ * enabling the embedding apex. Otherwise the rollback object might be in an inconsistent
+ * state where an embedding apex is successfully enabled while one of its embedded
+ * apk-in-apex failed. Note {@link Rollback#allPackagesEnabled()} won't behave correctly if
+ * a rollback object is inconsistent because it doesn't count apk-in-apex.
+ */
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
+ return rollback.enableForPackage(packageName, newPackage.versionCode,
+ pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
+ appInfo.splitSourceDirs, session.rollbackDataPolicy);
}
@Override
@@ -982,8 +986,6 @@
if (!session.isMultiPackage()) {
if (!enableRollbackForPackageSession(newRollback, session)) {
Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
- result.offer(-1);
- return;
}
} else {
for (int childSessionId : session.getChildSessionIds()) {
@@ -991,13 +993,11 @@
installer.getSessionInfo(childSessionId);
if (childSession == null) {
Slog.e(TAG, "No matching child install session for: " + childSessionId);
- result.offer(-1);
- return;
+ break;
}
if (!enableRollbackForPackageSession(newRollback, childSession)) {
Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
- result.offer(-1);
- return;
+ break;
}
}
}
@@ -1188,8 +1188,7 @@
}
/**
- * Add a rollback to the list of rollbacks. This should be called after rollback has been
- * enabled for all packages in the rollback. It does not make the rollback available yet.
+ * Add a rollback to the list of rollbacks. It does not make the rollback available yet.
*
* @return the Rollback instance for a successfully enable-completed rollback,
* or null on error.
diff --git a/services/core/java/com/android/server/tv/tuner/ClientProfile.java b/services/core/java/com/android/server/tv/tuner/ClientProfile.java
new file mode 100644
index 0000000..3845195
--- /dev/null
+++ b/services/core/java/com/android/server/tv/tuner/ClientProfile.java
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+package com.android.server.tv.tuner;
+
+/**
+ * A client profile object used by the Tuner Resource Manager to record the registered clients'
+ * information.
+ *
+ * @hide
+ */
+public final class ClientProfile {
+ public static final int INVALID_GROUP_ID = -1;
+ /**
+ * Client id sent to the client when registering with
+ * {@link #registerClientProfile(ResourceClientProfile, TunerResourceManagerCallback, int[])}
+ */
+ private final int mClientId;
+
+ /**
+ * see {@link ResourceClientProfile}
+ */
+ private final String mTvInputSessionId;
+
+ /**
+ * see {@link ResourceClientProfile}
+ */
+ private final int mUseCase;
+
+ /**
+ * Process id queried from {@link TvInputManager#}
+ */
+ private final int mProcessId;
+
+ /**
+ * All the clients that share the same resource would be under the same group id.
+ *
+ * <p>If a client's resource is to be reclaimed, all other clients under the same group id
+ * also lose their resources.
+ */
+ private int mGroupId = INVALID_GROUP_ID;
+
+ /**
+ * Optional nice value for TRM to reduce client’s priority.
+ */
+ private int mNiceValue;
+
+ /**
+ * Optional arbitrary priority value given by the client.
+ *
+ * <p>This value can override the default priorotiy calculated from
+ * the client profile.
+ */
+ private int mPriority;
+
+ private ClientProfile(ClientProfileBuilder builder) {
+ this.mClientId = builder.mClientId;
+ this.mTvInputSessionId = builder.mTvInputSessionId;
+ this.mUseCase = builder.mUseCase;
+ this.mProcessId = builder.mProcessId;
+ this.mGroupId = builder.mGroupId;
+ this.mNiceValue = builder.mNiceValue;
+ this.mPriority = builder.mPriority;
+ }
+
+ public int getClientId() {
+ return mClientId;
+ }
+
+ public String getTvInputSessionId() {
+ return mTvInputSessionId;
+ }
+
+ public int getUseCase() {
+ return mUseCase;
+ }
+
+ public int getProcessId() {
+ return mProcessId;
+ }
+
+ public int getGroupId() {
+ return mGroupId;
+ }
+
+ public int getPriority() {
+ return mPriority;
+ }
+
+ public int getNiceValue() {
+ return mNiceValue;
+ }
+
+ public void setGroupId(int groupId) {
+ mGroupId = groupId;
+ }
+
+ public void setPriority(int priority) {
+ mPriority = priority;
+ }
+
+ public void setNiceValue(int niceValue) {
+ mNiceValue = niceValue;
+ }
+
+ @Override
+ public String toString() {
+ return "ClientProfile: " + this.mClientId + ", " + this.mTvInputSessionId + ", "
+ + this.mUseCase + ", " + this.mProcessId;
+ }
+
+ public static class ClientProfileBuilder {
+ private final int mClientId;
+ private String mTvInputSessionId;
+ private int mUseCase;
+ private int mProcessId;
+ private int mGroupId;
+ private int mNiceValue;
+ private int mPriority;
+
+ ClientProfileBuilder(int clientId) {
+ this.mClientId = clientId;
+ }
+
+ /**
+ * Builder for {@link ClientProfile}.
+ *
+ * @param useCase the useCase of the client.
+ */
+ public ClientProfileBuilder useCase(int useCase) {
+ this.mUseCase = useCase;
+ return this;
+ }
+
+ /**
+ * Builder for {@link ClientProfile}.
+ *
+ * @param tvInputSessionId the id of the tv input session.
+ */
+ public ClientProfileBuilder tvInputSessionId(String tvInputSessionId) {
+ this.mTvInputSessionId = tvInputSessionId;
+ return this;
+ }
+
+ /**
+ * Builder for {@link ClientProfile}.
+ *
+ * @param processId the id of process.
+ */
+ public ClientProfileBuilder processId(int processId) {
+ this.mProcessId = processId;
+ return this;
+ }
+
+
+ /**
+ * Builder for {@link ClientProfile}.
+ *
+ * @param groupId the id of the group that shares the same resource.
+ */
+ public ClientProfileBuilder groupId(int groupId) {
+ this.mGroupId = groupId;
+ return this;
+ }
+
+ /**
+ * Builder for {@link ClientProfile}.
+ *
+ * @param niceValue the nice value of the client.
+ */
+ public ClientProfileBuilder niceValue(int niceValue) {
+ this.mNiceValue = niceValue;
+ return this;
+ }
+
+ /**
+ * Builder for {@link ClientProfile}.
+ *
+ * @param priority the priority value of the client.
+ */
+ public ClientProfileBuilder priority(int priority) {
+ this.mPriority = priority;
+ return this;
+ }
+
+ /**
+ * Build a {@link ClientProfile}.
+ *
+ * @return {@link ClientProfile}.
+ */
+ public ClientProfile build() {
+ ClientProfile clientProfile = new ClientProfile(this);
+ return clientProfile;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/tv/tuner/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tuner/TunerResourceManagerService.java
new file mode 100644
index 0000000..e876421
--- /dev/null
+++ b/services/core/java/com/android/server/tv/tuner/TunerResourceManagerService.java
@@ -0,0 +1,258 @@
+/*
+ * 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.
+ */
+
+package com.android.server.tv.tuner;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.tv.TvInputManager;
+import android.media.tv.tuner.CasSessionRequest;
+import android.media.tv.tuner.ITunerResourceManager;
+import android.media.tv.tuner.ITunerResourceManagerListener;
+import android.media.tv.tuner.ResourceClientProfile;
+import android.media.tv.tuner.TunerFrontendInfo;
+import android.media.tv.tuner.TunerFrontendRequest;
+import android.media.tv.tuner.TunerLnbRequest;
+import android.media.tv.tuner.TunerResourceManager;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides a system service that manages the TV tuner resources.
+ *
+ * @hide
+ */
+public class TunerResourceManagerService extends SystemService {
+ private static final String TAG = "TunerResourceManagerService";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private SparseArray<ClientProfile> mClientProfiles = new SparseArray<>();
+ private SparseArray<ITunerResourceManagerListener> mListeners = new SparseArray<>();
+ private int mNextUnusedFrontendId = 0;
+ private List<Integer> mReleasedClientId = new ArrayList<Integer>();
+ private List<Integer> mAvailableFrontendIds = new ArrayList<Integer>();
+
+ private TvInputManager mManager;
+
+ public TunerResourceManagerService(@Nullable Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, new BinderService());
+ mManager = (TvInputManager) getContext()
+ .getSystemService(Context.TV_INPUT_SERVICE);
+ }
+
+ private final class BinderService extends ITunerResourceManager.Stub {
+ @Override
+ public void registerClientProfile(@NonNull ResourceClientProfile profile,
+ @NonNull ITunerResourceManagerListener listener,
+ @NonNull int[] clientId) {
+ if (DEBUG) {
+ Slog.d(TAG, "registerClientProfile(clientProfile=" + profile + ")");
+ }
+
+ // TODO tell if the client already exists
+ if (mReleasedClientId.isEmpty()) {
+ clientId[0] = mNextUnusedFrontendId++;
+ } else {
+ clientId[0] = mReleasedClientId.get(0);
+ mReleasedClientId.remove(0);
+ }
+
+ if (mManager == null) {
+ Slog.e(TAG, "TvInputManager is null. Can't register client profile.");
+ return;
+ }
+
+ int callingPid = mManager.getClientPid(profile.getTvInputSessionId());
+
+ ClientProfile clientProfile = new ClientProfile.ClientProfileBuilder(
+ clientId[0])
+ .tvInputSessionId(profile.getTvInputSessionId())
+ .useCase(profile.getUseCase())
+ .processId(callingPid)
+ .build();
+ mClientProfiles.append(clientId[0], clientProfile);
+ mListeners.append(clientId[0], listener);
+ }
+
+ @Override
+ public void unregisterClientProfile(int clientId) {
+ if (DEBUG) {
+ Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")");
+ }
+
+ mClientProfiles.remove(clientId);
+ mListeners.remove(clientId);
+ mReleasedClientId.add(clientId);
+ }
+
+ @Override
+ public boolean updateClientPriority(int clientId, int priority, int niceValue) {
+ if (DEBUG) {
+ Slog.d(TAG, "updateClientPriority(clientId=" + clientId
+ + ", priority=" + priority + ", niceValue=" + niceValue + ")");
+ }
+
+ ClientProfile profile = mClientProfiles.get(clientId);
+ if (profile == null) {
+ Slog.e(TAG, "Can not find client profile with id " + clientId
+ + " when trying to update the client priority.");
+ return false;
+ }
+
+ profile.setPriority(priority);
+ profile.setNiceValue(niceValue);
+
+ return true;
+ }
+
+ @Override
+ public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos)
+ throws RemoteException {
+ if (infos == null || infos.length == 0) {
+ Slog.d(TAG, "Can't update with empty frontend info");
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "updateFrontendInfo:");
+ for (int i = 0; i < infos.length; i++) {
+ Slog.d(TAG, infos[i].toString());
+ }
+ }
+ }
+
+ @Override
+ public void updateCasInfo(int casSystemId, int maxSessionNum) {
+ if (DEBUG) {
+ Slog.d(TAG, "updateCasInfo(casSystemId="
+ + casSystemId + ", maxSessionNum=" + maxSessionNum + ")");
+ }
+ }
+
+ @Override
+ public void setLnbInfoList(int[] lnbIds) {
+ if (DEBUG) {
+ for (int i = 0; i < lnbIds.length; i++) {
+ Slog.d(TAG, "updateLnbInfo(lnbId=" + lnbIds[i] + ")");
+ }
+ }
+ }
+
+ @Override
+ public boolean requestFrontend(@NonNull TunerFrontendRequest request,
+ @NonNull int[] frontendId) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "requestFrontend(request=" + request + ")");
+ }
+
+ frontendId[0] = TunerResourceManager.INVALID_FRONTEND_ID;
+
+ if (getContext() == null) {
+ Slog.e(TAG, "Can not find context when requesting frontend");
+ return false;
+ }
+
+ if (mClientProfiles.get(request.getClientId()) == null) {
+ Slog.e(TAG, "Request from unregistered client. Id: "
+ + request.getClientId());
+ return false;
+ }
+
+ String sessionId = mClientProfiles.get(request.getClientId())
+ .getTvInputSessionId();
+
+ if (DEBUG) {
+ Slog.d(TAG, "session Id:" + sessionId + ")");
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "No available Frontend found.");
+ }
+
+ return false;
+ }
+
+ @Override
+ public void shareFrontend(int selfClientId, int targetClientId) {
+ if (DEBUG) {
+ Slog.d(TAG, "shareFrontend from "
+ + selfClientId + " with " + targetClientId);
+ }
+ }
+
+ @Override
+ public boolean requestCasSession(@NonNull CasSessionRequest request,
+ @NonNull int[] sessionResourceId) {
+ if (DEBUG) {
+ Slog.d(TAG, "requestCasSession(request=" + request + ")");
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbId) {
+ if (DEBUG) {
+ Slog.d(TAG, "requestLnb(request=" + request + ")");
+ }
+ return true;
+ }
+
+ @Override
+ public void releaseFrontend(int frontendId) {
+ if (DEBUG) {
+ Slog.d(TAG, "releaseFrontend(id=" + frontendId + ")");
+ }
+ }
+
+ @Override
+ public void releaseCasSession(int sessionResourceId) {
+ if (DEBUG) {
+ Slog.d(TAG, "releaseCasSession(sessionResourceId=" + sessionResourceId + ")");
+ }
+ }
+
+ @Override
+ public void releaseLnb(int lnbId) {
+ if (DEBUG) {
+ Slog.d(TAG, "releaseLnb(lnbId=" + lnbId + ")");
+ }
+ }
+
+ @Override
+ public boolean isHigherPriority(ResourceClientProfile challengerProfile,
+ ResourceClientProfile holderProfile) {
+ if (DEBUG) {
+ Slog.d(TAG, "isHigherPriority(challengerProfile=" + challengerProfile
+ + ", holderProfile=" + challengerProfile + ")");
+ }
+ return true;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ca6bd2d..38b0ca8 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -145,7 +145,6 @@
import android.util.Slog;
import android.view.DisplayCutout;
import android.view.Gravity;
-import android.view.IApplicationToken;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -333,8 +332,6 @@
private WindowState mFocusedWindow;
private WindowState mLastFocusedWindow;
- IApplicationToken mFocusedApp;
-
// The states of decor windows from the last layout. These are used to generate another display
// layout in different bounds but with the same states.
private boolean mLastNavVisible;
@@ -3287,7 +3284,6 @@
&& mLastBehavior == behavior
&& mLastFocusIsFullscreen == isFullscreen
&& mLastFocusIsImmersive == isImmersive
- && mFocusedApp == win.getAppToken()
&& mLastNonDockedStackBounds.equals(mNonDockedStackBounds)
&& mLastDockedStackBounds.equals(mDockedStackBounds)) {
return 0;
@@ -3304,7 +3300,6 @@
mLastBehavior = behavior;
mLastFocusIsFullscreen = isFullscreen;
mLastFocusIsImmersive = isImmersive;
- mFocusedApp = win.getAppToken();
mLastNonDockedStackBounds.set(mNonDockedStackBounds);
mLastDockedStackBounds.set(mDockedStackBounds);
final Rect fullscreenStackBounds = new Rect(mNonDockedStackBounds);
@@ -3803,9 +3798,6 @@
if (mFocusedWindow != null) {
pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
}
- if (mFocusedApp != null) {
- pw.print(prefix); pw.print("mFocusedApp="); pw.println(mFocusedApp);
- }
if (mTopFullscreenOpaqueWindowState != null) {
pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
pw.println(mTopFullscreenOpaqueWindowState);
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 2f61ca0..63346b9 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -67,7 +67,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer(
ResetTargetTaskHelper::processTask, this, PooledLambda.__(Task.class));
- targetTask.mWmService.mRoot.forAllTasks(c);
+ targetTask.mWmService.mRoot.forAllTasks(c, true /*traverseTopToBottom*/, mTargetStack);
c.recycle();
processPendingReparentActivities();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9c23ed3..0aed691 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3070,17 +3070,11 @@
return matchParentBounds();
}
+ @Override
void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom, Task excludedTask) {
- if (traverseTopToBottom) {
- super.forAllTasks(callback, traverseTopToBottom);
- if (excludedTask != this) {
- callback.accept(this);
- }
- } else {
- super.forAllTasks(callback, traverseTopToBottom);
- if (excludedTask != this) {
- callback.accept(this);
- }
+ super.forAllTasks(callback, traverseTopToBottom, excludedTask);
+ if (excludedTask != this) {
+ callback.accept(this);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 504aa2d..0bb4e03 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1425,6 +1425,19 @@
}
}
+ void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom, Task excludedTask) {
+ final int count = mChildren.size();
+ if (traverseTopToBottom) {
+ for (int i = count - 1; i >= 0; --i) {
+ mChildren.get(i).forAllTasks(callback, traverseTopToBottom, excludedTask);
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ mChildren.get(i).forAllTasks(callback, traverseTopToBottom, excludedTask);
+ }
+ }
+ }
+
Task getTaskAbove(Task t) {
return getTask(
(above) -> true, t, false /*includeBoundary*/, false /*traverseTopToBottom*/);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d2ec436..aa5dafc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -57,6 +57,7 @@
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
@@ -522,7 +523,8 @@
/** Keyguard features that are allowed to be set on a managed profile */
private static final int PROFILE_KEYGUARD_FEATURES =
- PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER | PROFILE_KEYGUARD_FEATURES_PROFILE_ONLY;
+ NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER
+ | PROFILE_KEYGUARD_FEATURES_PROFILE_ONLY;
private static final int DEVICE_ADMIN_DEACTIVATE_TIMEOUT = 10000;
@@ -2542,7 +2544,7 @@
* corporate owned device.
*/
@GuardedBy("getLockObject()")
- private void maybeMigrateToProfileOnOrganizationOwnedDeviceLocked() {
+ private void migrateToProfileOnOrganizationOwnedDeviceIfCompLocked() {
logIfVerbose("Checking whether we need to migrate COMP ");
final int doUserId = mOwners.getDeviceOwnerUserId();
if (doUserId == UserHandle.USER_NULL) {
@@ -2605,6 +2607,11 @@
// Note: KeyChain keys are not removed and will remain accessible for the apps that have
// been given grants to use them.
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.COMP_TO_ORG_OWNED_PO_MIGRATED)
+ .setAdmin(poAdminComponent)
+ .write();
}
private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
@@ -3865,7 +3872,7 @@
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
maybeStartSecurityLogMonitorOnActivityManagerReady();
synchronized (getLockObject()) {
- maybeMigrateToProfileOnOrganizationOwnedDeviceLocked();
+ migrateToProfileOnOrganizationOwnedDeviceIfCompLocked();
}
final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
if (userId >= 0) {
@@ -8163,16 +8170,20 @@
}
Objects.requireNonNull(who, "ComponentName is null");
final int userHandle = mInjector.userHandleGetCallingUserId();
- if (isManagedProfile(userHandle)) {
- if (parent) {
- which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
- } else {
- which = which & PROFILE_KEYGUARD_FEATURES;
- }
- }
synchronized (getLockObject()) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
+ if (isManagedProfile(userHandle)) {
+ if (parent) {
+ if (isProfileOwnerOfOrganizationOwnedDevice(ap)) {
+ which = which & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+ } else {
+ which = which & NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+ }
+ } else {
+ which = which & PROFILE_KEYGUARD_FEATURES;
+ }
+ }
if (ap.disabledKeyguardFeatures != which) {
ap.disabledKeyguardFeatures = which;
saveSettingsLocked(userHandle);
@@ -11553,7 +11564,7 @@
}
@Override
- public void setLockdownAdminConfiguredNetworks(ComponentName who, boolean lockdown) {
+ public void setConfiguredNetworksLockdownState(ComponentName who, boolean lockdown) {
if (!mHasFeature) {
return;
}
@@ -11572,7 +11583,7 @@
}
@Override
- public boolean isLockdownAdminConfiguredNetworks(ComponentName who) {
+ public boolean hasLockdownAdminConfiguredNetworks(ComponentName who) {
if (!mHasFeature) {
return false;
}
@@ -15551,6 +15562,12 @@
mInjector.binderWithCleanCallingIdentity(
() -> applyPersonalAppsSuspension(callingUserId, suspended));
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PERSONAL_APPS_SUSPENDED)
+ .setAdmin(who)
+ .setBoolean(suspended)
+ .write();
}
/**
@@ -15722,9 +15739,16 @@
mInjector.binderWithCleanCallingIdentity(
() -> updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked()));
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF)
+ .setAdmin(who)
+ .setTimePeriod(timeoutMs)
+ .write();
}
- void enforceHandlesCheckPolicyComplianceIntent(@UserIdInt int userId, String packageName) {
+ private void enforceHandlesCheckPolicyComplianceIntent(
+ @UserIdInt int userId, String packageName) {
mInjector.binderWithCleanCallingIdentity(() -> {
final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
intent.setPackage(packageName);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index eef6c63..93662c9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -162,6 +162,7 @@
import com.android.server.trust.TrustManagerService;
import com.android.server.tv.TvInputManagerService;
import com.android.server.tv.TvRemoteService;
+import com.android.server.tv.tuner.TunerResourceManagerService;
import com.android.server.twilight.TwilightService;
import com.android.server.uri.UriGrantsManagerService;
import com.android.server.usage.UsageStatsService;
@@ -1854,6 +1855,8 @@
|| mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
t.traceBegin("StartTvInputManager");
mSystemServiceManager.startService(TvInputManagerService.class);
+ t.traceBegin("StartTunerResourceManager");
+ mSystemServiceManager.startService(TunerResourceManagerService.class);
t.traceEnd();
}
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index c8673f8..a904b42 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -322,7 +322,8 @@
private void updateDefaultDialer(@NonNull UserData userData) {
TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
String defaultDialer = telecomManager != null
- ? telecomManager.getDefaultDialerPackage(userData.getUserId()) : null;
+ ? telecomManager.getDefaultDialerPackage(
+ new UserHandle(userData.getUserId())) : null;
userData.setDefaultDialer(defaultDialer);
}
diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
index 4e37f47..644c155 100644
--- a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java
@@ -59,7 +59,7 @@
*/
boolean querySince(long sinceTime) {
UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser(
- mUserId, sinceTime, System.currentTimeMillis(), false, false);
+ mUserId, sinceTime, System.currentTimeMillis(), false, false, false);
if (usageEvents == null) {
return false;
}
diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk
new file mode 100644
index 0000000..9161d86
--- /dev/null
+++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk
Binary files differ
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 8a7462c..164ee31 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -1095,6 +1095,24 @@
}
@Test
+ public void testRegisterAuthenticator_updatesStrengths() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ verify(mBiometricService.mBiometricStrengthController).startListening();
+ verify(mBiometricService.mBiometricStrengthController, never()).updateStrengths();
+
+ when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+ .thenReturn(true);
+ when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
+ mBiometricService.mImpl.registerAuthenticator(0 /* testId */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ mFingerprintAuthenticator);
+
+ verify(mBiometricService.mBiometricStrengthController).updateStrengths();
+ }
+
+ @Test
public void testWithDowngradedAuthenticator() throws Exception {
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
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 3f74681..8f70cca 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2188,6 +2188,42 @@
assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts);
}
+ public void testSetKeyguardDisabledFeaturesWithDO() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+
+ dpm.setKeyguardDisabledFeatures(admin1, DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+
+ assertThat(dpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+ DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+ }
+
+ public void testSetKeyguardDisabledFeaturesWithPO() throws Exception {
+ setupProfileOwner();
+
+ dpm.setKeyguardDisabledFeatures(admin1, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+
+ assertThat(dpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+ DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+ }
+
+ public void testSetKeyguardDisabledFeaturesWithPOOfOrganizationOwnedDevice()
+ throws Exception {
+ final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
+ final int MANAGED_PROFILE_ADMIN_UID =
+ UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+ configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+ parentDpm.setKeyguardDisabledFeatures(admin1,
+ DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+
+ assertThat(parentDpm.getKeyguardDisabledFeatures(admin1)).isEqualTo(
+ DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA);
+ }
+
public void testSetApplicationHiddenWithDO() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -3781,35 +3817,35 @@
assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
}
- public void testSetLockdownAdminConfiguredNetworksWithDO() throws Exception {
+ public void testSetConfiguredNetworksLockdownStateWithDO() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
- dpm.setLockdownAdminConfiguredNetworks(admin1, true);
+ dpm.setConfiguredNetworksLockdownState(admin1, true);
verify(getServices().settings).settingsGlobalPutInt(
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
- dpm.setLockdownAdminConfiguredNetworks(admin1, false);
+ dpm.setConfiguredNetworksLockdownState(admin1, false);
verify(getServices().settings).settingsGlobalPutInt(
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
}
- public void testSetLockdownAdminConfiguredNetworksWithPO() throws Exception {
+ public void testSetConfiguredNetworksLockdownStateWithPO() throws Exception {
setupProfileOwner();
assertExpectException(SecurityException.class, null,
- () -> dpm.setLockdownAdminConfiguredNetworks(admin1, false));
+ () -> dpm.setConfiguredNetworksLockdownState(admin1, false));
verify(getServices().settings, never()).settingsGlobalPutInt(
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
}
- public void testSetLockdownAdminConfiguredNetworksWithPOOfOrganizationOwnedDevice()
+ public void testSetConfiguredNetworksLockdownStateWithPOOfOrganizationOwnedDevice()
throws Exception {
setupProfileOwner();
configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
- dpm.setLockdownAdminConfiguredNetworks(admin1, true);
+ dpm.setConfiguredNetworksLockdownState(admin1, true);
verify(getServices().settings).settingsGlobalPutInt(
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
- dpm.setLockdownAdminConfiguredNetworks(admin1, false);
+ dpm.setConfiguredNetworksLockdownState(admin1, false);
verify(getServices().settings).settingsGlobalPutInt(
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0);
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index 8dae48c..0d4c6e8 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -19,6 +19,7 @@
import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID;
@@ -39,6 +40,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -65,6 +68,7 @@
import com.android.server.integrity.model.IntegrityCheckResult;
import com.android.server.testutils.TestUtils;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,8 +80,9 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -87,6 +92,9 @@
private static final String TEST_APP_PATH =
"/data/local/tmp/AppIntegrityManagerServiceTestApp.apk";
+ private static final String TEST_APP_TWO_CERT_PATH =
+ "AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk";
+
private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
private static final String VERSION = "version";
private static final String TEST_FRAMEWORK_PACKAGE = "com.android.frameworks.servicestests";
@@ -105,6 +113,11 @@
private static final String INSTALLER_SHA256 =
"30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227";
+ private static final String DUMMY_APP_TWO_CERTS_CERT_1 =
+ "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94";
+ private static final String DUMMY_APP_TWO_CERTS_CERT_2 =
+ "94366E0A80F3A3F0D8171A15760B88E228CD6E1101F0414C98878724FBE70147";
+
private static final String PLAY_STORE_PKG = "com.android.vending";
private static final String ADB_INSTALLER = "adb";
private static final String PLAY_STORE_CERT = "play_store_cert";
@@ -128,6 +141,7 @@
private PackageManager mSpyPackageManager;
private File mTestApk;
+ private File mTestApkTwoCerts;
private final Context mRealContext = InstrumentationRegistry.getTargetContext();
// under test
@@ -136,6 +150,10 @@
@Before
public void setup() throws Exception {
mTestApk = new File(TEST_APP_PATH);
+ mTestApkTwoCerts = File.createTempFile("AppIntegrity", ".apk");
+ try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_TWO_CERT_PATH)) {
+ Files.copy(inputStream, mTestApkTwoCerts.toPath(), REPLACE_EXISTING);
+ }
mService =
new AppIntegrityManagerServiceImpl(
@@ -154,6 +172,11 @@
when(mIntegrityFileManager.initialized()).thenReturn(true);
}
+ @After
+ public void tearDown() throws Exception {
+ mTestApkTwoCerts.delete();
+ }
+
@Test
public void updateRuleSet_notAuthorized() throws Exception {
makeUsSystemApp();
@@ -268,20 +291,16 @@
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent();
- when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+ when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
runJobInHandler();
ArgumentCaptor<AppInstallMetadata> metadataCaptor =
ArgumentCaptor.forClass(AppInstallMetadata.class);
- Map<String, String> allowedInstallers = new HashMap<>();
- ArgumentCaptor<Map<String, String>> allowedInstallersCaptor =
- ArgumentCaptor.forClass(allowedInstallers.getClass());
verify(mRuleEvaluationEngine)
- .evaluate(metadataCaptor.capture(), allowedInstallersCaptor.capture());
+ .evaluate(metadataCaptor.capture());
AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
- allowedInstallers = allowedInstallersCaptor.getValue();
assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName());
assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT);
assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName());
@@ -289,9 +308,34 @@
assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode());
assertFalse(appInstallMetadata.isPreInstalled());
// These are hardcoded in the test apk android manifest
+ Map<String, String> allowedInstallers =
+ appInstallMetadata.getAllowedInstallersAndCertificates();
assertEquals(2, allowedInstallers.size());
assertEquals(PLAY_STORE_CERT, allowedInstallers.get(PLAY_STORE_PKG));
- assertEquals(ADB_CERT, allowedInstallers.get(ADB_INSTALLER));
+ assertEquals(INSTALLER_CERTIFICATE_NOT_EVALUATED, allowedInstallers.get(ADB_INSTALLER));
+ }
+
+ @Test
+ public void handleBroadcast_correctArgs_multipleCerts() throws Exception {
+ whitelistUsAsRuleProvider();
+ makeUsSystemApp();
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mMockContext)
+ .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+ Intent intent = makeVerificationIntent();
+ intent.setDataAndType(Uri.fromFile(mTestApkTwoCerts), PACKAGE_MIME_TYPE);
+ when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
+
+ broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+ runJobInHandler();
+
+ ArgumentCaptor<AppInstallMetadata> metadataCaptor =
+ ArgumentCaptor.forClass(AppInstallMetadata.class);
+ verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture());
+ AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
+ assertThat(appInstallMetadata.getAppCertificates()).containsExactly(
+ DUMMY_APP_TWO_CERTS_CERT_1, DUMMY_APP_TWO_CERTS_CERT_2);
}
@Test
@@ -303,7 +347,7 @@
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent();
- when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+ when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
runJobInHandler();
@@ -321,7 +365,7 @@
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
- when(mRuleEvaluationEngine.evaluate(any(), any()))
+ when(mRuleEvaluationEngine.evaluate(any()))
.thenReturn(
IntegrityCheckResult.deny(
Arrays.asList(
@@ -349,7 +393,7 @@
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent();
- when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+ when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
runJobInHandler();
@@ -377,7 +421,7 @@
verify(mMockContext, atLeastOnce())
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent(TEST_FRAMEWORK_PACKAGE);
- when(mRuleEvaluationEngine.evaluate(any(), any()))
+ when(mRuleEvaluationEngine.evaluate(any()))
.thenReturn(IntegrityCheckResult.deny(/* rule= */ null));
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
index b0b9596..0488745 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java
@@ -22,6 +22,8 @@
import static org.mockito.Mockito.when;
import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.IntegrityFormula;
+import android.content.integrity.Rule;
import com.android.server.integrity.IntegrityFileManager;
import com.android.server.integrity.model.IntegrityCheckResult;
@@ -33,7 +35,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -60,13 +61,14 @@
mEngine = new RuleEvaluationEngine(mIntegrityFileManager);
- when(mIntegrityFileManager.readRules(any())).thenReturn(new ArrayList<>());
+ when(mIntegrityFileManager.readRules(any())).thenReturn(Collections.singletonList(new Rule(
+ IntegrityFormula.Installer.notAllowedByManifest(), Rule.DENY)));
+
+ when(mIntegrityFileManager.initialized()).thenReturn(true);
}
@Test
public void testAllowedInstallers_empty() {
- Map<String, String> allowedInstallers = Collections.emptyMap();
-
AppInstallMetadata appInstallMetadata1 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
@@ -83,11 +85,11 @@
.setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
- assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
- assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
}
@@ -100,32 +102,36 @@
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
+ .setAllowedInstallersAndCert(allowedInstallers)
.build();
- assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
AppInstallMetadata appInstallMetadata2 =
getAppInstallMetadataBuilder()
.setInstallerName(RANDOM_INSTALLER)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
AppInstallMetadata appInstallMetadata3 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
AppInstallMetadata appInstallMetadata4 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
}
@@ -138,57 +144,37 @@
AppInstallMetadata appInstallMetadata1 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
AppInstallMetadata appInstallMetadata2 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_2)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
AppInstallMetadata appInstallMetadata3 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
AppInstallMetadata appInstallMetadata4 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_2)
+ .setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
- assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
- .isEqualTo(IntegrityCheckResult.Effect.DENY);
- }
-
- @Test
- public void manifestBasedRuleEvaluationWorksEvenWhenIntegrityFilesAreUnavailable() {
- when(mIntegrityFileManager.initialized()).thenReturn(false);
-
- Map<String, String> allowedInstallers =
- Collections.singletonMap(INSTALLER_1, INSTALLER_1_CERT);
-
- AppInstallMetadata appInstallMetadata1 =
- getAppInstallMetadataBuilder()
- .setInstallerName(INSTALLER_1)
- .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
- .build();
- assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
- .isEqualTo(IntegrityCheckResult.Effect.ALLOW);
-
- AppInstallMetadata appInstallMetadata2 =
- getAppInstallMetadataBuilder()
- .setInstallerName(RANDOM_INSTALLER)
- .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
- .build();
- assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
+ assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 498d888..6769faa 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -142,7 +142,8 @@
when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
when(mContext.getSystemServiceName(TelecomManager.class)).thenReturn(
Context.TELECOM_SERVICE);
- when(mTelecomManager.getDefaultDialerPackage(anyInt())).thenReturn(TEST_PKG_NAME);
+ when(mTelecomManager.getDefaultDialerPackage(any(UserHandle.class)))
+ .thenReturn(TEST_PKG_NAME);
when(mExecutorService.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(
TimeUnit.class))).thenReturn(mScheduledFuture);
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
index b273578..01d9dc0 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -189,7 +189,7 @@
private void addUsageEvents(UsageEvents.Event... events) {
UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
- anyBoolean(), anyBoolean())).thenReturn(usageEvents);
+ anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(usageEvents);
}
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index e768205..9e57763 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -145,6 +145,7 @@
static class MyInjector extends AppStandbyController.Injector {
long mElapsedRealtime;
boolean mIsAppIdleEnabled = true;
+ boolean mIsCharging;
List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
boolean mDisplayOn;
DisplayManager.DisplayListener mDisplayListener;
@@ -179,6 +180,11 @@
}
@Override
+ boolean isCharging() {
+ return mIsCharging;
+ }
+
+ @Override
boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
return mPowerSaveWhitelistExceptIdle.contains(packageName);
}
@@ -281,6 +287,13 @@
} catch (PackageManager.NameNotFoundException nnfe) {}
}
+ private void setChargingState(AppStandbyController controller, boolean charging) {
+ mInjector.mIsCharging = charging;
+ if (controller != null) {
+ controller.setChargingState(charging);
+ }
+ }
+
private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) {
mInjector.mIsAppIdleEnabled = enabled;
if (controller != null) {
@@ -297,6 +310,7 @@
controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mInjector.setDisplayOn(false);
mInjector.setDisplayOn(true);
+ setChargingState(controller, false);
controller.checkIdleStates(USER_ID);
assertNotEquals(STANDBY_BUCKET_EXEMPTED,
controller.getAppStandbyBucket(PACKAGE_1, USER_ID,
@@ -324,6 +338,46 @@
mInjector.mElapsedRealtime, false));
}
+ @Test
+ public void testIsAppIdle_Charging() throws Exception {
+ setChargingState(mController, false);
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_FORCED_BY_SYSTEM);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setChargingState(mController, true);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setChargingState(mController, false);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ }
+
+ @Test
+ public void testIsAppIdle_Enabled() throws Exception {
+ setChargingState(mController, false);
+ setAppIdleEnabled(mController, true);
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_FORCED_BY_SYSTEM);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setAppIdleEnabled(mController, false);
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+
+ setAppIdleEnabled(mController, true);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+ assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false));
+ }
+
private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
mInjector.mElapsedRealtime = elapsedTime;
controller.checkIdleStates(USER_ID);
diff --git a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml
index f5dbf43..98572d4 100644
--- a/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml
+++ b/services/tests/servicestests/test-apps/AppIntegrityManagerServiceTestApp/AndroidManifest.xml
@@ -22,7 +22,7 @@
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="28" />
<application android:hasCode="false">
- <meta-data android:name="allowed-installers" android:value="com.android.vending|play_store_cert,adb|"/>
+ <meta-data android:name="allowed-installers" android:value="com.android.vending|play_store_cert,adb"/>
</application>
</manifest>
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 a0f7f5b..75efdd7 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5373,21 +5373,7 @@
}
@Test
- public void testCancelAllNotifications_cancelsBubble() throws Exception {
- final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
- nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
- mService.addNotification(nr);
-
- mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
- waitForIdle();
-
- StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
- assertEquals(0, notifs.length);
- assertEquals(0, mService.getNotificationRecordCount());
- }
-
- @Test
- public void testAppCancelNotifications_cancelsBubbles() throws Exception {
+ public void testCancelNotificationsFromApp_cancelsBubbles() throws Exception {
final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE;
@@ -5413,6 +5399,20 @@
}
@Test
+ public void testCancelAllNotificationsFromApp_cancelsBubble() throws Exception {
+ final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
+ mService.addNotification(nr);
+
+ mBinderService.cancelAllNotifications(PKG, nr.getSbn().getUserId());
+ waitForIdle();
+
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(0, notifs.length);
+ assertEquals(0, mService.getNotificationRecordCount());
+ }
+
+ @Test
public void testCancelAllNotificationsFromListener_ignoresBubbles() throws Exception {
final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel);
final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel);
@@ -5448,6 +5448,25 @@
}
@Test
+ public void testCancelAllNotificationsFromStatusBar_ignoresBubble() throws Exception {
+ // GIVEN a notification bubble
+ final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ nr.getSbn().getNotification().flags |= FLAG_BUBBLE;
+ mService.addNotification(nr);
+
+ // WHEN the status bar clears all notifications
+ mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+ nr.getSbn().getUserId());
+ waitForIdle();
+
+ // THEN the bubble notification does not get removed
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifs.length);
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
+
+
+ @Test
public void testGetAllowedAssistantAdjustments() throws Exception {
List<String> capabilities = mBinderService.getAllowedAssistantAdjustments(null);
assertNotNull(capabilities);
@@ -6105,6 +6124,26 @@
}
@Test
+ public void testNotificationBubbles_bubbleStays_whenClicked()
+ throws Exception {
+ // GIVEN a notification that has the auto cancels flag (cancel on click) and is a bubble
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
+ final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ nr.getSbn().getNotification().flags |= FLAG_BUBBLE | FLAG_AUTO_CANCEL;
+ mService.addNotification(nr);
+
+ // WHEN we click the notification
+ final NotificationVisibility nv = NotificationVisibility.obtain(nr.getKey(), 1, 2, true);
+ mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+ nr.getKey(), nv);
+ waitForIdle();
+
+ // THEN the bubble should still exist
+ StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsAfter.length);
+ }
+
+ @Test
public void testLoadDefaultApprovedServices_emptyResources() {
TestableResources tr = mContext.getOrCreateTestableResources();
tr.addOverride(com.android.internal.R.string.config_defaultListenerAccessPackages, "");
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 1371481..420695d 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -491,6 +491,15 @@
return true; // hide by default if we can't verify visibility
}
+ private boolean shouldHideLocusIdEvents(int callingPid, int callingUid) {
+ if (callingUid == Process.SYSTEM_UID) {
+ return false;
+ }
+ return !(getContext().checkPermission(
+ android.Manifest.permission.ACCESS_LOCUS_ID_USAGE_STATS, callingPid, callingUid)
+ == PackageManager.PERMISSION_GRANTED);
+ }
+
private static void deleteRecursively(File f) {
File[] files = f.listFiles();
if (files != null) {
@@ -1030,7 +1039,8 @@
* Called by the Binder stub.
*/
UsageEvents queryEvents(int userId, long beginTime, long endTime,
- boolean shouldObfuscateInstantApps, boolean shouldHideShortcutInvocationEvents) {
+ boolean shouldObfuscateInstantApps, boolean shouldHideShortcutInvocationEvents,
+ boolean shouldHideLocusIdEvents) {
synchronized (mLock) {
if (!mUserUnlockedStates.get(userId)) {
Slog.w(TAG, "Failed to query events for locked user " + userId);
@@ -1042,7 +1052,7 @@
return null; // user was stopped or removed
}
return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps,
- shouldHideShortcutInvocationEvents);
+ shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents);
}
}
@@ -1465,8 +1475,10 @@
try {
final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
userId, callingPackage, callingPid, callingUid);
+ boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
- obfuscateInstantApps, hideShortcutInvocationEvents);
+ obfuscateInstantApps, hideShortcutInvocationEvents,
+ shouldHideLocusIdEvents);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1513,8 +1525,10 @@
try {
final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents(
userId, callingPackage, callingPid, callingUid);
+ boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid);
return UsageStatsService.this.queryEvents(userId, beginTime, endTime,
- obfuscateInstantApps, hideShortcutInvocationEvents);
+ obfuscateInstantApps, hideShortcutInvocationEvents,
+ shouldHideLocusIdEvents);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2131,9 +2145,11 @@
@Override
public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
- boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents) {
+ boolean obfuscateInstantApps, boolean shouldHideShortcutInvocationEvents,
+ boolean shouldHideLocusIdEvents) {
return UsageStatsService.this.queryEvents(
- userId, beginTime, endTime, obfuscateInstantApps, hideShortcutInvocationEvents);
+ userId, beginTime, endTime, obfuscateInstantApps,
+ shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents);
}
@Override
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 4d71112..d9317ac 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -481,8 +481,8 @@
return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner);
}
- UsageEvents queryEvents(final long beginTime, final long endTime,
- boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents) {
+ UsageEvents queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps,
+ boolean hideShortcutInvocationEvents, boolean hideLocusIdEvents) {
if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
return null;
}
@@ -504,6 +504,10 @@
&& event.mEventType == Event.SHORTCUT_INVOCATION) {
continue;
}
+ if (hideLocusIdEvents
+ && event.mEventType == Event.LOCUS_ID_SET) {
+ continue;
+ }
if (obfuscateInstantApps) {
event = event.getObfuscatedIfInstantApp();
}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index ec99f36..52213d8 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -17,6 +17,7 @@
package android.telecom;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -458,8 +459,14 @@
/** Call supports the deflect feature. */
public static final int CAPABILITY_SUPPORT_DEFLECT = 0x01000000;
+ /**
+ * Call supports adding participants to the call via
+ * {@link #addConferenceParticipants(List)}.
+ * @hide
+ */
+ public static final int CAPABILITY_ADD_PARTICIPANT = 0x02000000;
//******************************************************************************************
- // Next CAPABILITY value: 0x02000000
+ // Next CAPABILITY value: 0x04000000
//******************************************************************************************
/**
@@ -539,7 +546,7 @@
*
* @see TelecomManager#EXTRA_USE_ASSISTED_DIALING
*/
- public static final int PROPERTY_ASSISTED_DIALING_USED = 0x00000200;
+ public static final int PROPERTY_ASSISTED_DIALING = 0x00000200;
/**
* Indicates that the call is an RTT call. Use {@link #getRttCall()} to get the
@@ -689,6 +696,9 @@
if (can(capabilities, CAPABILITY_SUPPORT_DEFLECT)) {
builder.append(" CAPABILITY_SUPPORT_DEFLECT");
}
+ if (can(capabilities, CAPABILITY_ADD_PARTICIPANT)) {
+ builder.append(" CAPABILITY_ADD_PARTICIPANT");
+ }
builder.append("]");
return builder.toString();
}
@@ -744,7 +754,7 @@
if (hasProperty(properties, PROPERTY_HAS_CDMA_VOICE_PRIVACY)) {
builder.append(" PROPERTY_HAS_CDMA_VOICE_PRIVACY");
}
- if (hasProperty(properties, PROPERTY_ASSISTED_DIALING_USED)) {
+ if (hasProperty(properties, PROPERTY_ASSISTED_DIALING)) {
builder.append(" PROPERTY_ASSISTED_DIALING_USED");
}
if (hasProperty(properties, PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL)) {
@@ -1703,6 +1713,17 @@
}
/**
+ * Pulls participants to existing call by forming a conference call.
+ * See {@link Details#CAPABILITY_ADD_PARTICIPANT}.
+ *
+ * @param participants participants to be pulled to existing call.
+ * @hide
+ */
+ public void addConferenceParticipants(@NonNull List<Uri> participants) {
+ mInCallAdapter.addConferenceParticipants(mTelecomCallId, participants);
+ }
+
+ /**
* Initiates a request to the {@link ConnectionService} to pull an external call to the local
* device.
* <p>
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 56acdff..f019a9d 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -16,8 +16,13 @@
package android.telecom;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.net.Uri;
@@ -319,6 +324,13 @@
public void onConnectionAdded(Connection connection) {}
/**
+ * Notifies the {@link Conference} of a request to add a new participants to the conference call
+ * @param participants that will be added to this conference call
+ * @hide
+ */
+ public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
+
+ /**
* Notifies this Conference, which is in {@code STATE_RINGING}, of
* a request to accept.
* For managed {@link ConnectionService}s, this will be called when the user answers a call via
@@ -625,12 +637,12 @@
* Should be specified in wall-clock time returned by {@link System#currentTimeMillis()}.
* <p>
* When setting the connection time, you should always set the connection elapsed time via
- * {@link #setConnectionStartElapsedRealTime(long)} to ensure the duration is reflected.
+ * {@link #setConnectionStartElapsedRealtimeMillis(long)} to ensure the duration is reflected.
*
* @param connectionTimeMillis The connection time, in milliseconds, as returned by
* {@link System#currentTimeMillis()}.
*/
- public final void setConnectionTime(long connectionTimeMillis) {
+ public final void setConnectionTime(@IntRange(from = 0) long connectionTimeMillis) {
mConnectTimeMillis = connectionTimeMillis;
}
@@ -646,8 +658,28 @@
*
* @param connectionStartElapsedRealTime The connection time, as measured by
* {@link SystemClock#elapsedRealtime()}.
+ * @deprecated use {@link #setConnectionStartElapsedRealtimeMillis(long)} instead.
*/
+ @Deprecated
public final void setConnectionStartElapsedRealTime(long connectionStartElapsedRealTime) {
+ setConnectionStartElapsedRealtimeMillis(connectionStartElapsedRealTime);
+ }
+
+ /**
+ * Sets the start time of the {@link Conference} which is the basis for the determining the
+ * duration of the {@link Conference}.
+ * <p>
+ * You should use a value returned by {@link SystemClock#elapsedRealtime()} to ensure that time
+ * zone changes do not impact the conference duration.
+ * <p>
+ * When setting this, you should also set the connection time via
+ * {@link #setConnectionTime(long)}.
+ *
+ * @param connectionStartElapsedRealTime The connection time, as measured by
+ * {@link SystemClock#elapsedRealtime()}.
+ */
+ public final void setConnectionStartElapsedRealtimeMillis(
+ @ElapsedRealtimeLong long connectionStartElapsedRealTime) {
mConnectionStartElapsedRealTime = connectionStartElapsedRealTime;
}
@@ -668,7 +700,7 @@
*
* @return The time at which the {@code Conference} was connected.
*/
- public final long getConnectionTime() {
+ public final @IntRange(from = 0) long getConnectionTime() {
return mConnectTimeMillis;
}
@@ -685,11 +717,8 @@
* has no general use other than to the Telephony framework.
*
* @return The elapsed time at which the {@link Conference} was connected.
- * @hide
*/
- @SystemApi
- @TestApi
- public final long getConnectionStartElapsedRealTime() {
+ public final @ElapsedRealtimeLong long getConnectionStartElapsedRealtimeMillis() {
return mConnectionStartElapsedRealTime;
}
@@ -987,6 +1016,7 @@
*/
@SystemApi
@TestApi
+ @RequiresPermission(MODIFY_PHONE_STATE)
public void setConferenceState(boolean isConference) {
for (Listener l : mListeners) {
l.onConferenceStateChanged(this, isConference);
@@ -1007,6 +1037,7 @@
*/
@SystemApi
@TestApi
+ @RequiresPermission(MODIFY_PHONE_STATE)
public final void setAddress(@NonNull Uri address,
@TelecomManager.Presentation int presentation) {
Log.d(this, "setAddress %s", address);
@@ -1113,12 +1144,52 @@
}
/**
- * Sends an event associated with this {@code Conference} with associated event extras to the
- * {@link InCallService} (note: this is identical in concept to
- * {@link Connection#sendConnectionEvent(String, Bundle)}).
- * @see Connection#sendConnectionEvent(String, Bundle)
+ * Sends an event associated with this {@link Conference} with associated event extras to the
+ * {@link InCallService}.
+ * <p>
+ * Connection events are used to communicate point in time information from a
+ * {@link ConnectionService} to an {@link InCallService} implementation. An example of a
+ * custom connection event includes notifying the UI when a WIFI call has been handed over to
+ * LTE, which the InCall UI might use to inform the user that billing charges may apply. The
+ * Android Telephony framework will send the {@link Connection#EVENT_MERGE_COMPLETE}
+ * connection event when a call to {@link Call#mergeConference()} has completed successfully.
+ * <p>
+ * Events are exposed to {@link InCallService} implementations via
+ * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+ * <p>
+ * No assumptions should be made as to how an In-Call UI or service will handle these events.
+ * The {@link ConnectionService} must assume that the In-Call UI could even chose to ignore
+ * some events altogether.
+ * <p>
+ * Events should be fully qualified (e.g. {@code com.example.event.MY_EVENT}) to avoid
+ * conflicts between {@link ConnectionService} implementations. Further, custom
+ * {@link ConnectionService} implementations shall not re-purpose events in the
+ * {@code android.*} namespace, nor shall they define new event types in this namespace. When
+ * defining a custom event type, ensure the contents of the extras {@link Bundle} is clearly
+ * defined. Extra keys for this bundle should be named similar to the event type (e.g.
+ * {@code com.example.extra.MY_EXTRA}).
+ * <p>
+ * When defining events and the associated extras, it is important to keep their behavior
+ * consistent when the associated {@link ConnectionService} is updated. Support for deprecated
+ * events/extras should me maintained to ensure backwards compatibility with older
+ * {@link InCallService} implementations which were built to support the older behavior.
+ * <p>
+ * Expected connection events from the Telephony stack are:
+ * <p>
+ * <ul>
+ * <li>{@link Connection#EVENT_CALL_HOLD_FAILED} with {@code null} {@code extras} when the
+ * {@link Conference} could not be held.</li>
+ * <li>{@link Connection#EVENT_MERGE_START} with {@code null} {@code extras} when a new
+ * call is being merged into the conference.</li>
+ * <li>{@link Connection#EVENT_MERGE_COMPLETE} with {@code null} {@code extras} a new call
+ * has completed being merged into the conference.</li>
+ * <li>{@link Connection#EVENT_CALL_MERGE_FAILED} with {@code null} {@code extras} a new
+ * call has failed to merge into the conference (the dialer app can determine which call
+ * failed to merge based on the fact that the call still exists outside of the conference
+ * at the end of the merge process).</li>
+ * </ul>
*
- * @param event The connection event.
+ * @param event The conference event.
* @param extras Optional bundle containing extra information associated with the event.
*/
public void sendConferenceEvent(@NonNull String event, @Nullable Bundle extras) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 8049459..3b0ba25 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -16,9 +16,14 @@
package android.telecom;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Notification;
@@ -376,8 +381,14 @@
/** Call supports the deflect feature. */
public static final int CAPABILITY_SUPPORT_DEFLECT = 0x02000000;
+ /**
+ * When set, indicates that this {@link Connection} supports initiation of a conference call
+ * by directly adding participants using {@link #onAddConferenceParticipants(List)}.
+ * @hide
+ */
+ public static final int CAPABILITY_ADD_PARTICIPANT = 0x04000000;
//**********************************************************************************************
- // Next CAPABILITY value: 0x04000000
+ // Next CAPABILITY value: 0x08000000
//**********************************************************************************************
/**
@@ -474,7 +485,7 @@
*
* @see TelecomManager#EXTRA_USE_ASSISTED_DIALING
*/
- public static final int PROPERTY_ASSISTED_DIALING_USED = 1 << 9;
+ public static final int PROPERTY_ASSISTED_DIALING = 1 << 9;
/**
* Set by the framework to indicate that the network has identified a Connection as an emergency
@@ -953,7 +964,9 @@
if ((capabilities & CAPABILITY_SUPPORT_DEFLECT) == CAPABILITY_SUPPORT_DEFLECT) {
builder.append(isLong ? " CAPABILITY_SUPPORT_DEFLECT" : " sup_def");
}
-
+ if ((capabilities & CAPABILITY_ADD_PARTICIPANT) == CAPABILITY_ADD_PARTICIPANT) {
+ builder.append(isLong ? " CAPABILITY_ADD_PARTICIPANT" : " add_participant");
+ }
builder.append("]");
return builder.toString();
}
@@ -2109,19 +2122,24 @@
*/
@SystemApi
@TestApi
- public final long getConnectTimeMillis() {
+ public final @IntRange(from = 0) long getConnectTimeMillis() {
return mConnectTimeMillis;
}
/**
* Retrieves the connection start time of the {@link Connection}, if specified. A value of
* {@link Conference#CONNECT_TIME_NOT_SPECIFIED} indicates that Telecom should determine the
- * start time of the conference.
+ * start time of the connection.
* <p>
* Based on the value of {@link SystemClock#elapsedRealtime()}, which ensures that wall-clock
* changes do not impact the call duration.
* <p>
* Used internally in Telephony when migrating conference participant data for IMS conferences.
+ * <p>
+ * The value returned is the same one set using
+ * {@link #setConnectionStartElapsedRealtimeMillis(long)}. This value is never updated from
+ * the Telecom framework, so no permission enforcement occurs when retrieving the value with
+ * this method.
*
* @return The time at which the {@link Connection} was connected.
*
@@ -2129,7 +2147,7 @@
*/
@SystemApi
@TestApi
- public final long getConnectElapsedTimeMillis() {
+ public final @ElapsedRealtimeLong long getConnectionStartElapsedRealtimeMillis() {
return mConnectElapsedTimeMillis;
}
@@ -2550,6 +2568,9 @@
* Sets the time at which a call became active on this Connection. This is set only
* when a conference call becomes active on this connection.
* <p>
+ * This time corresponds to the date/time of connection and is stored in the call log in
+ * {@link android.provider.CallLog.Calls#DATE}.
+ * <p>
* Used by telephony to maintain calls associated with an IMS Conference.
*
* @param connectTimeMillis The connection time, in milliseconds. Should be set using a value
@@ -2559,7 +2580,8 @@
*/
@SystemApi
@TestApi
- public final void setConnectTimeMillis(long connectTimeMillis) {
+ @RequiresPermission(MODIFY_PHONE_STATE)
+ public final void setConnectTimeMillis(@IntRange(from = 0) long connectTimeMillis) {
mConnectTimeMillis = connectTimeMillis;
}
@@ -2567,15 +2589,23 @@
* Sets the time at which a call became active on this Connection. This is set only
* when a conference call becomes active on this connection.
* <p>
+ * This time is used to establish the duration of a call. It uses
+ * {@link SystemClock#elapsedRealtime()} to ensure that the call duration is not impacted by
+ * time zone changes during a call. The difference between the current
+ * {@link SystemClock#elapsedRealtime()} and the value set at the connection start time is used
+ * to populate {@link android.provider.CallLog.Calls#DURATION} in the call log.
+ * <p>
* Used by telephony to maintain calls associated with an IMS Conference.
+ *
* @param connectElapsedTimeMillis The connection time, in milliseconds. Stored in the format
* {@link SystemClock#elapsedRealtime()}.
- *
* @hide
*/
@SystemApi
@TestApi
- public final void setConnectionStartElapsedRealTime(long connectElapsedTimeMillis) {
+ @RequiresPermission(MODIFY_PHONE_STATE)
+ public final void setConnectionStartElapsedRealtimeMillis(
+ @ElapsedRealtimeLong long connectElapsedTimeMillis) {
mConnectElapsedTimeMillis = connectElapsedTimeMillis;
}
@@ -2953,6 +2983,14 @@
public void onSeparate() {}
/**
+ * Supports initiation of a conference call by directly adding participants to an ongoing call.
+ *
+ * @param participants with which conference call will be formed.
+ * @hide
+ */
+ public void onAddConferenceParticipants(@NonNull List<Uri> participants) {}
+
+ /**
* Notifies this Connection of a request to abort.
*/
public void onAbort() {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 00c2918..2aea723 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
@@ -142,6 +141,7 @@
private static final String SESSION_SPLIT_CONFERENCE = "CS.sFC";
private static final String SESSION_MERGE_CONFERENCE = "CS.mC";
private static final String SESSION_SWAP_CONFERENCE = "CS.sC";
+ private static final String SESSION_ADD_PARTICIPANT = "CS.aP";
private static final String SESSION_POST_DIAL_CONT = "CS.oPDC";
private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
@@ -195,6 +195,7 @@
private static final int MSG_CREATE_CONFERENCE_COMPLETE = 36;
private static final int MSG_CREATE_CONFERENCE_FAILED = 37;
private static final int MSG_REJECT_WITH_REASON = 38;
+ private static final int MSG_ADD_PARTICIPANT = 39;
private static Connection sNullConnection;
@@ -627,6 +628,21 @@
}
@Override
+ public void addConferenceParticipants(String callId, List<Uri> participants,
+ Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_ADD_PARTICIPANT);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = participants;
+ args.arg3 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_ADD_PARTICIPANT, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void onPostDialContinue(String callId, boolean proceed, Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_POST_DIAL_CONT);
try {
@@ -1224,6 +1240,19 @@
}
break;
}
+ case MSG_ADD_PARTICIPANT: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ Log.continueSession((Session) args.arg3,
+ SESSION_HANDLER + SESSION_ADD_PARTICIPANT);
+ addConferenceParticipants((String) args.arg1, (List<Uri>)args.arg2);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
+
case MSG_ON_POST_DIAL_CONTINUE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@@ -1778,7 +1807,7 @@
null : conference.getVideoProvider().getInterface(),
conference.getVideoState(),
conference.getConnectTimeMillis(),
- conference.getConnectionStartElapsedRealTime(),
+ conference.getConnectionStartElapsedRealtimeMillis(),
conference.getStatusHints(),
conference.getExtras(),
conference.getAddress(),
@@ -1884,7 +1913,7 @@
connection.isRingbackRequested(),
connection.getAudioModeIsVoip(),
connection.getConnectTimeMillis(),
- connection.getConnectElapsedTimeMillis(),
+ connection.getConnectionStartElapsedRealtimeMillis(),
connection.getStatusHints(),
connection.getDisconnectCause(),
createIdList(connection.getConferenceables()),
@@ -2152,6 +2181,17 @@
}
}
+ private void addConferenceParticipants(String callId, List<Uri> participants) {
+ Log.d(this, "addConferenceParticipants(%s)", callId);
+ if (mConnectionById.containsKey(callId)) {
+ findConnectionForAction(callId, "addConferenceParticipants")
+ .onAddConferenceParticipants(participants);
+ } else {
+ findConferenceForAction(callId, "addConferenceParticipants")
+ .onAddConferenceParticipants(participants);
+ }
+ }
+
/**
* Notifies a {@link Connection} of a request to pull an external call.
*
@@ -2374,7 +2414,7 @@
null : conference.getVideoProvider().getInterface(),
conference.getVideoState(),
conference.getConnectTimeMillis(),
- conference.getConnectionStartElapsedRealTime(),
+ conference.getConnectionStartElapsedRealtimeMillis(),
conference.getStatusHints(),
conference.getExtras(),
conference.getAddress(),
@@ -2465,7 +2505,7 @@
connection.isRingbackRequested(),
connection.getAudioModeIsVoip(),
connection.getConnectTimeMillis(),
- connection.getConnectElapsedTimeMillis(),
+ connection.getConnectionStartElapsedRealtimeMillis(),
connection.getStatusHints(),
connection.getDisconnectCause(),
emptyList,
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 594c1eb..9d291740 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -283,6 +283,20 @@
}
/**
+ * Instructs Telecom to pull participants to existing call
+ *
+ * @param callId The unique ID of the call.
+ * @param participants participants to be pulled to existing call.
+ */
+ public void addConferenceParticipants(String callId, List<Uri> participants) {
+ try {
+ mAdapter.addConferenceParticipants(callId, participants);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+
+ /**
* Instructs Telecom to split the specified call from any conference call with which it may be
* connected.
*
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index ebfa3a1..982e5f3 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -20,6 +20,7 @@
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
+import android.app.UiModeManager;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
@@ -43,12 +44,32 @@
* phone calls.
* <h2>Becoming the Default Phone App</h2>
* The default dialer/phone app is one which provides the in-call user interface while the device is
- * in a call. A device is bundled with a system provided default dialer/phone app. The user may
- * choose a single app to take over this role from the system app. An app which wishes to fulfill
- * one this role uses the {@code android.app.role.RoleManager} to request that they fill the role.
+ * in a call. It also provides the user with a means to initiate calls and see a history of calls
+ * on their device. A device is bundled with a system provided default dialer/phone app. The user
+ * may choose a single app to take over this role from the system app. An app which wishes to
+ * fulfill one this role uses the {@link android.app.role.RoleManager} to request that they fill the
+ * {@link android.app.role.RoleManager#ROLE_DIALER} role.
* <p>
- * An app filling the role of the default phone app provides a user interface while the device is in
- * a call, and the device is not in car mode.
+ * The default phone app provides a user interface while the device is in a call, and the device is
+ * not in car mode (i.e. {@link UiModeManager#getCurrentModeType()} is not
+ * {@link android.content.res.Configuration#UI_MODE_TYPE_CAR}).
+ * <p>
+ * In order to fill the {@link android.app.role.RoleManager#ROLE_DIALER} role, an app must meet a
+ * number of requirements:
+ * <ul>
+ * <li>It must handle the {@link Intent#ACTION_DIAL} intent. This means the app must provide
+ * a dial pad UI for the user to initiate outgoing calls.</li>
+ * <li>It must fully implement the {@link InCallService} API and provide both an incoming call
+ * UI, as well as an ongoing call UI.</li>
+ * </ul>
+ * <p>
+ * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} crashes during
+ * {@link InCallService} binding, the Telecom framework will automatically fall back to using the
+ * dialer app pre-loaded on the device. The system will display a notification to the user to let
+ * them know that the app has crashed and that their call was continued using the pre-loaded dialer
+ * app.
+ * <p>
+ * Further, the pre-loaded dialer will ALWAYS be used when the user places an emergency call.
* <p>
* Below is an example manifest registration for an {@code InCallService}. The meta-data
* {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular
@@ -82,6 +103,11 @@
* <action android:name="android.intent.action.DIAL" />
* <category android:name="android.intent.category.DEFAULT" />
* </intent-filter>
+ * <intent-filter>
+ * <action android:name="android.intent.action.DIAL" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:scheme="tel" />
+ * </intent-filter>
* </activity>
* }
* </pre>
@@ -111,6 +137,7 @@
* }
* }
* }
+ * }
* </pre>
* <p id="incomingCallNotification">
* <h3>Showing the Incoming Call Notification</h3>
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index f00432b..4e6e1a5 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -16,7 +16,10 @@
package android.telecom;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Intent;
@@ -614,7 +617,8 @@
* time. By default, there is no group Id for a {@link PhoneAccount} (an empty String). Only
* grouped {@link PhoneAccount}s with the same {@link ConnectionService} can be replaced.
* <p>
- * Note: This is an API specific to the Telephony stack.
+ * Note: This is an API specific to the Telephony stack; the group Id will be ignored for
+ * callers not holding the correct permission.
*
* @param groupId The group Id of the {@link PhoneAccount} that will replace any other
* registered {@link PhoneAccount} in Telecom with the same Group Id.
@@ -623,6 +627,7 @@
*/
@SystemApi
@TestApi
+ @RequiresPermission(MODIFY_PHONE_STATE)
public @NonNull Builder setGroupId(@NonNull String groupId) {
if (groupId != null) {
mGroupId = groupId;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index a28cc4f..5d7d649 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -819,8 +819,8 @@
* automatically add dialing prefixes when placing international calls.
* <p>
* Setting this extra on the outgoing call extras will cause the
- * {@link Connection#PROPERTY_ASSISTED_DIALING_USED} property and
- * {@link Call.Details#PROPERTY_ASSISTED_DIALING_USED} property to be set on the
+ * {@link Connection#PROPERTY_ASSISTED_DIALING} property and
+ * {@link Call.Details#PROPERTY_ASSISTED_DIALING} property to be set on the
* {@link Connection}/{@link Call} in question. When the call is logged to the call log, the
* {@link android.provider.CallLog.Calls#FEATURES_ASSISTED_DIALING_USED} call feature is set to
* indicate that assisted dialing was used for the call.
@@ -1412,7 +1412,7 @@
/**
* Used to determine the currently selected default dialer package for a specific user.
*
- * @param userId the user id to query the default dialer package for.
+ * @param userHandle the user id to query the default dialer package for.
* @return package name for the default dialer package or null if no package has been
* selected as the default dialer.
* @hide
@@ -1420,10 +1420,11 @@
@SystemApi
@TestApi
@RequiresPermission(READ_PRIVILEGED_PHONE_STATE)
- public @Nullable String getDefaultDialerPackage(int userId) {
+ public @Nullable String getDefaultDialerPackage(@NonNull UserHandle userHandle) {
try {
if (isServiceConnected()) {
- return getTelecomService().getDefaultDialerPackageForUser(userId);
+ return getTelecomService().getDefaultDialerPackageForUser(
+ userHandle.getIdentifier());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException attempting to get the default dialer package name.", e);
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 4249dff..a397d77 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -104,6 +104,9 @@
void swapConference(String conferenceCallId, in Session.Info sessionInfo);
+ void addConferenceParticipants(String CallId, in List<Uri> participants,
+ in Session.Info sessionInfo);
+
void onPostDialContinue(String callId, boolean proceed, in Session.Info sessionInfo);
void pullExternalCall(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index eb2d714..9beff22 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -67,6 +67,8 @@
void swapConference(String callId);
+ void addConferenceParticipants(String callId, in List<Uri> participants);
+
void turnOnProximitySensor();
void turnOffProximitySensor(boolean screenOnImmediately);
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index d2a5905..a27c480 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -661,4 +661,16 @@
})
@Retention(RetentionPolicy.SOURCE)
public @interface Skip464XlatStatus {}
+
+ /**
+ * Override network type
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "OVERRIDE_NETWORK_TYPE_", value = {
+ DisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+ DisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
+ DisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
+ DisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+ DisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE})
+ public @interface OverrideNetworkType {}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ebb53c5..51b4a31 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1090,6 +1090,14 @@
"support_adhoc_conference_calls_bool";
/**
+ * Determines whether conference participants can be added to existing call. When {@code true},
+ * adding conference participants to existing call is supported, {@code false otherwise}.
+ * @hide
+ */
+ public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL =
+ "support_add_conference_participants_bool";
+
+ /**
* Determines whether conference calls are supported by a carrier. When {@code true},
* conference calling is supported, {@code false otherwise}.
*/
@@ -4004,6 +4012,7 @@
sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false);
sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL, false);
+ sDefaults.putBoolean(KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index ec86c14..bfa209b 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -16,9 +16,9 @@
package android.telephony;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.radio.V1_4.CellInfo.Info;
import android.os.Parcel;
@@ -178,18 +178,18 @@
/**
* Approximate time this cell information was received from the modem.
*
- * @return a time stamp in nanos since boot.
+ * @return a time stamp in millis since boot.
*/
- @SuppressLint("MethodNameUnits")
- public long getTimestampNanos() {
- return mTimeStamp;
+ @ElapsedRealtimeLong
+ public long getTimestampMillis() {
+ return mTimeStamp / 1000000;
}
/**
* Approximate time this cell information was received from the modem.
*
* @return a time stamp in nanos since boot.
- * @deprecated Use {@link #getTimestampNanos} instead.
+ * @deprecated Use {@link #getTimestampMillis} instead.
*/
@Deprecated
public long getTimeStamp() {
diff --git a/telephony/java/android/telephony/DisplayInfo.aidl b/telephony/java/android/telephony/DisplayInfo.aidl
new file mode 100644
index 0000000..861b0fe
--- /dev/null
+++ b/telephony/java/android/telephony/DisplayInfo.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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.telephony;
+
+parcelable DisplayInfo;
diff --git a/telephony/java/android/telephony/DisplayInfo.java b/telephony/java/android/telephony/DisplayInfo.java
new file mode 100644
index 0000000..d54bcf9
--- /dev/null
+++ b/telephony/java/android/telephony/DisplayInfo.java
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation.OverrideNetworkType;
+
+import java.util.Objects;
+
+/**
+ * DisplayInfo contains telephony-related information used for display purposes only. This
+ * information is provided in accordance with carrier policy and branding preferences; it is not
+ * necessarily a precise or accurate representation of the current state and should be treated
+ * accordingly.
+ */
+public final class DisplayInfo implements Parcelable {
+ /**
+ * No override. {@link #getNetworkType()} should be used for display network
+ * type.
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_NONE = 0;
+
+ /**
+ * Override network type when the device is connected to
+ * {@link TelephonyManager#NETWORK_TYPE_LTE} cellular network and is using carrier aggregation.
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1;
+
+ /**
+ * Override network type when the device is connected to advanced pro
+ * {@link TelephonyManager#NETWORK_TYPE_LTE} cellular network.
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2;
+
+ /**
+ * Override network type when the device is connected to
+ * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
+ * capability or is currently connected to the secondary
+ * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network.
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3;
+
+ /**
+ * Override network type when the device is connected to
+ * {@link TelephonyManager#NETWORK_TYPE_LTE} network and has E-UTRA-NR Dual Connectivity(EN-DC)
+ * capability or is currently connected to the secondary
+ * {@link TelephonyManager#NETWORK_TYPE_NR} cellular network on millimeter wave bands.
+ *
+ * @see AccessNetworkConstants.NgranBands#FREQUENCY_RANGE_GROUP_2
+ */
+ public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4;
+
+ @NetworkType
+ private final int mNetworkType;
+
+ @OverrideNetworkType
+ private final int mOverrideNetworkType;
+
+ /**
+ * Constructor
+ *
+ * @param networkType Current packet-switching cellular network type
+ * @param overrideNetworkType The override network type
+ *
+ * @hide
+ */
+ public DisplayInfo(@NetworkType int networkType, @OverrideNetworkType int overrideNetworkType) {
+ mNetworkType = networkType;
+ mOverrideNetworkType = overrideNetworkType;
+ }
+
+ /** @hide */
+ public DisplayInfo(Parcel p) {
+ mNetworkType = p.readInt();
+ mOverrideNetworkType = p.readInt();
+ }
+
+ /**
+ * Get current packet-switching cellular network type. This is the actual network type the
+ * device is camped on.
+ *
+ * @return The network type.
+ */
+ @NetworkType
+ public int getNetworkType() {
+ return mNetworkType;
+ }
+
+ /**
+ * Get the override network type. Note the override network type is for market branding
+ * or visualization purposes only. It cannot be treated as the actual network type device is
+ * camped on.
+ *
+ * @return The override network type.
+ */
+ @OverrideNetworkType
+ public int getOverrideNetworkType() {
+ return mOverrideNetworkType;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mNetworkType);
+ dest.writeInt(mOverrideNetworkType);
+ }
+
+ public static final @NonNull Parcelable.Creator<DisplayInfo> CREATOR =
+ new Parcelable.Creator<DisplayInfo>() {
+ @Override
+ public DisplayInfo createFromParcel(Parcel source) {
+ return new DisplayInfo(source);
+ }
+
+ @Override
+ public DisplayInfo[] newArray(int size) {
+ return new DisplayInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DisplayInfo that = (DisplayInfo) o;
+ return mNetworkType == that.mNetworkType
+ && mOverrideNetworkType == that.mOverrideNetworkType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNetworkType, mOverrideNetworkType);
+ }
+
+ private static String overrideNetworkTypeToString(@OverrideNetworkType int type) {
+ switch (type) {
+ case OVERRIDE_NETWORK_TYPE_NONE: return "NONE";
+ case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
+ case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
+ case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
+ case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+ default: return "UNKNOWN";
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "DisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
+ + ", override=" + overrideNetworkTypeToString(mOverrideNetworkType);
+ }
+}
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 54980a2..250d9e8 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -256,337 +256,6 @@
/** Access Blocked by CDMA network. */
public static final int CDMA_ACCESS_BLOCKED = 1009;
- /** Mapped from ImsReasonInfo */
- // TODO: remove ImsReasonInfo from preciseDisconnectCause
- /* The passed argument is an invalid */
- /** @hide */
- public static final int LOCAL_ILLEGAL_ARGUMENT = 1200;
- // The operation is invoked in invalid call state
- /** @hide */
- public static final int LOCAL_ILLEGAL_STATE = 1201;
- // IMS service internal error
- /** @hide */
- public static final int LOCAL_INTERNAL_ERROR = 1202;
- // IMS service goes down (service connection is lost)
- /** @hide */
- public static final int LOCAL_IMS_SERVICE_DOWN = 1203;
- // No pending incoming call exists
- /** @hide */
- public static final int LOCAL_NO_PENDING_CALL = 1204;
- // Service unavailable; by power off
- /** @hide */
- public static final int LOCAL_POWER_OFF = 1205;
- // Service unavailable; by low battery
- /** @hide */
- public static final int LOCAL_LOW_BATTERY = 1206;
- // Service unavailable; by out of service (data service state)
- /** @hide */
- public static final int LOCAL_NETWORK_NO_SERVICE = 1207;
- /* Service unavailable; by no LTE coverage
- * (VoLTE is not supported even though IMS is registered)
- */
- /** @hide */
- public static final int LOCAL_NETWORK_NO_LTE_COVERAGE = 1208;
- /** Service unavailable; by located in roaming area */
- /** @hide */
- public static final int LOCAL_NETWORK_ROAMING = 1209;
- /** Service unavailable; by IP changed */
- /** @hide */
- public static final int LOCAL_NETWORK_IP_CHANGED = 1210;
- /** Service unavailable; other */
- /** @hide */
- public static final int LOCAL_SERVICE_UNAVAILABLE = 1211;
- /* Service unavailable; IMS connection is lost (IMS is not registered) */
- /** @hide */
- public static final int LOCAL_NOT_REGISTERED = 1212;
- /** Max call exceeded */
- /** @hide */
- public static final int LOCAL_MAX_CALL_EXCEEDED = 1213;
- /** Call decline */
- /** @hide */
- public static final int LOCAL_CALL_DECLINE = 1214;
- /** SRVCC is in progress */
- /** @hide */
- public static final int LOCAL_CALL_VCC_ON_PROGRESSING = 1215;
- /** Resource reservation is failed (QoS precondition) */
- /** @hide */
- public static final int LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 1216;
- /** Retry CS call; VoLTE service can't be provided by the network or remote end
- * Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
- * @hide
- */
- public static final int LOCAL_CALL_CS_RETRY_REQUIRED = 1217;
- /** Retry VoLTE call; VoLTE service can't be provided by the network temporarily */
- /** @hide */
- public static final int LOCAL_CALL_VOLTE_RETRY_REQUIRED = 1218;
- /** IMS call is already terminated (in TERMINATED state) */
- /** @hide */
- public static final int LOCAL_CALL_TERMINATED = 1219;
- /** Handover not feasible */
- /** @hide */
- public static final int LOCAL_HO_NOT_FEASIBLE = 1220;
-
- /** 1xx waiting timer is expired after sending INVITE request (MO only) */
- /** @hide */
- public static final int TIMEOUT_1XX_WAITING = 1221;
- /** User no answer during call setup operation (MO/MT)
- * MO : 200 OK to INVITE request is not received,
- * MT : No action from user after alerting the call
- * @hide
- */
- public static final int TIMEOUT_NO_ANSWER = 1222;
- /** User no answer during call update operation (MO/MT)
- * MO : 200 OK to re-INVITE request is not received,
- * MT : No action from user after alerting the call
- * @hide
- */
- public static final int TIMEOUT_NO_ANSWER_CALL_UPDATE = 1223;
-
- /**
- * STATUSCODE (SIP response code) (IMS -> Telephony)
- */
- /** SIP request is redirected */
- /** @hide */
- public static final int SIP_REDIRECTED = 1300;
- /** 4xx responses */
- /** 400 : Bad Request */
- /** @hide */
- public static final int SIP_BAD_REQUEST = 1310;
- /** 403 : Forbidden */
- /** @hide */
- public static final int SIP_FORBIDDEN = 1311;
- /** 404 : Not Found */
- /** @hide */
- public static final int SIP_NOT_FOUND = 1312;
- /** 415 : Unsupported Media Type
- * 416 : Unsupported URI Scheme
- * 420 : Bad Extension
- */
- /** @hide */
- public static final int SIP_NOT_SUPPORTED = 1313;
- /** 408 : Request Timeout */
- /** @hide */
- public static final int SIP_REQUEST_TIMEOUT = 1314;
- /** 480 : Temporarily Unavailable */
- /** @hide */
- public static final int SIP_TEMPRARILY_UNAVAILABLE = 1315;
- /** 484 : Address Incomplete */
- /** @hide */
- public static final int SIP_BAD_ADDRESS = 1316;
- /** 486 : Busy Here
- * 600 : Busy Everywhere
- */
- /** @hide */
- public static final int SIP_BUSY = 1317;
- /** 487 : Request Terminated */
- /** @hide */
- public static final int SIP_REQUEST_CANCELLED = 1318;
- /** 406 : Not Acceptable
- * 488 : Not Acceptable Here
- * 606 : Not Acceptable
- */
- /** @hide */
- public static final int SIP_NOT_ACCEPTABLE = 1319;
- /** 410 : Gone
- * 604 : Does Not Exist Anywhere
- */
- /** @hide */
- public static final int SIP_NOT_REACHABLE = 1320;
- /** Others */
- /** @hide */
- public static final int SIP_CLIENT_ERROR = 1321;
- /** 481 : Transaction Does Not Exist */
- /** @hide */
- public static final int SIP_TRANSACTION_DOES_NOT_EXIST = 1322;
- /** 5xx responses
- * 501 : Server Internal Error
- */
- /** @hide */
- public static final int SIP_SERVER_INTERNAL_ERROR = 1330;
- /** 503 : Service Unavailable */
- /** @hide */
- public static final int SIP_SERVICE_UNAVAILABLE = 1331;
- /** 504 : Server Time-out */
- /** @hide */
- public static final int SIP_SERVER_TIMEOUT = 1332;
- /** Others */
- /** @hide */
- public static final int SIP_SERVER_ERROR = 1333;
- /** 6xx responses
- * 603 : Decline
- */
- /** @hide */
- public static final int SIP_USER_REJECTED = 1340;
- /** Others */
- /** @hide */
- public static final int SIP_GLOBAL_ERROR = 1341;
- /** Emergency failure */
- /** @hide */
- public static final int EMERGENCY_TEMP_FAILURE = 1342;
- /** @hide */
- public static final int EMERGENCY_PERM_FAILURE = 1343;
- /** Media resource initialization failed */
- /** @hide */
- public static final int MEDIA_INIT_FAILED = 1400;
- /** RTP timeout (no audio / video traffic in the session) */
- /** @hide */
- public static final int MEDIA_NO_DATA = 1401;
- /** Media is not supported; so dropped the call */
- /** @hide */
- public static final int MEDIA_NOT_ACCEPTABLE = 1402;
- /** Unknown media related errors */
- /** @hide */
- public static final int MEDIA_UNSPECIFIED = 1403;
- /** User triggers the call end */
- /** @hide */
- public static final int USER_TERMINATED = 1500;
- /** No action while an incoming call is ringing */
- /** @hide */
- public static final int USER_NOANSWER = 1501;
- /** User ignores an incoming call */
- /** @hide */
- public static final int USER_IGNORE = 1502;
- /** User declines an incoming call */
- /** @hide */
- public static final int USER_DECLINE = 1503;
- /** Device declines/ends a call due to low battery */
- /** @hide */
- public static final int LOW_BATTERY = 1504;
- /** Device declines call due to blacklisted call ID */
- /** @hide */
- public static final int BLACKLISTED_CALL_ID = 1505;
- /** The call is terminated by the network or remote user */
- /** @hide */
- public static final int USER_TERMINATED_BY_REMOTE = 1510;
-
- /**
- * UT
- */
- /** @hide */
- public static final int UT_NOT_SUPPORTED = 1800;
- /** @hide */
- public static final int UT_SERVICE_UNAVAILABLE = 1801;
- /** @hide */
- public static final int UT_OPERATION_NOT_ALLOWED = 1802;
- /** @hide */
- public static final int UT_NETWORK_ERROR = 1803;
- /** @hide */
- public static final int UT_CB_PASSWORD_MISMATCH = 1804;
-
- /**
- * ECBM
- * @hide
- */
- public static final int ECBM_NOT_SUPPORTED = 1900;
-
- /**
- * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework.
- * @hide
- */
- public static final int MULTIENDPOINT_NOT_SUPPORTED = 1901;
-
- /**
- * CALL DROP error codes (Call could drop because of many reasons like Network not available,
- * handover, failed, etc)
- */
-
- /**
- * CALL DROP error code for the case when a device is ePDG capable and when the user is on an
- * active wifi call and at the edge of coverage and there is no qualified LTE network available
- * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error
- * code is received as part of the handover message.
- * @hide
- */
- public static final int CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE = 2000;
-
- /**
- * MT call has ended due to a release from the network
- * because the call was answered elsewhere
- * @hide
- */
- public static final int ANSWERED_ELSEWHERE = 2100;
-
- /**
- * For MultiEndpoint - Call Pull request has failed
- * @hide
- */
- public static final int CALL_PULL_OUT_OF_SYNC = 2101;
-
- /**
- * For MultiEndpoint - Call has been pulled from primary to secondary
- * @hide
- */
- public static final int CALL_PULLED = 2102;
-
- /**
- * Supplementary services (HOLD/RESUME) failure error codes.
- * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision.
- * @hide
- */
- public static final int SUPP_SVC_FAILED = 2300;
- /** @hide */
- public static final int SUPP_SVC_CANCELLED = 2301;
- /** @hide */
- public static final int SUPP_SVC_REINVITE_COLLISION = 2302;
-
- /**
- * DPD Procedure received no response or send failed
- * @hide
- */
- public static final int IWLAN_DPD_FAILURE = 2400;
-
- /**
- * Establishment of the ePDG Tunnel Failed
- * @hide
- */
- public static final int EPDG_TUNNEL_ESTABLISH_FAILURE = 2500;
-
- /**
- * Re-keying of the ePDG Tunnel Failed; may not always result in teardown
- * @hide
- */
- public static final int EPDG_TUNNEL_REKEY_FAILURE = 2501;
-
- /**
- * Connection to the packet gateway is lost
- * @hide
- */
- public static final int EPDG_TUNNEL_LOST_CONNECTION = 2502;
-
- /**
- * The maximum number of calls allowed has been reached. Used in a multi-endpoint scenario
- * where the number of calls across all connected devices has reached the maximum.
- * @hide
- */
- public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED = 2503;
-
- /**
- * Similar to {@link #CODE_LOCAL_CALL_DECLINE}, except indicates that a remote device has
- * declined the call. Used in a multi-endpoint scenario where a remote device declined an
- * incoming call.
- * @hide
- */
- public static final int REMOTE_CALL_DECLINE = 2504;
-
- /**
- * Indicates the call was disconnected due to the user reaching their data limit.
- * @hide
- */
- public static final int DATA_LIMIT_REACHED = 2505;
-
- /**
- * Indicates the call was disconnected due to the user disabling cellular data.
- * @hide
- */
- public static final int DATA_DISABLED = 2506;
-
- /**
- * Indicates a call was disconnected due to loss of wifi signal.
- * @hide
- */
- public static final int WIFI_LOST = 2507;
-
-
/* OEM specific error codes. To be used by OEMs when they don't want to
reveal error code which would be replaced by ERROR_UNSPECIFIED */
public static final int OEM_CAUSE_1 = 0xf001;
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 5a4dac5..ad58c54 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -16,8 +16,8 @@
package android.telephony;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -79,8 +79,9 @@
/* The type of signal measurement */
private static final String MEASUREMENT_TYPE_RSCP = "rscp";
- // timeStamp of signalStrength in nanoseconds since boot
- private long mTimestamp = Long.MAX_VALUE;
+ // Timestamp of SignalStrength since boot
+ // Effectively final. Timestamp is set during construction of SignalStrength
+ private long mTimestampMillis;
CellSignalStrengthCdma mCdma;
CellSignalStrengthGsm mGsm;
@@ -139,7 +140,7 @@
mTdscdma = tdscdma;
mLte = lte;
mNr = nr;
- mTimestamp = SystemClock.elapsedRealtimeNanos();
+ mTimestampMillis = SystemClock.elapsedRealtime();
}
/**
@@ -274,7 +275,6 @@
mTdscdma.updateLevel(cc, ss);
mLte.updateLevel(cc, ss);
mNr.updateLevel(cc, ss);
- mTimestamp = SystemClock.elapsedRealtimeNanos();
}
/**
@@ -300,7 +300,7 @@
mTdscdma = new CellSignalStrengthTdscdma(s.mTdscdma);
mLte = new CellSignalStrengthLte(s.mLte);
mNr = new CellSignalStrengthNr(s.mNr);
- mTimestamp = s.getTimestampNanos();
+ mTimestampMillis = s.getTimestampMillis();
}
/**
@@ -318,7 +318,7 @@
mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader());
mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
- mTimestamp = in.readLong();
+ mTimestampMillis = in.readLong();
}
/**
@@ -331,15 +331,17 @@
out.writeParcelable(mTdscdma, flags);
out.writeParcelable(mLte, flags);
out.writeParcelable(mNr, flags);
- out.writeLong(mTimestamp);
+ out.writeLong(mTimestampMillis);
}
/**
- * @return mTimestamp in nanoseconds
+ * @return timestamp in milliseconds since boot for {@link SignalStrength}.
+ * This timestamp reports the approximate time that the signal was measured and reported
+ * by the modem. It can be used to compare the recency of {@link SignalStrength} instances.
*/
- @SuppressLint("MethodNameUnits")
- public long getTimestampNanos() {
- return mTimestamp;
+ @ElapsedRealtimeLong
+ public long getTimestampMillis() {
+ return mTimestampMillis;
}
/**
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 20d0e96..ae20cae 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -430,10 +430,9 @@
private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException {
String apkPath = getApkPath(TARGET_PACKAGE);
String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
+ // Exclude directories since we only care about files.
HashSet<String> actualFiles = new HashSet<>(Arrays.asList(
- expectRemoteCommandToSucceed("ls " + appDir).split("\n")));
- assertTrue(actualFiles.remove("lib"));
- assertTrue(actualFiles.remove("oat"));
+ expectRemoteCommandToSucceed("ls -p " + appDir + " | grep -v '/'").split("\n")));
HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames));
assertEquals(expectedFiles, actualFiles);
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 89005da..24623fb 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -29,7 +29,7 @@
name: "StagedRollbackTest",
srcs: ["StagedRollbackTest/src/**/*.java"],
libs: ["tradefed"],
- static_libs: ["testng", "compatibility-tradefed"],
+ static_libs: ["testng", "compatibility-tradefed", "RollbackTestLib"],
test_suites: ["general-tests"],
test_config: "StagedRollbackTest.xml",
data: [":com.android.apex.apkrollback.test_v1"],
@@ -39,7 +39,7 @@
name: "NetworkStagedRollbackTest",
srcs: ["NetworkStagedRollbackTest/src/**/*.java"],
libs: ["tradefed"],
- static_libs: ["testng"],
+ static_libs: ["testng", "RollbackTestLib"],
test_suites: ["general-tests"],
test_config: "NetworkStagedRollbackTest.xml",
}
@@ -52,6 +52,12 @@
test_config: "MultiUserRollbackTest.xml",
}
+java_library_host {
+ name: "RollbackTestLib",
+ srcs: ["lib/src/**/*.java"],
+ libs: ["tradefed"],
+}
+
genrule {
name: "com.android.apex.apkrollback.test.pem",
out: ["com.android.apex.apkrollback.test.pem"],
diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
index a72bd38..d4e34f9 100644
--- a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
@@ -16,12 +16,12 @@
package com.android.tests.rollback.host;
+import static com.android.tests.rollback.host.WatchdogEventLogger.watchdogEventOccurred;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.testng.Assert.assertThrows;
-import com.android.tradefed.device.LogcatReceiver;
-import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -30,10 +30,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -58,19 +54,16 @@
private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE";
private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED";
- private LogcatReceiver mReceiver;
+ private WatchdogEventLogger mLogger = new WatchdogEventLogger();
@Before
public void setUp() throws Exception {
- mReceiver = new LogcatReceiver(getDevice(), "logcat -s WatchdogRollbackLogger",
- getDevice().getOptions().getMaxLogcatDataSize(), 0);
- mReceiver.start();
+ mLogger.start(getDevice());
}
@After
public void tearDown() throws Exception {
- mReceiver.stop();
- mReceiver.clear();
+ mLogger.stop();
}
/**
@@ -94,16 +87,12 @@
getDevice().waitForDeviceAvailable();
// Verify rollback was executed after health check deadline
runPhase("testNetworkFailedRollback_Phase4");
- InputStreamSource logcatStream = mReceiver.getLogcatData();
- try {
- List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
- REASON_EXPLICIT_HEALTH_CHECK, null));
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
- null, null));
- } finally {
- logcatStream.close();
- }
+
+ List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+ REASON_EXPLICIT_HEALTH_CHECK, null));
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+ null, null));
} finally {
// Reconnect internet again so we won't break tests which assume internet available
getDevice().executeShellCommand("svc wifi enable");
@@ -133,66 +122,9 @@
// Verify rollback was not executed after health check deadline
runPhase("testNetworkPassedDoesNotRollback_Phase3");
- InputStreamSource logcatStream = mReceiver.getLogcatData();
- try {
- List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
- assertEquals(watchdogEventOccurred(watchdogEvents, null, null,
- REASON_EXPLICIT_HEALTH_CHECK, null), false);
- } finally {
- logcatStream.close();
- }
- }
- /**
- * Returns a list of all Watchdog logging events which have occurred.
- */
- private List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource)
- throws Exception {
- List<String> watchdogEvents = new ArrayList<>();
- InputStream inputStream = inputStreamSource.createInputStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
- String line;
- while ((line = reader.readLine()) != null) {
- if (line.contains("Watchdog event occurred")) {
- watchdogEvents.add(line);
- }
- }
- return watchdogEvents;
- }
-
- /**
- * Returns whether a Watchdog event has occurred that matches the given criteria.
- *
- * Check the value of all non-null parameters against the list of Watchdog events that have
- * occurred, and return {@code true} if an event exists which matches all criteria.
- */
- private boolean watchdogEventOccurred(List<String> loggingEvents,
- String type, String logPackage,
- String rollbackReason, String failedPackageName) throws Exception {
- List<String> eventCriteria = new ArrayList<>();
- if (type != null) {
- eventCriteria.add("type: " + type);
- }
- if (logPackage != null) {
- eventCriteria.add("logPackage: " + logPackage);
- }
- if (rollbackReason != null) {
- eventCriteria.add("rollbackReason: " + rollbackReason);
- }
- if (failedPackageName != null) {
- eventCriteria.add("failedPackageName: " + failedPackageName);
- }
- for (String loggingEvent: loggingEvents) {
- boolean matchesCriteria = true;
- for (String criterion: eventCriteria) {
- if (!loggingEvent.contains(criterion)) {
- matchesCriteria = false;
- }
- }
- if (matchesCriteria) {
- return true;
- }
- }
- return false;
+ List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+ assertEquals(watchdogEventOccurred(watchdogEvents, null, null,
+ REASON_EXPLICIT_HEALTH_CHECK, null), false);
}
}
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 c3fd962..8104d1d 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
@@ -16,6 +16,8 @@
package com.android.tests.rollback.host;
+import static com.android.tests.rollback.host.WatchdogEventLogger.watchdogEventOccurred;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -23,8 +25,6 @@
import static org.testng.Assert.assertThrows;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.device.LogcatReceiver;
-import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -33,11 +33,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.BufferedReader;
import java.io.File;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -83,12 +79,10 @@
private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE";
private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED";
- private LogcatReceiver mReceiver;
+ private WatchdogEventLogger mLogger = new WatchdogEventLogger();
@Before
public void setUp() throws Exception {
- mReceiver = new LogcatReceiver(getDevice(), "logcat -s WatchdogRollbackLogger",
- getDevice().getOptions().getMaxLogcatDataSize(), 0);
if (!getDevice().isAdbRoot()) {
getDevice().enableAdbRoot();
}
@@ -98,13 +92,12 @@
+ "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
getDevice().reboot();
runPhase("testCleanUp");
- mReceiver.start();
+ mLogger.start(getDevice());
}
@After
public void tearDown() throws Exception {
- mReceiver.stop();
- mReceiver.clear();
+ mLogger.stop();
runPhase("testCleanUp");
if (!getDevice().isAdbRoot()) {
@@ -134,16 +127,12 @@
getDevice().waitForDeviceAvailable();
runPhase("testBadApkOnly_Phase4");
- InputStreamSource logcatStream = mReceiver.getLogcatData();
- try {
- List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
- REASON_APP_CRASH, TESTAPP_A));
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
- null, null));
- } finally {
- logcatStream.close();
- }
+
+ List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+ REASON_APP_CRASH, TESTAPP_A));
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+ null, null));
}
@Test
@@ -171,16 +160,12 @@
// verify rollback committed
runPhase("testNativeWatchdogTriggersRollback_Phase3");
- InputStreamSource logcatStream = mReceiver.getLogcatData();
- try {
- List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
- REASON_NATIVE_CRASH, null));
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
- null, null));
- } finally {
- logcatStream.close();
- }
+
+ List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+ REASON_NATIVE_CRASH, null));
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+ null, null));
}
@Test
@@ -215,16 +200,12 @@
// verify all available rollbacks have been committed
runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4");
- InputStreamSource logcatStream = mReceiver.getLogcatData();
- try {
- List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
- REASON_NATIVE_CRASH, null));
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
- null, null));
- } finally {
- logcatStream.close();
- }
+
+ List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+ REASON_NATIVE_CRASH, null));
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+ null, null));
}
/**
@@ -290,16 +271,12 @@
getDevice().waitForDeviceAvailable();
// Verify rollback occurred due to crash of apk-in-apex
runPhase("testRollbackApexWithApkCrashing_Phase3");
- InputStreamSource logcatStream = mReceiver.getLogcatData();
- try {
- List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
- REASON_APP_CRASH, TESTAPP_A));
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
- null, null));
- } finally {
- logcatStream.close();
- }
+
+ List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+ REASON_APP_CRASH, TESTAPP_A));
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+ null, null));
}
/**
@@ -464,57 +441,4 @@
return false;
}
}
-
- /**
- * Returns a list of all Watchdog logging events which have occurred.
- */
- private List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource)
- throws Exception {
- List<String> watchdogEvents = new ArrayList<>();
- InputStream inputStream = inputStreamSource.createInputStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
- String line;
- while ((line = reader.readLine()) != null) {
- if (line.contains("Watchdog event occurred")) {
- watchdogEvents.add(line);
- }
- }
- return watchdogEvents;
- }
-
- /**
- * Returns whether a Watchdog event has occurred that matches the given criteria.
- *
- * Check the value of all non-null parameters against the list of Watchdog events that have
- * occurred, and return {@code true} if an event exists which matches all criteria.
- */
- private boolean watchdogEventOccurred(List<String> loggingEvents,
- String type, String logPackage,
- String rollbackReason, String failedPackageName) throws Exception {
- List<String> eventCriteria = new ArrayList<>();
- if (type != null) {
- eventCriteria.add("type: " + type);
- }
- if (logPackage != null) {
- eventCriteria.add("logPackage: " + logPackage);
- }
- if (rollbackReason != null) {
- eventCriteria.add("rollbackReason: " + rollbackReason);
- }
- if (failedPackageName != null) {
- eventCriteria.add("failedPackageName: " + failedPackageName);
- }
- for (String loggingEvent: loggingEvents) {
- boolean matchesCriteria = true;
- for (String criterion: eventCriteria) {
- if (!loggingEvent.contains(criterion)) {
- matchesCriteria = false;
- }
- }
- if (matchesCriteria) {
- return true;
- }
- }
- return false;
- }
}
diff --git a/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java b/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java
new file mode 100644
index 0000000..317d67e
--- /dev/null
+++ b/tests/RollbackTest/lib/src/com/android/tests/rollback/host/WatchdogEventLogger.java
@@ -0,0 +1,101 @@
+/*
+ * 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.tests.rollback.host;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.LogcatReceiver;
+import com.android.tradefed.result.InputStreamSource;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class WatchdogEventLogger {
+ private LogcatReceiver mReceiver;
+
+ public void start(ITestDevice device) {
+ mReceiver = new LogcatReceiver(device, "logcat -s WatchdogRollbackLogger",
+ device.getOptions().getMaxLogcatDataSize(), 0);
+ mReceiver.start();
+ }
+
+ public void stop() {
+ mReceiver.stop();
+ mReceiver.clear();
+ }
+
+ /**
+ * Returns a list of all Watchdog logging events which have occurred.
+ */
+ public List<String> getWatchdogLoggingEvents() throws Exception {
+ try (InputStreamSource logcatStream = mReceiver.getLogcatData()) {
+ return getWatchdogLoggingEvents(logcatStream);
+ }
+ }
+
+ private static List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource)
+ throws Exception {
+ List<String> watchdogEvents = new ArrayList<>();
+ InputStream inputStream = inputStreamSource.createInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.contains("Watchdog event occurred")) {
+ watchdogEvents.add(line);
+ }
+ }
+ return watchdogEvents;
+ }
+
+ /**
+ * Returns whether a Watchdog event has occurred that matches the given criteria.
+ *
+ * Check the value of all non-null parameters against the list of Watchdog events that have
+ * occurred, and return {@code true} if an event exists which matches all criteria.
+ */
+ public static boolean watchdogEventOccurred(List<String> loggingEvents,
+ String type, String logPackage,
+ String rollbackReason, String failedPackageName) throws Exception {
+ List<String> eventCriteria = new ArrayList<>();
+ if (type != null) {
+ eventCriteria.add("type: " + type);
+ }
+ if (logPackage != null) {
+ eventCriteria.add("logPackage: " + logPackage);
+ }
+ if (rollbackReason != null) {
+ eventCriteria.add("rollbackReason: " + rollbackReason);
+ }
+ if (failedPackageName != null) {
+ eventCriteria.add("failedPackageName: " + failedPackageName);
+ }
+ for (String loggingEvent: loggingEvents) {
+ boolean matchesCriteria = true;
+ for (String criterion: eventCriteria) {
+ if (!loggingEvent.contains(criterion)) {
+ matchesCriteria = false;
+ }
+ }
+ if (matchesCriteria) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 155c61f..eb78529 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -148,6 +148,7 @@
@Mock private AppOpsManager mAppOps;
@Mock private NotificationManager mNotificationManager;
@Mock private Vpn.SystemServices mSystemServices;
+ @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
@Mock private ConnectivityManager mConnectivityManager;
@Mock private KeyStore mKeyStore;
private final VpnProfile mVpnProfile = new VpnProfile("key");
@@ -867,7 +868,8 @@
* Mock some methods of vpn object.
*/
private Vpn createVpn(@UserIdInt int userId) {
- return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices);
+ return new Vpn(Looper.myLooper(), mContext, mNetService,
+ userId, mSystemServices, mIkev2SessionCreator);
}
private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b6f4490..f693315 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -28,6 +28,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.ActivityManager;
@@ -1621,11 +1622,13 @@
* @param wifiConfiguration WifiConfiguration object corresponding to the network
* user selected.
*/
+ @SuppressLint("CallbackMethodName")
default void select(@NonNull WifiConfiguration wifiConfiguration) {}
/**
* User rejected the app's request.
*/
+ @SuppressLint("CallbackMethodName")
default void reject() {}
}