diff options
236 files changed, 7630 insertions, 2097 deletions
diff --git a/Android.bp b/Android.bp index df852bda4904..412099dede85 100644 --- a/Android.bp +++ b/Android.bp @@ -372,6 +372,7 @@ java_library { "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 08b1c2b9f548..abf78c67fa6f 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 @@ import java.util.List; * 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 4ffcf8ab6076..4b4fb9623ba0 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java @@ -80,27 +80,22 @@ public class PowerWhitelistManager { } /** - * 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 d2d942a4a7e5..dc72d6d9c4b3 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 @@ public interface AppStandbyInternal { /** * 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 b516279b58a5..e4c6b52f94bb 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.JobSnapshot; 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.net.Uri; 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.AppStateTracker; 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 @@ public class JobSchedulerService extends com.android.server.SystemService /** 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService 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 @@ public class JobSchedulerService extends com.android.server.SystemService // 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 f1bfa0411978..e343478ec61f 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_RARE; 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.content.pm.ParceledListSlice; 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { if (mPendingOneTimeCheckIdleStates) { postOneTimeCheckIdleStates(); } + } else if (phase == PHASE_BOOT_COMPLETED) { + setChargingState(mInjector.isCharging()); } } @@ -515,6 +523,16 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { if (isAppSpecial(packageName, appId, userId)) { return false; } else { + synchronized (mAppIdleLock) { + if (!mAppIdleEnabled || mIsCharging) { + return false; + } + } return isAppIdleUnfiltered(packageName, userId, elapsedRealtime); } } @@ -1543,6 +1566,8 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { return buildFlag && runtimeFlag; } + boolean isCharging() { + return mBatteryManager.isCharging(); + } + boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException { return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName); } @@ -1766,15 +1797,19 @@ public class AppStandbyController implements AppStandbyInternal { } }; - 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 3eed26b108f4..7d18578aff66 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.DataSource; 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 @@ public final class MediaParser { // 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); + + 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 != 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); if (format.colorInfo.hdrStaticInfo != null) { result.setByteBuffer( MediaFormat.KEY_HDR_STATIC_INFO, @@ -830,63 +829,50 @@ public final class MediaParser { } } - 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); - } - 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); + 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); } - // 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 @@ public final class MediaParser { 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 1f9f18cd051a..baa1c251324f 100644 --- a/apex/statsd/Android.bp +++ b/apex/statsd/Android.bp @@ -20,6 +20,8 @@ apex { apex_defaults { native_shared_libs: [ + "libstatssocket", + "libstatspull", "libstats_jni", ], // binaries: ["vold"], @@ -28,6 +30,7 @@ apex_defaults { "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", @@ -74,4 +77,4 @@ cc_library_shared { //TODO (b/148620413): remove platform. "//apex_available:platform", ], -}
\ No newline at end of file +} diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp index 7c93bc73e45d..4ccdd7e734db 100644 --- a/apex/statsd/aidl/Android.bp +++ b/apex/statsd/aidl/Android.bp @@ -38,6 +38,10 @@ aidl_interface { }, ndk: { enabled: true, + apex_available: [ + "com.android.os.statsd", + ], } - } + + }, } diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index 63a853a6005c..ab669d4ac45a 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 @@ java_library_static { srcs: [ ":statslog-statsd-java-gen", ], + visibility: [ + "//cts/hostsidetests/statsd/apps:__subpackages__", + ] } filegroup { @@ -34,6 +41,9 @@ filegroup { ":framework-statsd-aidl-sources", ":statslog-statsd-java-gen", ], + visibility: [ + "//frameworks/base", // For the "global" stubs. + ], } java_defaults { @@ -139,6 +149,10 @@ java_library { "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 @@ java_library { "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 @@ java_library { "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 65669cdcb118..9f64c6a4497c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -289,6 +289,7 @@ package android { field public static final int allowBackup = 16843392; // 0x1010280 field public static final int allowClearUserData = 16842757; // 0x1010005 field public static final int allowEmbedded = 16843765; // 0x10103f5 + field public static final int allowNativeHeapPointerTagging = 16844311; // 0x1010617 field public static final int allowParallelSyncs = 16843570; // 0x1010332 field public static final int allowSingleTap = 16843353; // 0x1010259 field public static final int allowTaskReparenting = 16843268; // 0x1010204 @@ -2876,6 +2877,7 @@ package android.accessibilityservice { method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.accessibilityservice.AccessibilityService.ScreenshotResult>); field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14 + field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28 field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13 field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b @@ -2883,6 +2885,7 @@ package android.accessibilityservice { field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19 field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15 field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17 + field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29 field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16 field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f @@ -2890,6 +2893,7 @@ package android.accessibilityservice { field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18 field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26 + field public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; // 0x2a field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25 field public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34; // 0x22 field public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35; // 0x23 @@ -6917,6 +6921,7 @@ package android.app.admin { 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 +6940,6 @@ package android.app.admin { 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 +6986,7 @@ package android.app.admin { 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 +7006,6 @@ package android.app.admin { 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); @@ -10141,7 +10145,6 @@ package android.content { field public static final String ALARM_SERVICE = "alarm"; field public static final String APPWIDGET_SERVICE = "appwidget"; field public static final String APP_OPS_SERVICE = "appops"; - field public static final String APP_SEARCH_SERVICE = "app_search"; field public static final String AUDIO_SERVICE = "audio"; field public static final String BATTERY_SERVICE = "batterymanager"; field public static final int BIND_ABOVE_CLIENT = 8; // 0x8 @@ -12703,8 +12706,7 @@ package android.content.res { 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 +12733,6 @@ package android.content.res { 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 +12760,7 @@ package android.content.res { 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 +45514,7 @@ package android.telecom { 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 +45603,8 @@ package android.telecom { 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 +45634,9 @@ package android.telecom { 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 +45796,7 @@ package android.telecom { 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 +46975,7 @@ package android.telephony { 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 +47135,19 @@ package android.telephony { 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 +47404,7 @@ package android.telephony { 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 +47422,7 @@ package android.telephony { 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 +47505,7 @@ package android.telephony { 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 b3a2c135add5..8725538f1fd8 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1809,7 +1809,7 @@ package android.content { field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry"; field public static final String TETHERING_SERVICE = "tethering"; field public static final String VR_SERVICE = "vrmanager"; - field public static final String WIFI_COND_SERVICE = "wificond"; + field public static final String WIFI_NL80211_SERVICE = "wifinl80211"; field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager"; field public static final String WIFI_SCANNING_SERVICE = "wifiscanner"; } @@ -2217,6 +2217,7 @@ package android.content.pm { field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; field public static final int FLAGS_PERMISSION_RESERVED_PERMISSIONCONTROLLER = -268435456; // 0xf0000000 field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000 + field public static final int FLAG_PERMISSION_AUTO_REVOKED = 1048576; // 0x100000 field public static final int FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED = 131072; // 0x20000 field public static final int FLAG_PERMISSION_AUTO_REVOKE_USER_SET = 262144; // 0x40000 field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20 @@ -2302,7 +2303,7 @@ package android.content.pm { method public void onPermissionsChanged(int); } - @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_USER_SET}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags { + @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags { } public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { @@ -6031,7 +6032,7 @@ package android.net { 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 @@ -8329,21 +8330,21 @@ package android.net.wifi.wificond { field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.RadioChainInfo> CREATOR; } - public class WifiCondManager { + public class WifiNl80211Manager { method public void abortScan(@NonNull String); method public void enableVerboseLogging(boolean); method @NonNull public int[] getChannelsMhzForBand(int); method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String); method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int); - method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String); - method @Nullable public static android.net.wifi.wificond.WifiCondManager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); - method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SoftApCallback); - method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SendMgmtFrameCallback); + method @Nullable public android.net.wifi.wificond.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); + method @Nullable public static android.net.wifi.wificond.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); + method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.SoftApCallback); + method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.SendMgmtFrameCallback); method public void setOnServiceDeadCallback(@NonNull Runnable); - method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback); + method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiNl80211Manager.ScanEventCallback); method public boolean setupInterfaceForSoftApMode(@NonNull String); - method @Nullable public android.net.wifi.wificond.WifiCondManager.SignalPollResult signalPoll(@NonNull String); - method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.wificond.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.PnoScanRequestCallback); + method @Nullable public android.net.wifi.wificond.WifiNl80211Manager.SignalPollResult signalPoll(@NonNull String); + method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.wificond.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.PnoScanRequestCallback); method public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>); method public boolean stopPnoScan(@NonNull String); method public boolean tearDownClientInterface(@NonNull String); @@ -8358,43 +8359,43 @@ package android.net.wifi.wificond { field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1 } - public static class WifiCondManager.OemSecurityType { - ctor public WifiCondManager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int); + public static class WifiNl80211Manager.OemSecurityType { + ctor public WifiNl80211Manager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int); field public final int groupCipher; field @NonNull public final java.util.List<java.lang.Integer> keyManagement; field @NonNull public final java.util.List<java.lang.Integer> pairwiseCipher; field public final int protocol; } - public static interface WifiCondManager.PnoScanRequestCallback { + public static interface WifiNl80211Manager.PnoScanRequestCallback { method public void onPnoRequestFailed(); method public void onPnoRequestSucceeded(); } - public static interface WifiCondManager.ScanEventCallback { + public static interface WifiNl80211Manager.ScanEventCallback { method public void onScanFailed(); method public void onScanResultReady(); } - public static interface WifiCondManager.SendMgmtFrameCallback { + public static interface WifiNl80211Manager.SendMgmtFrameCallback { method public void onAck(int); method public void onFailure(int); } - public static class WifiCondManager.SignalPollResult { + public static class WifiNl80211Manager.SignalPollResult { field public final int associationFrequencyMHz; field public final int currentRssiDbm; field public final int rxBitrateMbps; field public final int txBitrateMbps; } - public static interface WifiCondManager.SoftApCallback { + public static interface WifiNl80211Manager.SoftApCallback { method public void onConnectedClientsChanged(@NonNull android.net.wifi.wificond.NativeWifiClient, boolean); method public void onFailure(); method public void onSoftApChannelSwitched(int, int); } - public static class WifiCondManager.TxPacketCounters { + public static class WifiNl80211Manager.TxPacketCounters { field public final int txPacketFailed; field public final int txPacketSucceeded; } @@ -8839,8 +8840,8 @@ package android.os { } 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 @@ -10854,27 +10855,26 @@ package android.telecom { 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 @@ -11033,7 +11033,7 @@ package android.telecom { } 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 { @@ -11108,7 +11108,7 @@ package android.telecom { 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); @@ -12572,6 +12572,7 @@ package android.telephony { 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/system-lint-baseline.txt b/api/system-lint-baseline.txt index 0caee6bebbda..dfb0d7460ca4 100644 --- a/api/system-lint-baseline.txt +++ b/api/system-lint-baseline.txt @@ -234,7 +234,7 @@ OnNameExpected: android.content.ContentProvider#checkUriPermission(android.net.U If implemented by developer, should follow the on<Something> style; otherwise consider marking final -PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback): +PairedRegistration: android.net.wifi.wificond.WifiNl80211Manager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiNl80211Manager.SoftApCallback): diff --git a/api/test-current.txt b/api/test-current.txt index 7e8eb0c965ca..4c8bb0290cae 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -15,6 +15,7 @@ package android { 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 @@ package android.net { 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 @@ package android.os { } 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 @@ package android.telecom { } 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 @@ package android.telecom { } 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 @@ package android.telecom { 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 @@ package android.telephony { 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/Android.bp b/cmds/statsd/Android.bp index 956fd29205cb..1bcf44e03c09 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -120,13 +120,14 @@ cc_defaults { "libstatslog", "libstatsmetadata", "libsysutils", + // TODO(b/145923087): move to shared when statsd is moved to the apex + "libstatssocket", "libutils", ], shared_libs: [ "libbinder", "libincident", "liblog", - "libstatssocket", "statsd-aidl-cpp", ], } diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 1bc235aa1d91..23a4437910f7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -378,10 +378,10 @@ message Atom { 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; + NotificationChannelModified notification_channel_modified = 246; IntegrityCheckResultReported integrity_check_result_reported = 247 [(module) = "framework"]; IntegrityRulesPushed integrity_rules_pushed = 248 [(module) = "framework"]; CellBroadcastMessageReported cb_message_reported = diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 1bac19ed2c5d..4899b4a5247c 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -690,14 +690,16 @@ void StorageManager::trimToFit(const char* path, bool parseTimestampOnly) { if (name[0] == '.') continue; FileName output; + string file_name; if (parseTimestampOnly) { + file_name = StringPrintf("%s/%s", path, name); output.mTimestampSec = StrToInt64(strtok(name, "_")); output.mIsHistory = false; } else { parseFileName(name, &output); + file_name = output.getFullFileName(path); } if (output.mTimestampSec == -1) continue; - string file_name = output.getFullFileName(path); // Check for timestamp and delete if it's too old. long fileAge = nowSec - output.mTimestampSec; diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java index ace13513e39d..25729abf9e05 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -18,6 +18,7 @@ package android.accessibilityservice; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT; @@ -25,6 +26,7 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT; @@ -32,6 +34,7 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT; @@ -83,9 +86,11 @@ public final class AccessibilityGestureEvent implements Parcelable { @IntDef(prefix = { "GESTURE_" }, value = { GESTURE_2_FINGER_SINGLE_TAP, GESTURE_2_FINGER_DOUBLE_TAP, + GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, GESTURE_2_FINGER_TRIPLE_TAP, GESTURE_3_FINGER_SINGLE_TAP, GESTURE_3_FINGER_DOUBLE_TAP, + GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, GESTURE_3_FINGER_TRIPLE_TAP, GESTURE_DOUBLE_TAP, GESTURE_DOUBLE_TAP_AND_HOLD, @@ -114,6 +119,7 @@ public final class AccessibilityGestureEvent implements Parcelable { GESTURE_3_FINGER_SWIPE_RIGHT, GESTURE_3_FINGER_SWIPE_UP, GESTURE_4_FINGER_DOUBLE_TAP, + GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD, GESTURE_4_FINGER_SINGLE_TAP, GESTURE_4_FINGER_SWIPE_DOWN, GESTURE_4_FINGER_SWIPE_LEFT, @@ -175,12 +181,18 @@ public final class AccessibilityGestureEvent implements Parcelable { switch (eventType) { case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP"; case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP"; + case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD: + return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD"; case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP"; case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP"; case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP"; + case GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD: + return "GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD"; case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP"; case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP"; case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP"; + case GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD: + return "GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD"; case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP"; case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP"; case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD"; diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index b65f68e177ca..0ed6b1f38dfd 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -411,6 +411,15 @@ public abstract class AccessibilityService extends Service { /** The user has performed a four-finger triple tap gesture on the touch screen. */ public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39; + /** The user has performed a two-finger double tap and hold gesture on the touch screen. */ + public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; + + /** The user has performed a three-finger double tap and hold gesture on the touch screen. */ + public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; + + /** The user has performed a two-finger double tap and hold gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; + /** * The {@link Intent} that must be declared as handled by the service. */ diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 367c2f235f5f..1de68ba56c6a 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -7824,9 +7824,7 @@ public class AppOpsManager { * @hide */ public static boolean isCollectingNotedAppOps() { - synchronized (sLock) { - return sNotedAppOpsCollector != null; - } + return sNotedAppOpsCollector != null; } /** diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 9aa6b870792d..526c0b3f87ee 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -162,6 +162,7 @@ interface INotificationManager void applyAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment); void applyAdjustmentsFromAssistant(in INotificationListener token, in List<Adjustment> adjustments); void unsnoozeNotificationFromAssistant(in INotificationListener token, String key); + void unsnoozeNotificationFromSystemListener(in INotificationListener token, String key); ComponentName getEffectsSuppressor(); boolean matchesCallFilter(in Bundle extras); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 8b07418668ba..9b7306089dca 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -132,7 +132,7 @@ import android.net.lowpan.LowpanManager; import android.net.nsd.INsdManager; import android.net.nsd.NsdManager; import android.net.wifi.WifiFrameworkInitializer; -import android.net.wifi.wificond.WifiCondManager; +import android.net.wifi.wificond.WifiNl80211Manager; import android.nfc.NfcManager; import android.os.BatteryManager; import android.os.BatteryStats; @@ -761,11 +761,11 @@ public final class SystemServiceRegistry { return new EthernetManager(ctx.getOuterContext(), service); }}); - registerService(Context.WIFI_COND_SERVICE, WifiCondManager.class, - new CachedServiceFetcher<WifiCondManager>() { + registerService(Context.WIFI_NL80211_SERVICE, WifiNl80211Manager.class, + new CachedServiceFetcher<WifiNl80211Manager>() { @Override - public WifiCondManager createService(ContextImpl ctx) { - return new WifiCondManager(ctx.getOuterContext()); + public WifiNl80211Manager createService(ContextImpl ctx) { + return new WifiNl80211Manager(ctx.getOuterContext()); } }); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 48eab92fe061..4a5a23ab36f8 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4718,17 +4718,42 @@ public class DevicePolicyManager { 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 - * {@link DevicePolicyManager} instance. + * 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 PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER = + 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.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. * It has never affected the encryption status of a device. * @@ -6115,11 +6140,20 @@ public class DevicePolicyManager { * <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 @@ public class DevicePolicyManager { * @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 @@ public class DevicePolicyManager { * @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 8ef25bdd9156..0aed39cbda8d 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -266,8 +266,8 @@ interface IDevicePolicyManager { 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 ea66fd475153..db4f1de1f743 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -90,6 +90,7 @@ public final class RoleManager { * 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/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index ab71e73fd58c..0f999ad68a62 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -41,6 +41,43 @@ public final class UsageEvents implements Parcelable { /** @hide */ public static final String INSTANT_APP_CLASS_NAME = "android.instant_class"; + /** @hide */ + public static final String OBFUSCATED_NOTIFICATION_CHANNEL_ID = "unknown_channel_id"; + + /** + * Flag: indicates to not obfuscate or hide any usage event data when being queried. + * @hide + */ + public static final int SHOW_ALL_EVENT_DATA = 0x00000000; + + /** + * Flag: indicates to obfuscate package and class names for instant apps when querying usage + * events. + * @hide + */ + public static final int OBFUSCATE_INSTANT_APPS = 0x00000001; + + /** + * Flag: indicates to hide all {@link Event#SHORTCUT_INVOCATION} events when querying usage + * events. + * @hide + */ + public static final int HIDE_SHORTCUT_EVENTS = 0x00000002; + + /** + * Flag: indicates to obfuscate the notification channel id for all notification events, + * such as {@link Event#NOTIFICATION_SEEN} and {@link Event#NOTIFICATION_INTERRUPTION} events, + * when querying usage events. + * @hide + */ + public static final int OBFUSCATE_NOTIFICATION_EVENTS = 0x00000004; + + /** + * Flag: indicates to hide all {@link Event#LOCUS_ID_SET} events when querying usage events. + * @hide + */ + public static final int HIDE_LOCUS_EVENTS = 0x00000008; + /** * An event representing a state change for a component. */ @@ -627,6 +664,13 @@ public final class UsageEvents implements Parcelable { return ret; } + /** @hide */ + public Event getObfuscatedNotificationEvent() { + final Event ret = new Event(this); + ret.mNotificationChannelId = OBFUSCATED_NOTIFICATION_CHANNEL_ID; + return ret; + } + /** * Returns the locusId for this event if the event is of type {@link #LOCUS_ID_SET}, * otherwise it returns null. diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 5668944dfd4e..2c701b48455c 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -599,7 +599,8 @@ public final class UsageStatsManager { /** * 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 49f62f407806..ae12de027e6e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4064,16 +4064,16 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a - * {@link android.net.wifi.WifiCondManager} for handling management of the Wi-Fi control - * daemon. + * {@link android.net.wifi.wificond.WifiNl80211Manager} for handling management of the + * Wi-Fi nl802.11 daemon (wificond). * * @see #getSystemService(String) - * @see android.net.wifi.WifiCondManager + * @see android.net.wifi.wificond.WifiNl80211Manager * @hide */ @SystemApi @SuppressLint("ServiceName") - public static final String WIFI_COND_SERVICE = "wificond"; + public static final String WIFI_NL80211_SERVICE = "wifinl80211"; /** * Use with {@link #getSystemService(String)} to retrieve a {@link @@ -5098,10 +5098,11 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve an - * AppSearchManager for indexing and querying app data managed - * by the system. + * {@link android.app.appsearch.AppSearchManager} for + * indexing and querying app data managed by the system. * * @see #getSystemService(String) + * @hide */ public static final String APP_SEARCH_SERVICE = "app_search"; diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java index 475f019e7b26..9d37299e2373 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 @@ import java.util.Map; * * @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 @@ public class InstallerAllowedByManifestFormula extends IntegrityFormula { 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 ac4c9071f755..c5e5c8a8daad 100644 --- a/core/java/android/content/integrity/IntegrityFormula.java +++ b/core/java/android/content/integrity/IntegrityFormula.java @@ -214,6 +214,8 @@ public abstract class IntegrityFormula { 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/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index d251ba9519db..9d1c677f35c6 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -702,6 +702,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int PRIVATE_FLAG_ODM = 1 << 30; + /** + * Value for {@link #privateFlags}: If {@code true} this app allows heap tagging. + * {@link com.android.server.am.ProcessList#NATIVE_HEAP_POINTER_TAGGING} + * @hide + */ + public static final int PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING = 1 << 31; + /** @hide */ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = { PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE, @@ -733,6 +740,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE, PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE, PRIVATE_FLAG_ODM, + PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING, }) @Retention(RetentionPolicy.SOURCE) public @interface ApplicationInfoPrivateFlags {} @@ -1878,6 +1886,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return (privateFlags & PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE) != 0; } + /** + * If {@code true} this app allows heap pointer tagging. + * + * @hide + */ + public boolean allowsNativeHeapPointerTagging() { + return (privateFlags & PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING) != 0; + } + private boolean isAllowedToUseHiddenApis() { if (isSignedWithPlatformKey()) { return true; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b9a6f2f0f6c7..7b484b7c2c3d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -38,7 +38,7 @@ import android.app.PackageInstallObserver; import android.app.admin.DevicePolicyManager; import android.app.usage.StorageStatsManager; import android.compat.annotation.ChangeId; -import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -3386,6 +3386,14 @@ public abstract class PackageManager { public static final int FLAG_PERMISSION_AUTO_REVOKE_USER_SET = 1 << 18; /** + * Permission flag: Whether permission was revoked by auto-revoke. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 20; + + /** * Permission flags: Reserved for use by the permission controller. * * @hide @@ -3438,7 +3446,8 @@ public abstract class PackageManager { | FLAG_PERMISSION_REVOKED_COMPAT | FLAG_PERMISSION_ONE_TIME | FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED - | FLAG_PERMISSION_AUTO_REVOKE_USER_SET; + | FLAG_PERMISSION_AUTO_REVOKE_USER_SET + | FLAG_PERMISSION_AUTO_REVOKED; /** * Injected activity in app that forwards user to setting activity of that app. @@ -3582,7 +3591,7 @@ public abstract class PackageManager { * @hide */ @ChangeId - @Disabled + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) public static final long FILTER_APPLICATION_QUERY = 135549675L; /** {@hide} */ @@ -4263,7 +4272,8 @@ public abstract class PackageManager { FLAG_PERMISSION_REVOKED_COMPAT, FLAG_PERMISSION_ONE_TIME, FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED, - FLAG_PERMISSION_AUTO_REVOKE_USER_SET + FLAG_PERMISSION_AUTO_REVOKE_USER_SET, + FLAG_PERMISSION_AUTO_REVOKED }) @Retention(RetentionPolicy.SOURCE) public @interface PermissionFlags {} @@ -7402,6 +7412,7 @@ public abstract class PackageManager { case FLAG_PERMISSION_ONE_TIME: return "ONE_TIME"; case FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED: return "AUTO_REVOKE_IF_UNUSED"; case FLAG_PERMISSION_AUTO_REVOKE_USER_SET: return "AUTO_REVOKE_USER_SET"; + case FLAG_PERMISSION_AUTO_REVOKED: return "AUTO_REVOKED"; default: return Integer.toString(flag); } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 637e64d3d2c2..da44f70178de 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3710,6 +3710,11 @@ public class PackageParser { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE; } + if (sa.getBoolean( + R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, true)) { + ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING; + } + ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0); ai.minAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0); diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java index 548d82a6ab76..a2671136ff7b 100644 --- a/core/java/android/content/pm/parsing/ApkParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkParseUtils.java @@ -2098,6 +2098,9 @@ public class ApkParseUtils { R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, parsingPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q)); + parsingPackage.setAllowNativeHeapPointerTagging(sa.getBoolean( + R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, true)); + parsingPackage .setMaxAspectRatio( sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0)) diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java index 0df950006f43..778d7b8b26b6 100644 --- a/core/java/android/content/pm/parsing/PackageImpl.java +++ b/core/java/android/content/pm/parsing/PackageImpl.java @@ -1509,6 +1509,16 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android } @Override + public PackageImpl setAllowNativeHeapPointerTagging(boolean allowNativeHeapPointerTagging) { + this.privateFlags = allowNativeHeapPointerTagging + ? this.privateFlags | ApplicationInfo + .PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING + : this.privateFlags & ~ApplicationInfo + .PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING; + return this; + } + + @Override public PackageImpl setUsesNonSdkApi(boolean usesNonSdkApi) { this.privateFlags = usesNonSdkApi ? this.privateFlags | ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index a2fe064b66c3..954d65c75730 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -191,6 +191,8 @@ public interface ParsingPackage extends AndroidPackage { ParsingPackage setRequestLegacyExternalStorage(boolean requestLegacyExternalStorage); + ParsingPackage setAllowNativeHeapPointerTagging(boolean allowNativeHeapPointerTagging); + ParsingPackage setRestoreAnyVersion(boolean restoreAnyVersion); ParsingPackage setSplitHasCode(int splitIndex, boolean splitHasCode); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 471e83c4c3eb..cb809da3b867 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -62,6 +62,7 @@ import android.view.DisplayAdjustments; 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 @@ public class Resources { 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 @@ public class Resources { @UnsupportedAppUsage final ClassLoader mClassLoader; + @GuardedBy("mUpdateLock") private UpdateCallbacks mCallbacks = null; /** @@ -2375,6 +2377,7 @@ public class Resources { * * <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 @@ public class Resources { } /** - * 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); + + for (int i = 0; i < loaders.length; i++) { + final ResourcesLoader loader = loaders[i]; + if (!loaderSet.contains(loader)) { + newLoaders.add(loader); + } + } - final List<ResourcesLoader> loaders = new ArrayList<>( - mResourcesImpl.getAssets().getLoaders()); - if (!loaders.remove(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 69daceeaffc2..58fec603a2d5 100644 --- a/core/java/android/content/res/loader/ResourcesLoader.java +++ b/core/java/android/content/res/loader/ResourcesLoader.java @@ -40,8 +40,8 @@ import java.util.List; * 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/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index ef28e6c6db2a..ac36188ee61a 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -75,6 +75,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * @hide */ + public static final String KEY_DEVICE_CREDENTIAL_TITLE = "device_credential_title"; + /** + * @hide + */ + public static final String KEY_DEVICE_CREDENTIAL_SUBTITLE = "device_credential_subtitle"; + /** + * @hide + */ + public static final String KEY_DEVICE_CREDENTIAL_DESCRIPTION = "device_credential_description"; + /** + * @hide + */ public static final String KEY_NEGATIVE_TEXT = "negative_text"; /** * @hide @@ -221,6 +233,30 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Sets an optional title, subtitle, and/or description that will override other text when + * the user is authenticating with PIN/pattern/password. Currently for internal use only. + * @return This builder. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + @NonNull + public Builder setTextForDeviceCredential( + @Nullable CharSequence title, + @Nullable CharSequence subtitle, + @Nullable CharSequence description) { + if (title != null) { + mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_TITLE, title); + } + if (subtitle != null) { + mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_SUBTITLE, subtitle); + } + if (description != null) { + mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_DESCRIPTION, description); + } + return this; + } + + /** * Required: Sets the text, executor, and click listener for the negative button on the * prompt. This is typically a cancel button, but may be also used to show an alternative * method for authentication, such as a screen that asks for a backup password. diff --git a/core/java/android/inputmethodservice/OWNERS b/core/java/android/inputmethodservice/OWNERS new file mode 100644 index 000000000000..444719701df2 --- /dev/null +++ b/core/java/android/inputmethodservice/OWNERS @@ -0,0 +1,3 @@ +set noparent + +include ../../../../services/core/java/com/android/server/inputmethod/OWNERS diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java index fb35b4bde303..8afeb3033cdb 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.Parcel; 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 @@ public class CaptivePortal implements Parcelable { 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 @@ public class CaptivePortal implements Parcelable { */ @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 @@ public class CaptivePortal implements Parcelable { */ @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 09ec6c35fcb9..d83715c692f7 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -51,7 +51,7 @@ import java.net.Socket; * * <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 6d46c205b215..3c1b86b9f196 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -548,32 +548,32 @@ public class NetworkStatsHistory implements Parcelable { 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/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 6450a67572bf..6516917afd9d 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -16,6 +16,7 @@ package android.os.incremental; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -47,6 +48,15 @@ public class V4Signature { } /** + * Construct a V4Signature from .idsig file. + */ + public static V4Signature readFrom(byte[] bytes) throws IOException { + try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) { + return readFrom(stream); + } + } + + /** * Store the V4Signature to a byte-array. */ public byte[] toByteArray() { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7c4ec8e00650..37efec34b330 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -997,8 +997,9 @@ public final class Settings { * <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 @@ public final class Settings { * 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 e65bd9f20ec4..d2735008611c 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -301,6 +301,13 @@ public class PhoneStateListener { 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 @@ public class PhoneStateListener { } /** + * 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 @@ public class PhoneStateListener { () -> 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 4024db1e16c4..2c077bbdc772 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -589,6 +589,24 @@ public class TelephonyRegistryManager { } /** + * 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 25480683c8a8..9cd6050efc0b 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -882,6 +882,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } 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/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java index 06cb51927ba8..0c1edac6608c 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -19,7 +19,6 @@ package android.view; import android.animation.Animator; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; -import android.compat.annotation.UnsupportedAppUsage; import android.graphics.CanvasProperty; import android.graphics.Paint; import android.graphics.RecordingCanvas; @@ -109,12 +108,10 @@ public class RenderNodeAnimator extends Animator { private long mStartDelay = 0; private long mStartTime; - @UnsupportedAppUsage public static int mapViewPropertyToRenderProperty(int viewProperty) { return sViewPropertyAnimatorMap.get(viewProperty); } - @UnsupportedAppUsage public RenderNodeAnimator(int property, float finalValue) { mRenderProperty = property; mFinalValue = finalValue; @@ -122,7 +119,6 @@ public class RenderNodeAnimator extends Animator { init(nCreateAnimator(property, finalValue)); } - @UnsupportedAppUsage public RenderNodeAnimator(CanvasProperty<Float> property, float finalValue) { init(nCreateCanvasPropertyFloatAnimator( property.getNativeContainer(), finalValue)); @@ -137,7 +133,6 @@ public class RenderNodeAnimator extends Animator { * {@link #PAINT_STROKE_WIDTH} * @param finalValue The target value for the property */ - @UnsupportedAppUsage public RenderNodeAnimator(CanvasProperty<Paint> property, int paintField, float finalValue) { init(nCreateCanvasPropertyPaintAnimator( property.getNativeContainer(), paintField, finalValue)); @@ -289,7 +284,6 @@ public class RenderNodeAnimator extends Animator { } /** @hide */ - @UnsupportedAppUsage public void setTarget(View view) { mViewTarget = view; setTarget(mViewTarget.mRenderNode); @@ -301,7 +295,6 @@ public class RenderNodeAnimator extends Animator { } /** @hide */ - @UnsupportedAppUsage public void setTarget(DisplayListCanvas canvas) { setTarget((RecordingCanvas) canvas); } @@ -316,7 +309,6 @@ public class RenderNodeAnimator extends Animator { mTarget.addAnimator(this); } - @UnsupportedAppUsage public void setStartValue(float startValue) { checkMutable(); nSetStartValue(mNativePtr.get(), startValue); @@ -337,7 +329,6 @@ public class RenderNodeAnimator extends Animator { return mUnscaledStartDelay; } - @UnsupportedAppUsage @Override public RenderNodeAnimator setDuration(long duration) { checkMutable(); @@ -502,7 +493,6 @@ public class RenderNodeAnimator extends Animator { } // Called by native - @UnsupportedAppUsage private static void callOnFinished(RenderNodeAnimator animator) { if (animator.mHandler != null) { animator.mHandler.post(animator::onFinished); diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index bf848196454d..680a8789a6b8 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -24,6 +24,7 @@ import android.graphics.PixelFormat; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.view.accessibility.IAccessibilityEmbeddedConnection; /** * Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy @@ -40,6 +41,7 @@ public class SurfaceControlViewHost { private WindowlessWindowManager mWm; private SurfaceControl mSurfaceControl; + private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; /** * Package encapsulating a Surface hierarchy which contains interactive view @@ -49,15 +51,18 @@ public class SurfaceControlViewHost { */ public static final class SurfacePackage implements Parcelable { private final SurfaceControl mSurfaceControl; - // TODO: Accessibility ID goes here + private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; - SurfacePackage(SurfaceControl sc) { + SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection) { mSurfaceControl = sc; + mAccessibilityEmbeddedConnection = connection; } private SurfacePackage(Parcel in) { mSurfaceControl = new SurfaceControl(); mSurfaceControl.readFromParcel(in); + mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface( + in.readStrongBinder()); } /** @@ -69,6 +74,16 @@ public class SurfaceControlViewHost { return mSurfaceControl; } + /** + * Gets an accessibility embedded connection interface for this SurfaceControlViewHost. + * + * @return {@link IAccessibilityEmbeddedConnection} interface. + * @hide + */ + public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() { + return mAccessibilityEmbeddedConnection; + } + @Override public int describeContents() { return 0; @@ -77,6 +92,7 @@ public class SurfaceControlViewHost { @Override public void writeToParcel(@NonNull Parcel out, int flags) { mSurfaceControl.writeToParcel(out, flags); + out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder()); } public static final @NonNull Creator<SurfacePackage> CREATOR @@ -95,6 +111,7 @@ public class SurfaceControlViewHost { @NonNull WindowlessWindowManager wwm) { mWm = wwm; mViewRoot = new ViewRootImpl(c, d, mWm); + mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } /** @@ -118,6 +135,7 @@ public class SurfaceControlViewHost { mWm = new WindowlessWindowManager(context.getResources().getConfiguration(), mSurfaceControl, hostToken); mViewRoot = new ViewRootImpl(context, display, mWm); + mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } /** @@ -128,8 +146,8 @@ public class SurfaceControlViewHost { * are linked. */ public @Nullable SurfacePackage getSurfacePackage() { - if (mSurfaceControl != null) { - return new SurfacePackage(mSurfaceControl); + if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) { + return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection); } else { return null; } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index d5ed36b57c02..47ffd3e2714c 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -28,6 +28,7 @@ import android.content.res.CompatibilityInfo.Translator; import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PorterDuff; @@ -38,12 +39,14 @@ import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceControl.Transaction; import android.view.SurfaceControlViewHost; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.IAccessibilityEmbeddedConnection; import com.android.internal.view.SurfaceCallbackHelper; @@ -203,8 +206,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); private int mParentSurfaceGenerationId; - // The token of embedded windowless view hierarchy. - private IBinder mEmbeddedViewHierarchy; + private RemoteAccessibilityEmbeddedConnection mRemoteAccessibilityEmbeddedConnection; + + private final Matrix mScreenMatrixForEmbeddedHierarchy = new Matrix(); + private final Matrix mTmpMatrix = new Matrix(); + private final float[] mMatrixValues = new float[9]; + SurfaceControlViewHost.SurfacePackage mSurfacePackage; public SurfaceView(Context context) { @@ -923,6 +930,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } mTmpTransaction.apply(); + updateScreenMatrixForEmbeddedHierarchy(); if (sizeChanged || creating) { redrawNeeded = true; @@ -1510,6 +1518,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void surfaceDestroyed() { setWindowStopped(true); + setRemoteAccessibilityEmbeddedConnection(null, null); } /** @@ -1568,31 +1577,133 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void reparentSurfacePackage(SurfaceControl.Transaction t, SurfaceControlViewHost.SurfacePackage p) { - // TODO: Link accessibility IDs here. + initEmbeddedHierarchyForAccessibility(p); final SurfaceControl sc = p.getSurfaceControl(); t.reparent(sc, mSurfaceControl).show(sc); } - /** - * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view - * hierarchy. - * - * @param token IBinder token. - * @hide - */ - public void setEmbeddedViewHierarchy(IBinder token) { - mEmbeddedViewHierarchy = token; - } - /** @hide */ @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); - if (mEmbeddedViewHierarchy == null) { + final RemoteAccessibilityEmbeddedConnection wrapper = + getRemoteAccessibilityEmbeddedConnection(); + if (wrapper == null) { return; } // Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this // leashed child would return the root node in the embedded hierarchy - info.addChild(mEmbeddedViewHierarchy); + info.addChild(wrapper.getLeashToken()); + } + + private void initEmbeddedHierarchyForAccessibility(SurfaceControlViewHost.SurfacePackage p) { + final IAccessibilityEmbeddedConnection connection = p.getAccessibilityEmbeddedConnection(); + final RemoteAccessibilityEmbeddedConnection wrapper = + getRemoteAccessibilityEmbeddedConnection(); + + // Do nothing if package is embedding the same view hierarchy. + if (wrapper != null && wrapper.getConnection().equals(connection)) { + return; + } + + // If this SurfaceView embeds a different view hierarchy, unlink the previous one first. + setRemoteAccessibilityEmbeddedConnection(null, null); + + try { + final IBinder leashToken = connection.associateEmbeddedHierarchy( + getViewRootImpl().mLeashToken, getAccessibilityViewId()); + setRemoteAccessibilityEmbeddedConnection(connection, leashToken); + } catch (RemoteException e) { + Log.d(TAG, "Error while associateEmbeddedHierarchy " + e); + } + updateScreenMatrixForEmbeddedHierarchy(); + } + + private void setRemoteAccessibilityEmbeddedConnection( + IAccessibilityEmbeddedConnection connection, IBinder leashToken) { + try { + if (mRemoteAccessibilityEmbeddedConnection != null) { + mRemoteAccessibilityEmbeddedConnection.getConnection() + .disassociateEmbeddedHierarchy(); + mRemoteAccessibilityEmbeddedConnection.unlinkToDeath(); + mRemoteAccessibilityEmbeddedConnection = null; + } + if (connection != null && leashToken != null) { + mRemoteAccessibilityEmbeddedConnection = + new RemoteAccessibilityEmbeddedConnection(connection, leashToken); + mRemoteAccessibilityEmbeddedConnection.linkToDeath(); + } + } catch (RemoteException e) { + Log.d(TAG, "Error while setRemoteEmbeddedConnection " + e); + } + } + + private RemoteAccessibilityEmbeddedConnection getRemoteAccessibilityEmbeddedConnection() { + return mRemoteAccessibilityEmbeddedConnection; + } + + private void updateScreenMatrixForEmbeddedHierarchy() { + mTmpMatrix.reset(); + mTmpMatrix.setTranslate(mScreenRect.left, mScreenRect.top); + mTmpMatrix.postScale(mScreenRect.width() / (float) mSurfaceWidth, + mScreenRect.height() / (float) mSurfaceHeight); + + // If the screen matrix is identity or doesn't change, do nothing. + if (mTmpMatrix.isIdentity() || mTmpMatrix.equals(mScreenMatrixForEmbeddedHierarchy)) { + return; + } + + try { + final RemoteAccessibilityEmbeddedConnection wrapper = + getRemoteAccessibilityEmbeddedConnection(); + if (wrapper == null) { + return; + } + mTmpMatrix.getValues(mMatrixValues); + wrapper.getConnection().setScreenMatrix(mMatrixValues); + mScreenMatrixForEmbeddedHierarchy.set(mTmpMatrix); + } catch (RemoteException e) { + Log.d(TAG, "Error while setScreenMatrix " + e); + } + } + + /** + * Wrapper of accessibility embedded connection for embedded view hierarchy. + */ + private final class RemoteAccessibilityEmbeddedConnection implements IBinder.DeathRecipient { + private final IAccessibilityEmbeddedConnection mConnection; + private final IBinder mLeashToken; + + RemoteAccessibilityEmbeddedConnection(IAccessibilityEmbeddedConnection connection, + IBinder leashToken) { + mConnection = connection; + mLeashToken = leashToken; + } + + IAccessibilityEmbeddedConnection getConnection() { + return mConnection; + } + + IBinder getLeashToken() { + return mLeashToken; + } + + void linkToDeath() throws RemoteException { + mConnection.asBinder().linkToDeath(this, 0); + } + + void unlinkToDeath() { + mConnection.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + unlinkToDeath(); + runOnUiThread(() -> { + if (mRemoteAccessibilityEmbeddedConnection == this) { + mRemoteAccessibilityEmbeddedConnection = null; + } + }); + } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 5964e121151a..b971a20d11c9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -223,26 +223,26 @@ public final class ViewRootImpl implements ViewParent, * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_NONE = 0; + public static int sNewInsetsMode = + SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0); /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_IME = 1; + public static final int NEW_INSETS_MODE_NONE = 0; /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_FULL = 2; + public static final int NEW_INSETS_MODE_IME = 1; /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static int sNewInsetsMode = - SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, NEW_INSETS_MODE_IME); + public static final int NEW_INSETS_MODE_FULL = 2; /** * Set this system property to true to force the view hierarchy to render @@ -655,7 +655,7 @@ public final class ViewRootImpl implements ViewParent, private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); - private IAccessibilityEmbeddedConnection mEmbeddedConnection; + private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; static final class SystemUiVisibilityInfo { int seq; @@ -9370,11 +9370,12 @@ public final class ViewRootImpl implements ViewParent, * Gets an accessibility embedded connection interface for this ViewRootImpl. * @hide */ - public IAccessibilityEmbeddedConnection getEmbeddedConnection() { - if (mEmbeddedConnection == null) { - mEmbeddedConnection = new AccessibilityEmbeddedConnection(ViewRootImpl.this); + public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() { + if (mAccessibilityEmbeddedConnection == null) { + mAccessibilityEmbeddedConnection = new AccessibilityEmbeddedConnection( + ViewRootImpl.this); } - return mEmbeddedConnection; + return mAccessibilityEmbeddedConnection; } private class SendWindowContentChangedAccessibilityEvent implements Runnable { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 56683dd9a5d1..3274449b95c8 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -16,6 +16,9 @@ package android.view; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + import android.annotation.NonNull; import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; @@ -67,10 +70,6 @@ public final class WindowManagerImpl implements WindowManager { private IBinder mDefaultToken; - private boolean mIsViewAdded; - private View mLastView; - private WindowManager.LayoutParams mLastParams; - public WindowManagerImpl(Context context) { this(context, null); } @@ -102,9 +101,6 @@ public final class WindowManagerImpl implements WindowManager { public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); - mIsViewAdded = true; - mLastView = view; - mLastParams = (WindowManager.LayoutParams) params; } @Override @@ -250,18 +246,15 @@ public final class WindowManagerImpl implements WindowManager { // TODO(window-context): This can only be properly implemented // once we flip the new insets mode flag. if (mParentWindow != null) { - if (mParentWindow.getDecorView().isAttachedToWindow()) { - return mParentWindow.getDecorView().getViewRootImpl() - .getWindowInsets(true /* forceConstruct */); - } return getWindowInsetsFromServer(mParentWindow.getAttributes()); } - if (mIsViewAdded) { - return mLastView.getViewRootImpl().getWindowInsets(true /* forceConstruct */); - } else { - return getWindowInsetsFromServer(new WindowManager.LayoutParams()); - } + return getWindowInsetsFromServer(getDefaultParams()); + } + private static WindowManager.LayoutParams getDefaultParams() { + final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; + return params; } private WindowInsets getWindowInsetsFromServer(WindowManager.LayoutParams attrs) { diff --git a/core/java/android/view/inputmethod/OWNERS b/core/java/android/view/inputmethod/OWNERS new file mode 100644 index 000000000000..244cc30e089e --- /dev/null +++ b/core/java/android/view/inputmethod/OWNERS @@ -0,0 +1,3 @@ +set noparent + +include ../../../../../services/core/java/com/android/server/inputmethod/OWNERS diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java index 8a1a0b5ef9b0..556b24c94b36 100644 --- a/core/java/android/webkit/UserPackage.java +++ b/core/java/android/webkit/UserPackage.java @@ -34,7 +34,7 @@ public class UserPackage { private final UserInfo mUserInfo; private final PackageInfo mPackageInfo; - public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.Q; + public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.R; public UserPackage(UserInfo user, PackageInfo packageInfo) { this.mUserInfo = user; diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 941af6ef1d7a..8790bbdcd8f7 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -47,7 +47,7 @@ public final class WebViewFactory { // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote. /** @hide */ private static final String CHROMIUM_WEBVIEW_FACTORY = - "com.android.webview.chromium.WebViewChromiumFactoryProviderForQ"; + "com.android.webview.chromium.WebViewChromiumFactoryProviderForR"; private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create"; diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index 93659a4ac1eb..a1c22e9994c8 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 android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHOR 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 android.widget.Toast; 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 @@ public class AccessibilityShortcutController { 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 @@ public class AccessibilityShortcutController { 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 @@ public class AccessibilityShortcutController { 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 @@ public class AccessibilityShortcutController { 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 @@ public class AccessibilityShortcutController { } 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 @@ public class AccessibilityShortcutController { 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 022573c5203a..c2c9fff401a3 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1278,10 +1278,9 @@ public class ResolverActivity extends Activity implements 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 @@ public class ResolverActivity extends Activity implements } setContentView(mLayoutId); mMultiProfilePagerAdapter.setupViewPager(findViewById(R.id.profile_pager)); - return postRebuildList(rebuildCompleted); + return postRebuildList(rebuildActiveCompleted && rebuildInactiveCompleted); } /** @@ -1338,10 +1337,11 @@ public class ResolverActivity extends Activity implements 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 @@ public class ResolverActivity extends Activity implements 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 ea8409058264..54453d0d0f46 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -87,6 +87,7 @@ public class ResolverListAdapter extends BaseAdapter { 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 @@ public class ResolverListAdapter extends BaseAdapter { mLastChosenPosition = -1; mAllTargetsAreBrowsers = false; mDisplayList.clear(); + mIsListLoaded = false; if (mBaseResolveList != null) { currentResolveList = mUnfilteredResolveList = new ArrayList<>(); @@ -352,6 +354,7 @@ public class ResolverListAdapter extends BaseAdapter { mResolverListCommunicator.sendVoiceChoicesIfNeeded(); postListReadyRunnable(doPostProcessing); + mIsListLoaded = true; } /** @@ -611,6 +614,10 @@ public class ResolverListAdapter extends BaseAdapter { 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/inputmethod/OWNERS b/core/java/com/android/internal/inputmethod/OWNERS new file mode 100644 index 000000000000..fc0e5d4dea2b --- /dev/null +++ b/core/java/com/android/internal/inputmethod/OWNERS @@ -0,0 +1,3 @@ +set noparent + +include ../../../../../../services/core/java/com/android/server/inputmethod/OWNERS diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 518911e652f6..0e9c2c4dd659 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -19,8 +19,6 @@ package com.android.internal.os; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.ApplicationErrorReport; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.type.DefaultMimeMapFactory; import android.os.Build; @@ -36,7 +34,6 @@ import android.util.Slog; import com.android.internal.logging.AndroidConfig; import com.android.server.NetworkManagementSocketTagger; -import dalvik.annotation.compat.VersionCodes; import dalvik.system.RuntimeHooks; import dalvik.system.ThreadPrioritySetter; import dalvik.system.VMRuntime; @@ -67,18 +64,8 @@ public class RuntimeInit { private static volatile boolean mCrashing = false; - /** - * Native heap allocations will now have a non-zero tag in the most significant byte. - * See - * <a href="https://source.android.com/devices/tech/debug/tagged-pointers">https://source.android.com/devices/tech/debug/tagged-pointers</a>. - */ - @ChangeId - @EnabledAfter(targetSdkVersion = VersionCodes.Q) - private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. - private static final native void nativeFinishInit(); private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup); - private static native void nativeDisableHeapPointerTagging(); private static int Clog_e(String tag, String msg, Throwable tr) { return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr); @@ -411,20 +398,6 @@ public class RuntimeInit { if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!"); } - private static void maybeDisableHeapPointerTagging(long[] disabledCompatChanges) { - // Heap tagging needs to be disabled before any additional threads are created, but the - // AppCompat framework is not initialized enough at this point. - // Check if the change is enabled manually. - if (disabledCompatChanges != null) { - for (int i = 0; i < disabledCompatChanges.length; i++) { - if (disabledCompatChanges[i] == NATIVE_HEAP_POINTER_TAGGING) { - nativeDisableHeapPointerTagging(); - break; - } - } - } - } - protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { // If the application calls System.exit(), terminate the process @@ -437,8 +410,6 @@ public class RuntimeInit { VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion); VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges); - maybeDisableHeapPointerTagging(disabledCompatChanges); - final Arguments args = new Arguments(argv); // The end of of the RuntimeInit event (see #zygoteInit). diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index dfd700f51103..556586477218 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -122,6 +122,25 @@ public final class Zygote { */ public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18; + public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20); + /** + * Enable pointer tagging in this process. + * Tags are checked during memory deallocation, but not on access. + * TBI stands for Top-Byte-Ignore, an ARM CPU feature. + * {@link https://developer.arm.com/docs/den0024/latest/the-memory-management-unit/translation-table-configuration/virtual-address-tagging} + */ + public static final int MEMORY_TAG_LEVEL_TBI = 1 << 19; + + /** + * Enable asynchronous memory tag checks in this process. + */ + public static final int MEMORY_TAG_LEVEL_ASYNC = 2 << 19; + + /** + * Enable synchronous memory tag checks in this process. + */ + public static final int MEMORY_TAG_LEVEL_SYNC = 3 << 19; + /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; /** Default external storage should be mounted. */ diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index ae54eb210de7..e34aa9722ec2 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -788,6 +788,10 @@ public class ZygoteInit { Zygote.applyDebuggerSystemProperty(parsedArgs); Zygote.applyInvokeWithSystemProperty(parsedArgs); + /* Enable pointer tagging in the system server unconditionally. Hardware support for + * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */ + parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + if (shouldProfileSystemServer()) { parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; } diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl index 0f50596f935d..3d5dfbbb871a 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.CallAttributes; 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 @@ oneway interface IPhoneStateListener { 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 47752c5b2d94..520ffc9584e2 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.BarringInfo; 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 @@ interface ITelephonyRegistry { 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 0b1c8b7f5335..19f6e2750219 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -243,14 +243,6 @@ static void com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup(JNIE gCurRuntime->setExitWithoutCleanup(exitWithoutCleanup); } -static void com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging( - JNIEnv* env, jobject clazz) { - HeapTaggingLevel tag_level = M_HEAP_TAGGING_LEVEL_NONE; - if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &tag_level, sizeof(tag_level))) { - ALOGE("ERROR: could not disable heap pointer tagging\n"); - } -} - /* * JNI registration. */ @@ -262,8 +254,6 @@ int register_com_android_internal_os_RuntimeInit(JNIEnv* env) (void*)com_android_internal_os_RuntimeInit_nativeFinishInit}, {"nativeSetExitWithoutCleanup", "(Z)V", (void*)com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup}, - {"nativeDisableHeapPointerTagging", "()V", - (void*)com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging}, }; return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit", methods, NELEM(methods)); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 7a9a3f8643c0..9fbb8df0d079 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -349,6 +349,8 @@ static const std::array<const std::string, MOUNT_EXTERNAL_COUNT> ExternalStorage enum RuntimeFlags : uint32_t { DEBUG_ENABLE_JDWP = 1, PROFILE_FROM_SHELL = 1 << 15, + MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20), + MEMORY_TAG_LEVEL_TBI = 1 << 19, }; enum UnsolicitedZygoteMessageTypes : uint32_t { @@ -1627,6 +1629,16 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } + HeapTaggingLevel heap_tagging_level; + switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) { + case RuntimeFlags::MEMORY_TAG_LEVEL_TBI: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI; + break; + default: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE; + } + android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level)); + if (NeedsNoRandomizeWorkaround()) { // Work around ARM kernel ASLR lossage (http://b/5817320). int old_personality = personality(0xffffffff); diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index bf4cdee72b2d..03676dd5a1c6 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -428,7 +428,7 @@ message IncidentProto { (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 d1392a5e0f31..a64883102186 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 @@ enum EventId { 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 34ed90a8c90c..e1af9622adb3 100644 --- a/core/proto/android/stats/mediametrics/mediametrics.proto +++ b/core/proto/android/stats/mediametrics/mediametrics.proto @@ -154,6 +154,8 @@ message CodecData { 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 57ba7fe1d143..60b3a19c4cf3 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" diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index c66261bb6630..e231c3a323fd 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1782,6 +1782,15 @@ The default value is {@code false}. --> <attr name="crossProfile" format="boolean" /> + + <!-- If {@code true} this app will receive tagged pointers to native heap allocations + from functions like malloc() on compatible devices. Note that this may not always + be respected due to policy or backwards compatibility reasons. See the + <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged Pointers</a> + document for more information on this feature. + + The default value is {@code true}. --> + <attr name="allowNativeHeapPointerTagging" format="boolean" /> </declare-styleable> <!-- The <code>feature</code> tag declares a feature. A feature is a logical part of an app. diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 4172044dfa13..0edad3b0d9cd 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3017,6 +3017,7 @@ <public name="autofillInlineSuggestionSubtitle" /> <!-- @hide @SystemApi --> <public name="isAutofillInlineSuggestionTheme" /> + <public name="allowNativeHeapPointerTagging" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 1ba832377001..e6a93e550a5f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4376,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/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt index 9e94bdc8a081..afe9d7f19f0d 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 @@ class DirectoryAssetsProviderTest : ResourceLoaderTestBase() { 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 e3ba93d64b0f..da5092de0627 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 @@ class ResourceLoaderAssetsTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderAssetsTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderAssetsTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderAssetsTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderAssetsTest : ResourceLoaderTestBase() { 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 0cc56d721651..16eafcd451c2 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { } @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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { } @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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { val testTwo = openTwo() val loader = ResourcesLoader() - resources.addLoader(loader) + resources.addLoaders(loader) loader.providers = listOf(testOne, testTwo) assertEquals(valueTwo, getValue()) @@ -254,20 +254,20 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { } @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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { val testTwo = openTwo() val loader = ResourcesLoader() - resources.addLoader(loader) + resources.addLoaders(loader) loader.addProvider(testOne) loader.addProvider(testTwo) @@ -341,12 +341,16 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { } @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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { val testTwo = openTwo() val loader = ResourcesLoader() - resources.addLoader(loader) + resources.addLoaders(loader) loader.addProvider(testOne) assertEquals(valueOne, getValue()) @@ -527,7 +532,7 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 59335a595334..be94f02dc3e7 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -138,6 +138,9 @@ <!-- vr test permissions --> <uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" /> + <!-- WindowMetricsTest permissions --> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + <application android:theme="@style/Theme" android:supportsRtl="true"> <uses-library android:name="android.test.runner" /> <uses-library android:name="org.apache.http.legacy" android:required="false" /> diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java index c897ace0e0b5..693d4cae1f1c 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 @@ public class InstallerAllowedByManifestFormulaTest { } @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 @@ public class InstallerAllowedByManifestFormulaTest { 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/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java new file mode 100644 index 000000000000..d2eb8f5f5573 --- /dev/null +++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java @@ -0,0 +1,81 @@ +/* + * 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.view; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.FlakyTest; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +/** + * Tests for {@link WindowManager#getCurrentWindowMetrics()} and + * {@link WindowManager#getMaximumWindowMetrics()}. + * + * <p>Build/Install/Run: + * atest FrameworksCoreTests:WindowMetricsTest + * + * <p>This test class is a part of Window Manager Service tests and specified in + * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}. + */ +@FlakyTest(bugId = 148789183, detail = "Remove after confirmed it's stable.") +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowMetricsTest { + private Context mWindowContext; + private WindowManager mWm; + + @Before + public void setUp() { + final Context insetContext = InstrumentationRegistry.getInstrumentation() + .getTargetContext(); + final Display display = insetContext.getSystemService(DisplayManager.class) + .getDisplay(DEFAULT_DISPLAY); + mWindowContext = insetContext.createDisplayContext(display) + .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */); + mWm = mWindowContext.getSystemService(WindowManager.class); + } + + @Test + public void testAddViewANdRemoveView_GetMetrics_DoNotCrash() { + final View view = new View(mWindowContext); + final WindowManager.LayoutParams params = + new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + Handler.getMain().runWithScissors(() -> { + mWm.addView(view, params); + // Check get metrics do not crash. + mWm.getCurrentWindowMetrics(); + mWm.getMaximumWindowMetrics(); + + mWm.removeViewImmediate(view); + // Check get metrics do not crash. + mWm.getCurrentWindowMetrics(); + mWm.getMaximumWindowMetrics(); + }, 0); + } +} diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index af115b1e80c1..4b95e4d7fa6c 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -381,6 +381,8 @@ applications that come with the platform <permission name="android.permission.REBOOT"/> <!-- Permission required for access VIBRATOR_STATE. --> <permission name="android.permission.ACCESS_VIBRATOR_STATE"/> + <!-- Permission required for UsageStatsTest CTS test. --> + <permission name="android.permission.MANAGE_NOTIFICATIONS"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 06d4fbdd85b1..ce8ff7dc38ba 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -867,7 +867,8 @@ public abstract class ColorSpace { } } - private ColorSpace( + /** @hide */ + ColorSpace( @NonNull String name, @NonNull Model model, @IntRange(from = MIN_ID, to = MAX_ID) int id) { diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java new file mode 100644 index 000000000000..f9033a53d7e6 --- /dev/null +++ b/graphics/java/android/graphics/ParcelableColorSpace.java @@ -0,0 +1,183 @@ +/* + * 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.graphics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A {@link Parcelable} {@link ColorSpace}. In order to enable parceling, the ColorSpace + * must be either a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb} instance + * that has an ICC parametric transfer function as returned by {@link Rgb#getTransferParameters()}. + * TODO: Make public + * @hide + */ +public final class ParcelableColorSpace extends ColorSpace implements Parcelable { + private final ColorSpace mColorSpace; + + /** + * Checks if the given ColorSpace is able to be parceled. A ColorSpace can only be + * parceled if it is a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb} + * instance that has an ICC parametric transfer function as returned by + * {@link Rgb#getTransferParameters()} + */ + public static boolean isParcelable(@NonNull ColorSpace colorSpace) { + if (colorSpace.getId() == ColorSpace.MIN_ID) { + if (!(colorSpace instanceof ColorSpace.Rgb)) { + return false; + } + ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace; + if (rgb.getTransferParameters() == null) { + return false; + } + } + return true; + } + + /** + * Constructs a new ParcelableColorSpace that wraps the provided ColorSpace. + * + * @param colorSpace The ColorSpace to wrap. The ColorSpace must be either named or be an + * RGB ColorSpace with an ICC parametric transfer function. + * @throws IllegalArgumentException If the provided ColorSpace does not satisfy the requirements + * to be parceled. See {@link #isParcelable(ColorSpace)}. + */ + public ParcelableColorSpace(@NonNull ColorSpace colorSpace) { + super(colorSpace.getName(), colorSpace.getModel(), colorSpace.getId()); + mColorSpace = colorSpace; + + if (mColorSpace.getId() == ColorSpace.MIN_ID) { + if (!(mColorSpace instanceof ColorSpace.Rgb)) { + throw new IllegalArgumentException( + "Unable to parcel unknown ColorSpaces that are not ColorSpace.Rgb"); + } + ColorSpace.Rgb rgb = (ColorSpace.Rgb) mColorSpace; + if (rgb.getTransferParameters() == null) { + throw new IllegalArgumentException("ColorSpace must use an ICC " + + "parametric transfer function to be parcelable"); + } + } + } + + public @NonNull ColorSpace getColorSpace() { + return mColorSpace; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + final int id = mColorSpace.getId(); + dest.writeInt(id); + if (id == ColorSpace.MIN_ID) { + // Not a named color space. We have to actually write, like, stuff. And things. Ugh. + // Cast is safe because this was asserted in the constructor + ColorSpace.Rgb rgb = (ColorSpace.Rgb) mColorSpace; + dest.writeString(rgb.getName()); + dest.writeFloatArray(rgb.getPrimaries()); + dest.writeFloatArray(rgb.getWhitePoint()); + ColorSpace.Rgb.TransferParameters transferParameters = rgb.getTransferParameters(); + dest.writeDouble(transferParameters.a); + dest.writeDouble(transferParameters.b); + dest.writeDouble(transferParameters.c); + dest.writeDouble(transferParameters.d); + dest.writeDouble(transferParameters.e); + dest.writeDouble(transferParameters.f); + dest.writeDouble(transferParameters.g); + } + } + + @NonNull + public static final Parcelable.Creator<ParcelableColorSpace> CREATOR = + new Parcelable.Creator<ParcelableColorSpace>() { + + public @NonNull ParcelableColorSpace createFromParcel(@NonNull Parcel in) { + final int id = in.readInt(); + if (id == ColorSpace.MIN_ID) { + String name = in.readString(); + float[] primaries = in.createFloatArray(); + float[] whitePoint = in.createFloatArray(); + double a = in.readDouble(); + double b = in.readDouble(); + double c = in.readDouble(); + double d = in.readDouble(); + double e = in.readDouble(); + double f = in.readDouble(); + double g = in.readDouble(); + ColorSpace.Rgb.TransferParameters function = + new ColorSpace.Rgb.TransferParameters(a, b, c, d, e, f, g); + return new ParcelableColorSpace( + new ColorSpace.Rgb(name, primaries, whitePoint, function)); + } else { + return new ParcelableColorSpace(ColorSpace.get(id)); + } + } + + public ParcelableColorSpace[] newArray(int size) { + return new ParcelableColorSpace[size]; + } + }; + + @Override + public boolean isWideGamut() { + return mColorSpace.isWideGamut(); + } + + @Override + public float getMinValue(int component) { + return mColorSpace.getMinValue(component); + } + + @Override + public float getMaxValue(int component) { + return mColorSpace.getMaxValue(component); + } + + @Override + public @NonNull float[] toXyz(@NonNull float[] v) { + return mColorSpace.toXyz(v); + } + + @Override + public @NonNull float[] fromXyz(@NonNull float[] v) { + return mColorSpace.fromXyz(v); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ParcelableColorSpace other = (ParcelableColorSpace) o; + return mColorSpace.equals(other.mColorSpace); + } + + @Override + public int hashCode() { + return mColorSpace.hashCode(); + } + + /** @hide */ + @Override + long getNativeInstance() { + return mColorSpace.getNativeInstance(); + } +} diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index 89a9b997af97..84c07d7d9dff 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -16,16 +16,16 @@ #include "Readback.h" -#include "pipeline/skia/LayerDrawable.h" -#include "renderthread/EglManager.h" -#include "renderthread/VulkanManager.h" - -#include <gui/Surface.h> -#include <ui/Fence.h> +#include <sync/sync.h> +#include <system/window.h> #include <ui/GraphicBuffer.h> + #include "DeferredLayerUpdater.h" #include "Properties.h" #include "hwui/Bitmap.h" +#include "pipeline/skia/LayerDrawable.h" +#include "renderthread/EglManager.h" +#include "renderthread/VulkanManager.h" #include "utils/Color.h" #include "utils/MathUtils.h" #include "utils/TraceUtils.h" @@ -35,40 +35,43 @@ using namespace android::uirenderer::renderthread; namespace android { namespace uirenderer { -CopyResult Readback::copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap) { +CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap) { ATRACE_CALL(); // Setup the source - sp<GraphicBuffer> sourceBuffer; - sp<Fence> sourceFence; + AHardwareBuffer* rawSourceBuffer; + int rawSourceFence; Matrix4 texTransform; - status_t err = surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence, texTransform.data); + status_t err = ANativeWindow_getLastQueuedBuffer(window, &rawSourceBuffer, &rawSourceFence, + texTransform.data); + base::unique_fd sourceFence(rawSourceFence); texTransform.invalidateType(); if (err != NO_ERROR) { ALOGW("Failed to get last queued buffer, error = %d", err); return CopyResult::UnknownError; } - if (!sourceBuffer.get()) { + if (rawSourceBuffer == nullptr) { ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); return CopyResult::SourceEmpty; } - if (sourceBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) { + + std::unique_ptr<AHardwareBuffer, decltype(&AHardwareBuffer_release)> sourceBuffer( + rawSourceBuffer, AHardwareBuffer_release); + AHardwareBuffer_Desc description; + AHardwareBuffer_describe(sourceBuffer.get(), &description); + if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) { ALOGW("Surface is protected, unable to copy from it"); return CopyResult::SourceInvalid; } - err = sourceFence->wait(500 /* ms */); - if (err != NO_ERROR) { + + if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) { ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); return CopyResult::Timeout; } - if (!sourceBuffer.get()) { - return CopyResult::UnknownError; - } - sk_sp<SkColorSpace> colorSpace = - DataSpaceToColorSpace(static_cast<android_dataspace>(surface.getBuffersDataSpace())); - sk_sp<SkImage> image = SkImage::MakeFromAHardwareBuffer( - reinterpret_cast<AHardwareBuffer*>(sourceBuffer.get()), - kPremul_SkAlphaType, colorSpace); + sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace( + static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window))); + sk_sp<SkImage> image = + SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace); return copyImageInto(image, texTransform, srcRect, bitmap); } diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h index e86a8136cfa3..e36f1ff6a072 100644 --- a/libs/hwui/Readback.h +++ b/libs/hwui/Readback.h @@ -47,7 +47,7 @@ public: /** * Copies the surface's most recently queued buffer into the provided bitmap. */ - CopyResult copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap); + CopyResult copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap); CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap); diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index f9e401a2e93b..1e7fc71a7f04 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -317,8 +317,9 @@ void RenderProxy::setRenderAheadDepth(int renderAhead) { int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom, SkBitmap* bitmap) { auto& thread = RenderThread::getInstance(); + ANativeWindow* window = surface.get(); return static_cast<int>(thread.queue().runSync([&]() -> auto { - return thread.readback().copySurfaceInto(*surface, Rect(left, top, right, bottom), bitmap); + return thread.readback().copySurfaceInto(window, Rect(left, top, right, bottom), bitmap); })); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 4683e1d69019..ab0dd2bcc8f5 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -140,6 +140,10 @@ public: */ ANDROID_API void setRenderAheadDepth(int renderAhead); + // TODO: This api will need to take in an ANativeWindow instead, but the + // caller, ThreadedRenderer, doesn't have access to libandroid due to a + // circular dependency, so it can't use the JNI ANativeWindow methods. Once + // that is resolved then replace the surface type here. ANDROID_API static int copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom, SkBitmap* bitmap); ANDROID_API static void prepareToDraw(Bitmap& bitmap); diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java index 05fa511fc81a..2bffe8ac0649 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -156,9 +156,7 @@ public class MediaScannerConnection implements ServiceConnection { } BackgroundThread.getExecutor().execute(() -> { final Uri uri = scanFileQuietly(mProvider, new File(path)); - if (mClient != null) { - mClient.onScanCompleted(path, uri); - } + runCallBack(mContext, mClient, path, uri); }); } } @@ -187,9 +185,7 @@ public class MediaScannerConnection implements ServiceConnection { .acquireContentProviderClient(MediaStore.AUTHORITY)) { for (String path : paths) { final Uri uri = scanFileQuietly(client, new File(path)); - if (callback != null) { - callback.onScanCompleted(path, uri); - } + runCallBack(context, callback, path, uri); } } }); @@ -206,6 +202,23 @@ public class MediaScannerConnection implements ServiceConnection { return uri; } + private static void runCallBack(Context context, OnScanCompletedListener callback, + String path, Uri uri) { + if (callback != null) { + // Ignore exceptions from callback to avoid calling app from crashing. + // Don't ignore exceptions for apps targeting 'R' or higher. + try { + callback.onScanCompleted(path, uri); + } catch (Throwable e) { + if (context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R) { + throw e; + } else { + Log.w(TAG, "Ignoring exception from callback for backward compatibility", e); + } + } + } + } + @Deprecated static class ClientProxy implements MediaScannerConnectionClient { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 9b183a3e0e92..44142e324962 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -532,6 +532,8 @@ public class Tuner implements AutoCloseable { Filter filter = nativeOpenFilter( mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize); if (filter != null) { + filter.setMainType(mainType); + filter.setSubtype(subType); filter.setCallback(cb); if (mHandler == null) { mHandler = createEventHandler(); diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java index 06de6e8a83c7..a98183bbf1a3 100644 --- a/media/java/android/media/tv/tuner/filter/Filter.java +++ b/media/java/android/media/tv/tuner/filter/Filter.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.TunerConstants.Result; +import android.media.tv.tuner.TunerUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -177,6 +178,8 @@ public class Filter implements AutoCloseable { private long mNativeContext; private FilterCallback mCallback; private final int mId; + private int mMainType; + private int mSubtype; private native int nativeConfigureFilter( int type, int subType, FilterConfiguration settings); @@ -197,6 +200,15 @@ public class Filter implements AutoCloseable { } /** @hide */ + public void setMainType(@Type int mainType) { + mMainType = mainType; + } + /** @hide */ + public void setSubtype(@Subtype int subtype) { + mSubtype = subtype; + } + + /** @hide */ public void setCallback(FilterCallback cb) { mCallback = cb; } @@ -213,10 +225,13 @@ public class Filter implements AutoCloseable { */ @Result public int configure(@NonNull FilterConfiguration config) { - int subType = -1; + // TODO: validate main type, subtype, config, settings + int subType; Settings s = config.getSettings(); if (s != null) { subType = s.getType(); + } else { + subType = TunerUtils.getFilterSubtype(mMainType, mSubtype); } return nativeConfigureFilter(config.getType(), subType, config); } diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java index bf5aaeda4742..a8dbfa5b11ec 100644 --- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java @@ -160,6 +160,12 @@ public class IpFilterConfiguration extends FilterConfiguration { */ @NonNull public IpFilterConfiguration build() { + int ipAddrLength = mSrcIpAddress.length; + if (ipAddrLength != mDstIpAddress.length || (ipAddrLength != 4 && ipAddrLength != 16)) { + throw new IllegalArgumentException( + "The lengths of src and dst IP address must be 4 or 16 and must be the same." + + "srcLength=" + ipAddrLength + ", dstLength=" + mDstIpAddress.length); + } return new IpFilterConfiguration( mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough); } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 08c3f988e85e..ac59003de5e6 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -27,16 +27,36 @@ #pragma GCC diagnostic ignored "-Wunused-function" using ::android::hardware::Void; +using ::android::hardware::hidl_bitfield; using ::android::hardware::hidl_vec; using ::android::hardware::tv::tuner::V1_0::DataFormat; +using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterSettings; +using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterType; +using ::android::hardware::tv::tuner::V1_0::DemuxAlpLengthType; +using ::android::hardware::tv::tuner::V1_0::DemuxFilterAvSettings; +using ::android::hardware::tv::tuner::V1_0::DemuxFilterDownloadSettings; using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType; using ::android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings; +using ::android::hardware::tv::tuner::V1_0::DemuxFilterRecordSettings; +using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionBits; +using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings; using ::android::hardware::tv::tuner::V1_0::DemuxFilterSettings; +using ::android::hardware::tv::tuner::V1_0::DemuxIpAddress; +using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterSettings; +using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterType; +using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterSettings; +using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType; using ::android::hardware::tv::tuner::V1_0::DemuxMmtpPid; using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits; +using ::android::hardware::tv::tuner::V1_0::DemuxRecordScIndexType; +using ::android::hardware::tv::tuner::V1_0::DemuxScHevcIndex; +using ::android::hardware::tv::tuner::V1_0::DemuxScIndex; +using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterSettings; +using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterType; using ::android::hardware::tv::tuner::V1_0::DemuxTpid; using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings; using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType; +using ::android::hardware::tv::tuner::V1_0::DemuxTsIndex; using ::android::hardware::tv::tuner::V1_0::DvrSettings; using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSettings; using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSifStandard; @@ -111,6 +131,9 @@ struct fields_t { static fields_t gFields; +static int IP_V4_LENGTH = 4; +static int IP_V6_LENGTH = 16; + namespace android { /////////////// LnbCallback /////////////////////// LnbCallback::LnbCallback(jweak tunerObj, LnbId id) : mObject(tunerObj), mId(id) {} @@ -1367,38 +1390,352 @@ static jobject android_media_tv_Tuner_open_time_filter(JNIEnv, jobject) { return NULL; } -static DemuxFilterSettings getFilterSettings( - JNIEnv *env, int type, int subtype, jobject filterSettingsObj) { +static DemuxFilterSectionBits getFilterSectionBits(JNIEnv *env, const jobject& settings) { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithSectionBits"); + jbyteArray jfilterBytes = static_cast<jbyteArray>( + env->GetObjectField(settings, env->GetFieldID(clazz, "mFilter", "[B"))); + jsize size = env->GetArrayLength(jfilterBytes); + std::vector<uint8_t> filterBytes(size); + env->GetByteArrayRegion( + jfilterBytes, 0, size, reinterpret_cast<jbyte*>(&filterBytes[0])); + + jbyteArray jmask = static_cast<jbyteArray>( + env->GetObjectField(settings, env->GetFieldID(clazz, "mMask", "[B"))); + size = env->GetArrayLength(jmask); + std::vector<uint8_t> mask(size); + env->GetByteArrayRegion(jmask, 0, size, reinterpret_cast<jbyte*>(&mask[0])); + + jbyteArray jmode = static_cast<jbyteArray>( + env->GetObjectField(settings, env->GetFieldID(clazz, "mMode", "[B"))); + size = env->GetArrayLength(jmode); + std::vector<uint8_t> mode(size); + env->GetByteArrayRegion(jmode, 0, size, reinterpret_cast<jbyte*>(&mode[0])); + + DemuxFilterSectionBits filterSectionBits { + .filter = filterBytes, + .mask = mask, + .mode = mode, + }; + return filterSectionBits; +} + +static DemuxFilterSectionSettings::Condition::TableInfo getFilterTableInfo( + JNIEnv *env, const jobject& settings) { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithTableInfo"); + uint16_t tableId = static_cast<uint16_t>( + env->GetIntField(settings, env->GetFieldID(clazz, "mTableId", "I"))); + uint16_t version = static_cast<uint16_t>( + env->GetIntField(settings, env->GetFieldID(clazz, "mVersion", "I"))); + DemuxFilterSectionSettings::Condition::TableInfo tableInfo { + .tableId = tableId, + .version = version, + }; + return tableInfo; +} + +static DemuxFilterSectionSettings getFilterSectionSettings(JNIEnv *env, const jobject& settings) { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/SectionSettings"); + bool isCheckCrc = static_cast<bool>( + env->GetBooleanField(settings, env->GetFieldID(clazz, "mCrcEnabled", "Z"))); + bool isRepeat = static_cast<bool>( + env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRepeat", "Z"))); + bool isRaw = static_cast<bool>( + env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRaw", "Z"))); + + DemuxFilterSectionSettings filterSectionSettings { + .isCheckCrc = isCheckCrc, + .isRepeat = isRepeat, + .isRaw = isRaw, + }; + if (env->IsInstanceOf( + settings, + env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithSectionBits"))) { + filterSectionSettings.condition.sectionBits(getFilterSectionBits(env, settings)); + } else if (env->IsInstanceOf( + settings, + env->FindClass("android/media/tv/tuner/filter/SectionSettingsWithTableInfo"))) { + filterSectionSettings.condition.tableInfo(getFilterTableInfo(env, settings)); + } + return filterSectionSettings; +} + +static DemuxFilterAvSettings getFilterAvSettings(JNIEnv *env, const jobject& settings) { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/AvSettings"); + bool isPassthrough = static_cast<bool>( + env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsPassthrough", "Z"))); + DemuxFilterAvSettings filterAvSettings { + .isPassthrough = isPassthrough, + }; + return filterAvSettings; +} + +static DemuxFilterPesDataSettings getFilterPesDataSettings(JNIEnv *env, const jobject& settings) { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/PesSettings"); + uint16_t streamId = static_cast<uint16_t>( + env->GetIntField(settings, env->GetFieldID(clazz, "mStreamId", "I"))); + bool isRaw = static_cast<bool>( + env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsRaw", "Z"))); + DemuxFilterPesDataSettings filterPesDataSettings { + .streamId = streamId, + .isRaw = isRaw, + }; + return filterPesDataSettings; +} + +static DemuxFilterRecordSettings getFilterRecordSettings(JNIEnv *env, const jobject& settings) { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/RecordSettings"); + hidl_bitfield<DemuxTsIndex> tsIndexMask = static_cast<hidl_bitfield<DemuxTsIndex>>( + env->GetIntField(settings, env->GetFieldID(clazz, "mTsIndexMask", "I"))); + DemuxRecordScIndexType scIndexType = static_cast<DemuxRecordScIndexType>( + env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexType", "I"))); + jint scIndexMask = env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexMask", "I")); + + DemuxFilterRecordSettings filterRecordSettings { + .tsIndexMask = tsIndexMask, + .scIndexType = scIndexType, + }; + if (scIndexType == DemuxRecordScIndexType::SC) { + filterRecordSettings.scIndexMask.sc(static_cast<hidl_bitfield<DemuxScIndex>>(scIndexMask)); + } else if (scIndexType == DemuxRecordScIndexType::SC_HEVC) { + filterRecordSettings.scIndexMask.scHevc( + static_cast<hidl_bitfield<DemuxScHevcIndex>>(scIndexMask)); + } + return filterRecordSettings; +} + +static DemuxFilterDownloadSettings getFilterDownloadSettings(JNIEnv *env, const jobject& settings) { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/DownloadSettings"); + uint32_t downloadId = static_cast<uint32_t>( + env->GetIntField(settings, env->GetFieldID(clazz, "mDownloadId", "I"))); + + DemuxFilterDownloadSettings filterDownloadSettings { + .downloadId = downloadId, + }; + return filterDownloadSettings; +} + +static DemuxIpAddress getDemuxIpAddress(JNIEnv *env, const jobject& config) { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/IpFilterConfiguration"); + + jbyteArray jsrcIpAddress = static_cast<jbyteArray>( + env->GetObjectField(config, env->GetFieldID(clazz, "mSrcIpAddress", "[B"))); + jsize srcSize = env->GetArrayLength(jsrcIpAddress); + jbyteArray jdstIpAddress = static_cast<jbyteArray>( + env->GetObjectField(config, env->GetFieldID(clazz, "mDstIpAddress", "[B"))); + jsize dstSize = env->GetArrayLength(jdstIpAddress); + + DemuxIpAddress res; + + if (srcSize != dstSize) { + // should never happen. Validated on Java size. + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "IP address lengths don't match. srcLength=%d, dstLength=%d", srcSize, dstSize); + return res; + } + + if (srcSize == IP_V4_LENGTH) { + uint8_t srcAddr[IP_V4_LENGTH]; + uint8_t dstAddr[IP_V4_LENGTH]; + env->GetByteArrayRegion( + jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte*>(srcAddr)); + env->GetByteArrayRegion( + jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte*>(dstAddr)); + res.srcIpAddress.v4(srcAddr); + res.dstIpAddress.v4(dstAddr); + } else if (srcSize == IP_V6_LENGTH) { + uint8_t srcAddr[IP_V6_LENGTH]; + uint8_t dstAddr[IP_V6_LENGTH]; + env->GetByteArrayRegion( + jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte*>(srcAddr)); + env->GetByteArrayRegion( + jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte*>(dstAddr)); + res.srcIpAddress.v6(srcAddr); + res.dstIpAddress.v6(dstAddr); + } else { + // should never happen. Validated on Java size. + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Invalid IP address length %d", srcSize); + return res; + } + + uint16_t srcPort = static_cast<uint16_t>( + env->GetIntField(config, env->GetFieldID(clazz, "mSrcPort", "I"))); + uint16_t dstPort = static_cast<uint16_t>( + env->GetIntField(config, env->GetFieldID(clazz, "mDstPort", "I"))); + + res.srcPort = srcPort; + res.dstPort = dstPort; + + return res; +} + +static DemuxFilterSettings getFilterConfiguration( + JNIEnv *env, int type, int subtype, jobject filterConfigObj) { DemuxFilterSettings filterSettings; - // TODO: more setting types jobject settingsObj = env->GetObjectField( - filterSettingsObj, + filterConfigObj, env->GetFieldID( env->FindClass("android/media/tv/tuner/filter/FilterConfiguration"), "mSettings", "Landroid/media/tv/tuner/filter/Settings;")); - if (type == (int)DemuxFilterMainType::TS) { - // DemuxTsFilterSettings - jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration"); - int tpid = env->GetIntField(filterSettingsObj, env->GetFieldID(clazz, "mTpid", "I")); - if (subtype == (int)DemuxTsFilterType::PES) { - // DemuxFilterPesDataSettings - jclass settingClazz = - env->FindClass("android/media/tv/tuner/filter/PesSettings"); - int streamId = env->GetIntField( - settingsObj, env->GetFieldID(settingClazz, "mStreamId", "I")); - bool isRaw = (bool)env->GetBooleanField( - settingsObj, env->GetFieldID(settingClazz, "mIsRaw", "Z")); - DemuxFilterPesDataSettings filterPesDataSettings { - .streamId = static_cast<uint16_t>(streamId), - .isRaw = isRaw, - }; + DemuxFilterMainType mainType = static_cast<DemuxFilterMainType>(type); + switch (mainType) { + case DemuxFilterMainType::TS: { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration"); + uint16_t tpid = static_cast<uint16_t>( + env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mTpid", "I"))); DemuxTsFilterSettings tsFilterSettings { - .tpid = static_cast<uint16_t>(tpid), + .tpid = tpid, }; - tsFilterSettings.filterSettings.pesData(filterPesDataSettings); + + DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype); + switch (tsType) { + case DemuxTsFilterType::SECTION: + tsFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + case DemuxTsFilterType::AUDIO: + case DemuxTsFilterType::VIDEO: + tsFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); + break; + case DemuxTsFilterType::PES: + tsFilterSettings.filterSettings.pesData( + getFilterPesDataSettings(env, settingsObj)); + break; + case DemuxTsFilterType::RECORD: + tsFilterSettings.filterSettings.record( + getFilterRecordSettings(env, settingsObj)); + break; + default: + break; + } filterSettings.ts(tsFilterSettings); + break; + } + case DemuxFilterMainType::MMTP: { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/MmtpFilterConfiguration"); + uint16_t mmtpPid = static_cast<uint16_t>( + env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mMmtpPid", "I"))); + DemuxMmtpFilterSettings mmtpFilterSettings { + .mmtpPid = mmtpPid, + }; + DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype); + switch (mmtpType) { + case DemuxMmtpFilterType::SECTION: + mmtpFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::AUDIO: + case DemuxMmtpFilterType::VIDEO: + mmtpFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::PES: + mmtpFilterSettings.filterSettings.pesData( + getFilterPesDataSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::RECORD: + mmtpFilterSettings.filterSettings.record( + getFilterRecordSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::DOWNLOAD: + mmtpFilterSettings.filterSettings.download( + getFilterDownloadSettings(env, settingsObj)); + break; + default: + break; + } + filterSettings.mmtp(mmtpFilterSettings); + break; + } + case DemuxFilterMainType::IP: { + DemuxIpAddress ipAddr = getDemuxIpAddress(env, filterConfigObj); + + DemuxIpFilterSettings ipFilterSettings { + .ipAddr = ipAddr, + }; + DemuxIpFilterType ipType = static_cast<DemuxIpFilterType>(subtype); + switch (ipType) { + case DemuxIpFilterType::SECTION: { + ipFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + } + case DemuxIpFilterType::IP: { + jclass clazz = env->FindClass( + "android/media/tv/tuner/filter/IpFilterConfiguration"); + bool bPassthrough = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID( + clazz, "mPassthrough", "Z"))); + ipFilterSettings.filterSettings.bPassthrough(bPassthrough); + break; + } + default: { + break; + } + } + filterSettings.ip(ipFilterSettings); + break; + } + case DemuxFilterMainType::TLV: { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/TlvFilterConfiguration"); + uint8_t packetType = static_cast<uint8_t>( + env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mPacketType", "I"))); + bool isCompressedIpPacket = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID(clazz, "mIsCompressedIpPacket", "Z"))); + + DemuxTlvFilterSettings tlvFilterSettings { + .packetType = packetType, + .isCompressedIpPacket = isCompressedIpPacket, + }; + DemuxTlvFilterType tlvType = static_cast<DemuxTlvFilterType>(subtype); + switch (tlvType) { + case DemuxTlvFilterType::SECTION: { + tlvFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + } + case DemuxTlvFilterType::TLV: { + bool bPassthrough = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID( + clazz, "mPassthrough", "Z"))); + tlvFilterSettings.filterSettings.bPassthrough(bPassthrough); + break; + } + default: { + break; + } + } + filterSettings.tlv(tlvFilterSettings); + break; + } + case DemuxFilterMainType::ALP: { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/AlpFilterConfiguration"); + uint8_t packetType = static_cast<uint8_t>( + env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mPacketType", "I"))); + DemuxAlpLengthType lengthType = static_cast<DemuxAlpLengthType>( + env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mLengthType", "I"))); + DemuxAlpFilterSettings alpFilterSettings { + .packetType = packetType, + .lengthType = lengthType, + }; + DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype); + switch (alpType) { + case DemuxAlpFilterType::SECTION: + alpFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + default: + break; + } + filterSettings.alp(alpFilterSettings); + break; + } + default: { + break; } } return filterSettings; @@ -1439,7 +1776,7 @@ static int android_media_tv_Tuner_configure_filter( ALOGD("Failed to configure filter: filter not found"); return (int)Result::INVALID_STATE; } - DemuxFilterSettings filterSettings = getFilterSettings(env, type, subtype, settings); + DemuxFilterSettings filterSettings = getFilterConfiguration(env, type, subtype, settings); Result res = iFilterSp->configure(filterSettings); MQDescriptorSync<uint8_t> filterMQDesc; if (res == Result::SUCCESS && filterSp->mFilterMQ == NULL) { diff --git a/media/tests/TunerTest/Android.bp b/media/tests/TunerTest/Android.bp new file mode 100644 index 000000000000..cef879112225 --- /dev/null +++ b/media/tests/TunerTest/Android.bp @@ -0,0 +1,18 @@ +android_test { + name: "mediatunertest", + + srcs: ["**/*.java"], + + libs: [ + "android.test.runner", + "android.test.base", + ], + + static_libs: [ + "android-support-test", + "testng" + ], + + platform_apis: true, + certificate: "platform", +} diff --git a/media/tests/TunerTest/AndroidManifest.xml b/media/tests/TunerTest/AndroidManifest.xml new file mode 100644 index 000000000000..17e9f197b68c --- /dev/null +++ b/media/tests/TunerTest/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.mediatunertest"> + + <uses-permission android:name="android.permission.ACCESS_TV_TUNER" /> + + <application android:label="@string/app_name"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.mediatunertest" + android:label="Media Tuner Tests"/> +</manifest> diff --git a/media/tests/TunerTest/AndroidTest.xml b/media/tests/TunerTest/AndroidTest.xml new file mode 100644 index 000000000000..d9c31f45532f --- /dev/null +++ b/media/tests/TunerTest/AndroidTest.xml @@ -0,0 +1,17 @@ +<configuration description="Runs Media Tuner tests."> + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="MediaTunerTest"/> + + <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="mediatunertest.apk"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.mediatunertest"/> + <option name="hidden-api-checks" value="false"/> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/> + </test> +</configuration> diff --git a/media/tests/TunerTest/res/values/strings.xml b/media/tests/TunerTest/res/values/strings.xml new file mode 100644 index 000000000000..b313944ed256 --- /dev/null +++ b/media/tests/TunerTest/res/values/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- name of the app [CHAR LIMIT=25]--> + <string name="app_name">MediaTunerTest</string> +</resources>
\ No newline at end of file diff --git a/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java b/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java new file mode 100644 index 000000000000..cbfbf77d11f5 --- /dev/null +++ b/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mediatunertest; + +import static org.junit.Assert.assertNotNull; + +import android.content.Context; +import android.media.tv.tuner.Descrambler; +import android.media.tv.tuner.Tuner; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TunerTest { + private static final String TAG = "MediaTunerTest"; + + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + } + + @After + public void tearDown() { + } + + @Test + public void testTunerConstructor() throws Exception { + Tuner tuner = new Tuner(mContext, "123", 1, null); + assertNotNull(tuner); + } + + @Test + public void testOpenDescrambler() throws Exception { + Tuner tuner = new Tuner(mContext, "123", 1, null); + Descrambler descrambler = tuner.openDescrambler(); + assertNotNull(descrambler); + } +} diff --git a/packages/PrintSpooler/res/values-ja/donottranslate.xml b/packages/PrintSpooler/res/values-ja/donottranslate.xml index d334ddd312cf..6a0f768336ae 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/Android.bp b/packages/SettingsLib/Android.bp index 62124934f416..3f42ad40eb8e 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -27,6 +27,7 @@ android_library { "SettingsLibRadioButtonPreference", "WifiTrackerLib", "SettingsLibDisplayDensityUtils", + "SettingsLibSchedulesProvider", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp new file mode 100644 index 000000000000..ef592527ba92 --- /dev/null +++ b/packages/SettingsLib/SchedulesProvider/Android.bp @@ -0,0 +1,12 @@ +android_library { + name: "SettingsLibSchedulesProvider", + + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.annotation_annotation", + ], + + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml b/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml new file mode 100644 index 000000000000..1b0e4bfc1e1d --- /dev/null +++ b/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.schedulesprovider"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java new file mode 100644 index 000000000000..7d2b8e2878d6 --- /dev/null +++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java @@ -0,0 +1,167 @@ +/* + * 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.settingslib.schedulesprovider; + +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +/** + * This is a schedule data item. It contains the schedule title text, the summary text which + * displays on the summary of the Settings preference and an {@link Intent}. Intent is able to + * launch the editing page of the schedule data when user clicks this item (preference). + */ +public class ScheduleInfo implements Parcelable { + private static final String TAG = "ScheduleInfo"; + private final String mTitle; + private final String mSummary; + private final Intent mIntent; + + public ScheduleInfo(Builder builder) { + mTitle = builder.mTitle; + mSummary = builder.mSummary; + mIntent = builder.mIntent; + } + + protected ScheduleInfo(Parcel in) { + mTitle = in.readString(); + mSummary = in.readString(); + mIntent = in.readParcelable(Intent.class.getClassLoader()); + } + + /** + * Returns the title text. + * + * @return The title. + */ + public String getTitle() { + return mTitle; + } + + /** + * Returns the summary text. + * + * @return The summary. + */ + public String getSummary() { + return mSummary; + } + + /** + * Returns an {@link Intent}. + */ + public Intent getIntent() { + return mIntent; + } + + /** + * Verify the member variables are valid. + * + * @return {@code true} if all member variables are valid. + */ + public boolean isValid() { + return !TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mSummary) && (mIntent != null); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mTitle); + dest.writeString(mSummary); + dest.writeParcelable(mIntent, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<ScheduleInfo> CREATOR = new Creator<ScheduleInfo>() { + @Override + public ScheduleInfo createFromParcel(Parcel in) { + return new ScheduleInfo(in); + } + + @Override + public ScheduleInfo[] newArray(int size) { + return new ScheduleInfo[size]; + } + }; + + @NonNull + @Override + public String toString() { + return "title : " + mTitle + " summary : " + mSummary + (mIntent == null + ? " and intent is null." : "."); + } + + /** + * A simple builder for {@link ScheduleInfo}. + */ + public static class Builder { + @NonNull + private String mTitle; + @NonNull + private String mSummary; + @NonNull + private Intent mIntent; + + /** + * Sets the title. + * + * @param title The title of the preference item. + * @return This instance. + */ + public Builder setTitle(@NonNull String title) { + mTitle = title; + return this; + } + + /** + * Sets the summary. + * + * @param summary The summary of the preference summary. + * @return This instance. + */ + public Builder setSummary(@NonNull String summary) { + mSummary = summary; + return this; + } + + /** + * Sets the {@link Intent}. + * + * @param intent The action when user clicks the preference. + * @return This instance. + */ + public Builder setIntent(@NonNull Intent intent) { + mIntent = intent; + return this; + } + + /** + * Creates an instance of {@link ScheduleInfo}. + * + * @return The instance of {@link ScheduleInfo}. + */ + public ScheduleInfo build() { + return new ScheduleInfo(this); + } + } +} diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java new file mode 100644 index 000000000000..a423e475d357 --- /dev/null +++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java @@ -0,0 +1,133 @@ +/* + * 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.settingslib.schedulesprovider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.SystemProperties; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * This provider is a bridge for client apps to provide the schedule data. + * Client provider needs to implement their {@link #getScheduleInfoList()} and returns a list of + * {@link ScheduleInfo}. + */ +public abstract class SchedulesProvider extends ContentProvider { + public static final String METHOD_GENERATE_SCHEDULE_INFO_LIST = "generateScheduleInfoList"; + public static final String BUNDLE_SCHEDULE_INFO_LIST = "scheduleInfoList"; + private static final String TAG = "SchedulesProvider"; + + @Override + public boolean onCreate() { + return true; + } + + @Override + public final Cursor query( + Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + throw new UnsupportedOperationException("Query operation is not supported currently."); + } + + @Override + public final String getType(Uri uri) { + throw new UnsupportedOperationException("GetType operation is not supported currently."); + } + + @Override + public final Uri insert(Uri uri, ContentValues values) { + throw new UnsupportedOperationException("Insert operation is not supported currently."); + } + + @Override + public final int delete(Uri uri, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException("Delete operation not supported currently."); + } + + @Override + public final int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + throw new UnsupportedOperationException("Update operation is not supported currently."); + } + + /** + * Return the list of the schedule information. + * + * @return a list of the {@link ScheduleInfo}. + */ + public abstract ArrayList<ScheduleInfo> getScheduleInfoList(); + + /** + * Returns a bundle which contains a list of {@link ScheduleInfo} and data types: + * scheduleInfoList : ArrayList<ScheduleInfo> + */ + @Override + public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) { + final Bundle bundle = new Bundle(); + if (METHOD_GENERATE_SCHEDULE_INFO_LIST.equals(method)) { + final ArrayList<ScheduleInfo> scheduleInfoList = filterInvalidData( + getScheduleInfoList()); + if (scheduleInfoList != null) { + bundle.putParcelableArrayList(BUNDLE_SCHEDULE_INFO_LIST, scheduleInfoList); + } + } + return bundle; + } + + /** + * To filter the invalid schedule info. + * + * @param scheduleInfoList The list of the {@link ScheduleInfo}. + * @return The valid list of the {@link ScheduleInfo}. + */ + private ArrayList<ScheduleInfo> filterInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) { + if (scheduleInfoList == null) { + Log.d(TAG, "package : " + getContext().getPackageName() + " has no scheduling data."); + return null; + } + // Dump invalid data in debug mode. + if (SystemProperties.getInt("ro.debuggable", 0) == 1) { + new Thread(() -> { + dumpInvalidData(scheduleInfoList); + }).start(); + } + final List<ScheduleInfo> filteredList = scheduleInfoList + .stream() + .filter(scheduleInfo -> scheduleInfo.isValid()) + .collect(Collectors.toList()); + + return new ArrayList<>(filteredList); + } + + private void dumpInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) { + Log.d(TAG, "package : " + getContext().getPackageName() + + " provided some scheduling data are invalid."); + scheduleInfoList + .stream() + .filter(scheduleInfo -> !scheduleInfo.isValid()) + .forEach(scheduleInfo -> Log.d(TAG, scheduleInfo.toString())); + } +} diff --git a/packages/SettingsProvider/res/values/strings.xml b/packages/SettingsProvider/res/values/strings.xml index 378772750194..76bea3160afe 100644 --- a/packages/SettingsProvider/res/values/strings.xml +++ b/packages/SettingsProvider/res/values/strings.xml @@ -23,15 +23,10 @@ <!-- A notification is shown when the user's softap config has been changed due to underlying hardware restrictions. This is the notifications's title. [CHAR_LIMIT=NONE] --> - <string name="wifi_softap_config_change">Changes to your hotspot settings</string> + <string name="wifi_softap_config_change">Hotspot settings have changed</string> <!-- A notification is shown when the user's softap config has been changed due to underlying hardware restrictions. This is the notification's summary message. [CHAR_LIMIT=NONE] --> - <string name="wifi_softap_config_change_summary">Your hotspot band has changed.</string> - - <!-- A notification is shown when the user's softap config has been changed due to underlying - hardware restrictions. This is the notification's full message. - [CHAR_LIMIT=NONE] --> - <string name="wifi_softap_config_change_detailed">This device doesn\u2019t support your preference for 5GHz only. Instead, this device will use the 5GHz band when available.</string> + <string name="wifi_softap_config_change_summary">Tap to see details</string> </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java index 1ee5f9093421..ca841a5cdcd6 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java @@ -57,7 +57,6 @@ public class WifiSoftApConfigChangedNotifier { Resources resources = context.getResources(); CharSequence title = resources.getText(R.string.wifi_softap_config_change); CharSequence contentSummary = resources.getText(R.string.wifi_softap_config_change_summary); - CharSequence content = resources.getText(R.string.wifi_softap_config_change_detailed); int color = resources.getColor( android.R.color.system_notification_accent_color, context.getTheme()); @@ -73,7 +72,6 @@ public class WifiSoftApConfigChangedNotifier { .setLocalOnly(true) .setColor(color) .setStyle(new Notification.BigTextStyle() - .bigText(content) .setBigContentTitle(title) .setSummaryText(contentSummary)) .setAutoCancel(true) diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index cc2c92b4c7eb..d821050ac5b4 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -253,7 +253,8 @@ <!-- Permission required for CTS test - ShortcutManagerUsageTest --> <uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/> - <!-- Permission required for CTS test - UsageStatsTest --> + <!-- Permissions required for CTS test - UsageStatsTest --> + <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS"/> <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"/> <!-- Permissions required to test ambient display. --> diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml index 95f205a1be34..481c4dbe3bf1 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/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 6dd89d88d252..ef9e705d0a81 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> diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java index 0f7f1bebae57..023b74b9c07e 100644 --- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java +++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java @@ -19,7 +19,6 @@ import static android.widget.Toast.Duration; import android.annotation.StringRes; import android.content.Context; -import android.view.WindowManager; import android.widget.Toast; public class SysUIToast { @@ -29,10 +28,7 @@ public class SysUIToast { } 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/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java index 68b05e358786..9de10406a822 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java @@ -16,6 +16,7 @@ package com.android.systemui.biometrics; +import android.annotation.NonNull; import android.content.Context; import android.graphics.drawable.Drawable; import android.hardware.biometrics.BiometricPrompt; @@ -33,6 +34,8 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -126,18 +129,18 @@ public abstract class AuthCredentialView extends LinearLayout { mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS); } - private void setTextOrHide(TextView view, String string) { - if (TextUtils.isEmpty(string)) { + private void setTextOrHide(TextView view, CharSequence text) { + if (TextUtils.isEmpty(text)) { view.setVisibility(View.GONE); } else { - view.setText(string); + view.setText(text); } Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this); } - private void setText(TextView view, String string) { - view.setText(string); + private void setText(TextView view, CharSequence text) { + view.setText(text); } void setEffectiveUserId(int effectiveUserId) { @@ -173,11 +176,9 @@ public abstract class AuthCredentialView extends LinearLayout { protected void onAttachedToWindow() { super.onAttachedToWindow(); - setText(mTitleView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_TITLE)); - setTextOrHide(mSubtitleView, - mBiometricPromptBundle.getString(BiometricPrompt.KEY_SUBTITLE)); - setTextOrHide(mDescriptionView, - mBiometricPromptBundle.getString(BiometricPrompt.KEY_DESCRIPTION)); + setText(mTitleView, getTitle(mBiometricPromptBundle)); + setTextOrHide(mSubtitleView, getSubtitle(mBiometricPromptBundle)); + setTextOrHide(mDescriptionView, getDescription(mBiometricPromptBundle)); final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId); final Drawable image; @@ -279,4 +280,28 @@ public abstract class AuthCredentialView extends LinearLayout { } } } + + @Nullable + private static CharSequence getTitle(@NonNull Bundle bundle) { + final CharSequence credentialTitle = + bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE); + return credentialTitle != null ? credentialTitle + : bundle.getCharSequence(BiometricPrompt.KEY_TITLE); + } + + @Nullable + private static CharSequence getSubtitle(@NonNull Bundle bundle) { + final CharSequence credentialSubtitle = + bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE); + return credentialSubtitle != null ? credentialSubtitle + : bundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); + } + + @Nullable + private static CharSequence getDescription(@NonNull Bundle bundle) { + final CharSequence credentialDescription = + bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION); + return credentialDescription != null ? credentialDescription + : bundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 45705b76f09c..1e39954ed414 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 @@ class Bubble { 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 05838abe184a..762e5f21cd63 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } 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 673121f92716..8a5aad875979 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 @@ public class BubbleData { 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 @@ public class BubbleData { 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 @@ public class BubbleData { 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 0d5261dcb7f3..fe191f40d31f 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 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList 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 2d55a1ddf654..f3cfa834dfc1 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.graphics.Color; 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 @@ import javax.inject.Inject; 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 @@ public class BubbleOverflowActivity extends Activity { 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 @@ public class BubbleOverflowActivity extends Activity { mBubbleController::promoteBubbleFromOverflow); mRecyclerView.setAdapter(mAdapter); - updateData(mBubbleController.getOverflowBubbles()); + onDataChanged(mBubbleController.getOverflowBubbles()); mBubbleController.setOverflowCallback(() -> { - updateData(mBubbleController.getOverflowBubbles()); + onDataChanged(mBubbleController.getOverflowBubbles()); }); } @@ -87,7 +92,7 @@ public class BubbleOverflowActivity extends Activity { 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 @@ public class BubbleOverflowActivity extends Activity { } 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 bce172b89187..cff78cfeaae0 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 @@ public class BubbleStackView extends FrameLayout { 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 @@ public class BubbleStackView extends FrameLayout { 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 class BubbleStackView extends FrameLayout { */ 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 @@ public class BubbleStackView extends FrameLayout { 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/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index ab8de263765e..5c1d332df7a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -105,8 +105,8 @@ class QSLogger @Inject constructor( fun logTileUpdated(tileSpec: String, state: QSTile.State) { log(VERBOSE, { str1 = tileSpec - str2 = state.label.toString() - str3 = state.icon.toString() + str2 = state.label?.toString() + str3 = state.icon?.toString() int1 = state.state if (state is QSTile.SignalState) { bool1 = true diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 573ea4dd85de..9f64b397e9d9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -352,6 +352,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis try { Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, + AccessibilityManager.ACCESSIBILITY_BUTTON); mContext.startActivityAsUser(intent, UserHandle.CURRENT); } finally { Binder.restoreCallingIdentity(token); 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 ec3285f2b241..91d2de760696 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 static java.util.Objects.requireNonNull; 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 @@ public class NotifCollection implements Dumpable { } /** - * 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; - } + requireNonNull(stats); + if (entry != mNotificationSet.get(entry.getKey())) { + throw new IllegalStateException("Invalid entry: " + entry.getKey()); + } - updateDismissInterceptors(entry); - if (isDismissIntercepted(entry)) { - mLogger.logNotifDismissedIntercepted(entry.getKey()); - return; + 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. + } + } } - // 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()); + 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))); + } - List<NotificationEntry> canceledEntries = new ArrayList<>(); + /** + * Dismisses all clearable notifications for a given userid on behalf of the user. + */ + public void dismissAllNotifications(@UserIdInt int userId) { + Assert.isMainThread(); + checkForReentrantCall(); - 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. + 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); } + } - // 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); + 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 @@ public class NotifCollection implements Dumpable { for (NotificationEntry canceledEntry : canceledEntries) { tryRemoveNotification(canceledEntry); } - rebuildList(); } private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { @@ -552,7 +617,7 @@ public class NotifCollection implements Dumpable { * * 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 @@ public class NotifCollection implements Dumpable { && 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 @@ public class NotifCollection implements Dumpable { } 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 83f56cc1e83d..1f6413b525cb 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 @@ public class NotifInflaterImpl implements NotifInflater { 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 class NotifInflaterImpl implements NotifInflater { public NotifInflaterImpl( IStatusBarService statusBarService, NotifCollection notifCollection, - NotifInflationErrorManager errorManager) { + NotifInflationErrorManager errorManager, + NotifPipeline notifPipeline) { mStatusBarService = statusBarService; mNotifCollection = notifCollection; mNotifErrorManager = errorManager; + mNotifPipeline = notifPipeline; } /** @@ -110,7 +113,7 @@ public class NotifInflaterImpl implements NotifInflater { 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 d4d2369ba822..44cec966ba55 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 class NotifPipeline implements CommonNotifCollection { 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 116c70c4f1cf..8b2a07d00378 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 @@ public class BubbleCoordinator implements Coordinator { 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 dc7a50d621a1..8675cca3cffe 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 @@ class NotifCollectionLogger @Inject constructor( }) } + 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/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt index 4f27c0f04c3f..5b4a927bb8f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt @@ -26,26 +26,23 @@ import android.content.Context import android.content.DialogInterface import android.graphics.Color import android.graphics.PixelFormat -import android.graphics.drawable.Drawable import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable import android.util.Log import android.view.Gravity import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.Window -import android.view.WindowInsets.Type import android.view.WindowInsets.Type.statusBars import android.view.WindowManager import android.widget.TextView import com.android.internal.annotations.VisibleForTesting - import com.android.systemui.R - import javax.inject.Inject import javax.inject.Singleton -const val TAG = "ChannelDialogController" +private const val TAG = "ChannelDialogController" /** * ChannelEditorDialogController is the controller for the dialog half-shelf @@ -149,9 +146,9 @@ class ChannelEditorDialogController @Inject constructor( val channels = groupList .flatMap { group -> group.channels.asSequence().filterNot { channel -> - channel.isImportanceLockedByOEM - || channel.importance == IMPORTANCE_NONE - || channel.isImportanceLockedByCriticalDeviceFunction + channel.isImportanceLockedByOEM || + channel.importance == IMPORTANCE_NONE || + channel.isImportanceLockedByCriticalDeviceFunction } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java index e2513dac44d8..d744fc398d7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -74,11 +74,15 @@ import javax.inject.Singleton; @Singleton public final class NotifBindPipeline { private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>(); + private final NotifBindPipelineLogger mLogger; private BindStage mStage; @Inject - NotifBindPipeline(CommonNotifCollection collection) { + NotifBindPipeline( + CommonNotifCollection collection, + NotifBindPipelineLogger logger) { collection.addCollectionListener(mCollectionListener); + mLogger = logger; } /** @@ -86,6 +90,8 @@ public final class NotifBindPipeline { */ public void setStage( BindStage stage) { + mLogger.logStageSet(stage.getClass().getName()); + mStage = stage; mStage.setBindRequestListener(this::onBindRequested); } @@ -96,6 +102,8 @@ public final class NotifBindPipeline { public void manageRow( @NonNull NotificationEntry entry, @NonNull ExpandableNotificationRow row) { + mLogger.logManagedRow(entry.getKey()); + final BindEntry bindEntry = getBindEntry(entry); bindEntry.row = row; if (bindEntry.invalidated) { @@ -130,6 +138,8 @@ public final class NotifBindPipeline { * callbacks when the run finishes. If a run is already in progress, it is restarted. */ private void startPipeline(NotificationEntry entry) { + mLogger.logStartPipeline(entry.getKey()); + if (mStage == null) { throw new IllegalStateException("No stage was ever set on the pipeline"); } @@ -147,10 +157,11 @@ public final class NotifBindPipeline { private void onPipelineComplete(NotificationEntry entry) { final BindEntry bindEntry = getBindEntry(entry); + final Set<BindCallback> callbacks = bindEntry.callbacks; - bindEntry.invalidated = false; + mLogger.logFinishedPipeline(entry.getKey(), callbacks.size()); - final Set<BindCallback> callbacks = bindEntry.callbacks; + bindEntry.invalidated = false; for (BindCallback cb : callbacks) { cb.onBindFinished(entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt new file mode 100644 index 000000000000..2717d7ad143b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class NotifBindPipelineLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logStageSet(stageName: String) { + buffer.log(TAG, INFO, { + str1 = stageName + }, { + "Stage set: $str1" + }) + } + + fun logManagedRow(notifKey: String) { + buffer.log(TAG, INFO, { + str1 = notifKey + }, { + "Row set for notif: $str1" + }) + } + + fun logStartPipeline(notifKey: String) { + buffer.log(TAG, INFO, { + str1 = notifKey + }, { + "Start pipeline for notif: $str1" + }) + } + + fun logFinishedPipeline(notifKey: String, numCallbacks: Int) { + buffer.log(TAG, INFO, { + str1 = notifKey + int1 = numCallbacks + }, { + "Finished pipeline for notif $str1 with $int1 callbacks" + }) + } +} + +private const val TAG = "NotifBindPipeline"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java index 5170d0b85b17..88ed0bb37d0d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -157,6 +157,15 @@ public final class RowContentBindParams { return mViewsNeedReinflation; } + @Override + public String toString() { + return String.format("RowContentBindParams[mContentViews=%x mDirtyContentViews=%x " + + "mUseLowPriority=%b mUseChildInGroup=%b mUseIncreasedHeight=%b " + + "mUseIncreasedHeadsUpHeight=%b mViewsNeedReinflation=%b]", + mContentViews, mDirtyContentViews, mUseLowPriority, mUseChildInGroup, + mUseIncreasedHeight, mUseIncreasedHeadsUpHeight, mViewsNeedReinflation); + } + /** * Content views that should be inflated by default for all notifications. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java index f78324596fb4..c632f3eb22a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java @@ -38,13 +38,16 @@ import javax.inject.Singleton; public class RowContentBindStage extends BindStage<RowContentBindParams> { private final NotificationRowContentBinder mBinder; private final NotifInflationErrorManager mNotifInflationErrorManager; + private final RowContentBindStageLogger mLogger; @Inject RowContentBindStage( NotificationRowContentBinder binder, - NotifInflationErrorManager errorManager) { + NotifInflationErrorManager errorManager, + RowContentBindStageLogger logger) { mBinder = binder; mNotifInflationErrorManager = errorManager; + mLogger = logger; } @Override @@ -54,6 +57,8 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> { @NonNull StageCallback callback) { RowContentBindParams params = getStageParams(entry); + mLogger.logStageParams(entry.getKey(), params.toString()); + // Resolve content to bind/unbind. @InflationFlag int inflationFlags = params.getContentViews(); @InflationFlag int invalidatedFlags = params.getDirtyContentViews(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt new file mode 100644 index 000000000000..29cce3375c8a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class RowContentBindStageLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logStageParams(notifKey: String, stageParams: String) { + buffer.log(TAG, INFO, { + str1 = notifKey + str2 = stageParams + }, { + "Invalidated notif $str1 with params: \n$str2" + }) + } +} + +private const val TAG = "RowContentBindStage"
\ No newline at end of file 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 0cc337187f02..b2b46d58713f 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.graphics.ColorUtils; 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.plugins.statusbar.StatusBarStateController.StateList 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.NotificationUtils; 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } }, 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd /* 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/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 3f5215e1a639..fd8c71b48417 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -956,6 +956,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private boolean onAccessibilityLongClick(View v) { Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, + AccessibilityManager.ACCESSIBILITY_BUTTON); v.getContext().startActivityAsUser(intent, UserHandle.CURRENT); return true; } 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 0f3b5db2d281..e1a20b6ac5d3 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.Looper; 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.dagger.qualifiers.UiBackground; 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.NotificationActivityStarter; 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit }); } + // --------------------- 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit @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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit return this; } - - public StatusBarNotificationActivityStarter build() { return new StatusBarNotificationActivityStarter(mContext, mCommandQueue, mAssistManagerLazy, @@ -638,7 +719,10 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 dea8c5d49dfc..edab4a72d8c8 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.TextView; 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 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks { @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/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 96db16adb7dc..12e9d31fdd0c 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.assertFalse; 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.app.Notification; 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 @@ public class NotifCollectionTest extends SysuiTestCase { @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 class NotifCollectionTest extends SysuiTestCase { 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 @@ public class NotifCollectionTest extends SysuiTestCase { } @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 @@ public class NotifCollectionTest extends SysuiTestCase { 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 @@ public class NotifCollectionTest extends SysuiTestCase { 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 @@ public class NotifCollectionTest extends SysuiTestCase { eq(notif.sbn.getKey()), anyInt(), anyInt(), - anyObject()); + eq(stats.notificationVisibility)); } @Test @@ -544,19 +541,16 @@ public class NotifCollectionTest extends SysuiTestCase { 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 @@ public class NotifCollectionTest extends SysuiTestCase { eq(notif.sbn.getKey()), anyInt(), anyInt(), - anyObject()); + eq(stats.notificationVisibility)); } @Test @@ -581,16 +575,12 @@ public class NotifCollectionTest extends SysuiTestCase { 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 @@ public class NotifCollectionTest extends SysuiTestCase { // 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 @@ public class NotifCollectionTest extends SysuiTestCase { @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 @@ public class NotifCollectionTest extends SysuiTestCase { @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 @@ public class NotifCollectionTest extends SysuiTestCase { @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 @@ public class NotifCollectionTest extends SysuiTestCase { @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 @@ public class NotifCollectionTest extends SysuiTestCase { 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/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java index 408bba48d422..6408f7a38133 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java @@ -59,7 +59,7 @@ public class NotifBindPipelineTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); CommonNotifCollection collection = mock(CommonNotifCollection.class); - mBindPipeline = new NotifBindPipeline(collection); + mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class)); mBindPipeline.setStage(mStage); ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index fd5512d62968..7a1bd052a336 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -111,11 +111,13 @@ public class NotificationTestHelper { mock(NotifRemoteViewCache.class), mock(NotificationRemoteInputManager.class)); contentBinder.setInflateSynchronously(true); - mBindStage = new RowContentBindStage(contentBinder, mock(NotifInflationErrorManager.class)); + mBindStage = new RowContentBindStage(contentBinder, + mock(NotifInflationErrorManager.class), + mock(RowContentBindStageLogger.class)); CommonNotifCollection collection = mock(CommonNotifCollection.class); - mBindPipeline = new NotifBindPipeline(collection); + mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class)); mBindPipeline.setStage(mBindStage); ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index d9fe6551ba1c..0f2482ce9c4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -60,8 +60,10 @@ public class RowContentBindStageTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mRowContentBindStage = new RowContentBindStage(mBinder, - mock(NotifInflationErrorManager.class)); + mRowContentBindStage = new RowContentBindStage( + mBinder, + mock(NotifInflationErrorManager.class), + mock(RowContentBindStageLogger.class)); mRowContentBindStage.createStageParams(mEntry); } 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 b16e52ce7bd4..9ccee75a3d09 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.NotificationFilter; 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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { 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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { 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/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 50276106f8d4..1e4df272b02b 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.bubbles.BubbleController; 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.ActivityLaunchAnimator; 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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { 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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { 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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { 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 000000000000..d58f2c9c1791 --- /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 ab21a76229ba..351e92c630d1 100644 --- a/packages/services/PacProcessor/jni/Android.bp +++ b/packages/services/PacProcessor/jni/Android.bp @@ -37,7 +37,8 @@ cc_library_shared { "-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 89c466500fef..4dd00f1ff01b 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 @@ package com.android.pacprocessor; import android.util.Log; import android.webkit.PacProcessor; -import com.android.internal.annotations.GuardedBy; - /** * @hide */ @@ -28,10 +26,6 @@ public class PacWebView implements LibpacInterface { 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 @@ public class PacWebView implements LibpacInterface { } @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/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java index b74be7e3f345..a3b5a3e2dcf9 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -17,6 +17,7 @@ package com.android.server.accessibility.gestures; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT; @@ -24,6 +25,7 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT; @@ -31,6 +33,7 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT; @@ -132,6 +135,9 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this)); mMultiFingerGestures.add( + new MultiFingerMultiTapAndHold( + mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, this)); + mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP, this)); // Three-finger taps. mMultiFingerGestures.add( @@ -139,6 +145,9 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this)); mMultiFingerGestures.add( + new MultiFingerMultiTapAndHold( + mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, this)); + mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this)); // Four-finger taps. mMultiFingerGestures.add( @@ -146,6 +155,9 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP, this)); mMultiFingerGestures.add( + new MultiFingerMultiTapAndHold( + mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD, this)); + mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this)); // Two-finger swipes. mMultiFingerGestures.add( diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java index 20def6154977..e5340f10dc4c 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java @@ -42,10 +42,10 @@ class MultiFingerMultiTap extends GestureMatcher { // The acceptable distance the pointer can move and still count as a tap. private int mTouchSlop; // A tap counts when target number of fingers are down and up once. - private int mCompletedTapCount; + protected int mCompletedTapCount; // A flag set to true when target number of fingers have touched down at once before. // Used to indicate what next finger action should be. Down when false and lift when true. - private boolean mIsTargetFingerCountReached = false; + protected boolean mIsTargetFingerCountReached = false; // Store initial down points for slop checking and update when next down if is inside slop. private PointF[] mBases; // The points in bases that already have slop checked when onDown or onPointerDown. @@ -56,7 +56,11 @@ class MultiFingerMultiTap extends GestureMatcher { * @throws IllegalArgumentException if <code>fingers<code/> is less than 2 * or <code>taps<code/> is not positive. */ - MultiFingerMultiTap(Context context, int fingers, int taps, int gestureId, + MultiFingerMultiTap( + Context context, + int fingers, + int taps, + int gestureId, GestureMatcher.StateChangeListener listener) { super(gestureId, new Handler(context.getMainLooper()), listener); Preconditions.checkArgument(fingers >= 2); @@ -117,8 +121,7 @@ class MultiFingerMultiTap extends GestureMatcher { cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags); final PointF nearest = findNearestPoint(rawEvent, mTouchSlop, false); - if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) - && null != nearest) { + if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) && null != nearest) { // Increase current tap count when the user have all fingers lifted // within the tap timeout since the target number of fingers are down. if (mIsTargetFingerCountReached) { @@ -169,8 +172,7 @@ class MultiFingerMultiTap extends GestureMatcher { } else { nearest = findNearestPoint(rawEvent, mDoubleTapSlop, true); } - if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) - && nearest != null) { + if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) && nearest != null) { // The user have all fingers down within the tap timeout since first finger down, // setting the timeout for fingers to be lifted. if (currentFingerCount == mTargetFingerCount) { @@ -227,11 +229,11 @@ class MultiFingerMultiTap extends GestureMatcher { } /** - * Find the nearest location to the given event in the bases. - * If no one found, it could be not inside {@code slop}, filtered or empty bases. - * When {@code filterMatched} is true, if the location of given event matches one of the points - * in {@link #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location - * will be added to {@link #mExcludedPointsForDownSlopChecked}. + * Find the nearest location to the given event in the bases. If no one found, it could be not + * inside {@code slop}, filtered or empty bases. When {@code filterMatched} is true, if the + * location of given event matches one of the points in {@link + * #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location will be + * added to {@link #mExcludedPointsForDownSlopChecked}. * * @param event to find nearest point in bases. * @param slop to check to the given location of the event. diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java new file mode 100644 index 000000000000..7824fd902c9b --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java @@ -0,0 +1,71 @@ +/* + * 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.accessibility.gestures; + +import android.content.Context; +import android.view.MotionEvent; + +/** + * This class matches gestures of the form multi-finger multi-tap and hold. The number of fingers + * and taps for each instance is specified in the constructor. + */ +class MultiFingerMultiTapAndHold extends MultiFingerMultiTap { + + MultiFingerMultiTapAndHold( + Context context, + int fingers, + int taps, + int gestureId, + GestureMatcher.StateChangeListener listener) { + super(context, fingers, taps, gestureId, listener); + } + + @Override + protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + super.onPointerDown(event, rawEvent, policyFlags); + if (mIsTargetFingerCountReached && mCompletedTapCount + 1 == mTargetTapCount) { + completeAfterLongPressTimeout(event, rawEvent, policyFlags); + } + } + + @Override + protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mCompletedTapCount + 1 == mTargetFingerCount) { + // Calling super.onUp would complete the multi-tap version of this. + cancelGesture(event, rawEvent, policyFlags); + } else { + super.onUp(event, rawEvent, policyFlags); + cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags); + } + } + + @Override + public String getGestureName() { + final StringBuilder builder = new StringBuilder(); + builder.append(mTargetFingerCount).append("-Finger "); + if (mTargetTapCount == 1) { + builder.append("Single"); + } else if (mTargetTapCount == 2) { + builder.append("Double"); + } else if (mTargetTapCount == 3) { + builder.append("Triple"); + } else if (mTargetTapCount > 3) { + builder.append(mTargetTapCount); + } + return builder.append(" Tap and hold").toString(); + } +} diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java index 4f49fb7578a1..0b3899d15993 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -16,6 +16,8 @@ package com.android.server.appprediction; +import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppGlobals; @@ -30,12 +32,17 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.provider.DeviceConfig; import android.service.appprediction.AppPredictionService; +import android.service.appprediction.IPredictionService; import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.infra.AbstractRemoteService; +import com.android.server.LocalServices; import com.android.server.infra.AbstractPerUserSystemService; +import com.android.server.people.PeopleServiceInternal; import java.util.function.Consumer; @@ -47,6 +54,8 @@ public class AppPredictionPerUserService extends implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks { private static final String TAG = AppPredictionPerUserService.class.getSimpleName(); + private static final String PREDICT_USING_PEOPLE_SERVICE_PREFIX = + "predict_using_people_service_"; @Nullable @GuardedBy("mLock") @@ -104,14 +113,16 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context, @NonNull AppPredictionSessionId sessionId) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.onCreatePredictionSession(context, sessionId); - - if (!mSessionInfos.containsKey(sessionId)) { - mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context, - this::removeAppPredictionSessionInfo)); - } + if (!mSessionInfos.containsKey(sessionId)) { + mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context, + DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, + PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false), + this::removeAppPredictionSessionInfo)); + } + final boolean serviceExists = resolveService(sessionId, s -> + s.onCreatePredictionSession(context, sessionId)); + if (!serviceExists) { + mSessionInfos.remove(sessionId); } } @@ -121,10 +132,7 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId, @NonNull AppTargetEvent event) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.notifyAppTargetEvent(sessionId, event); - } + resolveService(sessionId, s -> s.notifyAppTargetEvent(sessionId, event)); } /** @@ -133,10 +141,8 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void notifyLaunchLocationShownLocked(@NonNull AppPredictionSessionId sessionId, @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.notifyLaunchLocationShown(sessionId, launchLocation, targetIds); - } + resolveService(sessionId, s -> + s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds)); } /** @@ -145,10 +151,7 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId, @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.sortAppTargets(sessionId, targets, callback); - } + resolveService(sessionId, s -> s.sortAppTargets(sessionId, targets, callback)); } /** @@ -157,14 +160,11 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId, @NonNull IPredictionCallback callback) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.registerPredictionUpdates(sessionId, callback); - - AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); - if (sessionInfo != null) { - sessionInfo.addCallbackLocked(callback); - } + final boolean serviceExists = resolveService(sessionId, s -> + s.registerPredictionUpdates(sessionId, callback)); + final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (serviceExists && sessionInfo != null) { + sessionInfo.addCallbackLocked(callback); } } @@ -174,14 +174,11 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId, @NonNull IPredictionCallback callback) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.unregisterPredictionUpdates(sessionId, callback); - - AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); - if (sessionInfo != null) { - sessionInfo.removeCallbackLocked(callback); - } + final boolean serviceExists = resolveService(sessionId, s -> + s.unregisterPredictionUpdates(sessionId, callback)); + final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (serviceExists && sessionInfo != null) { + sessionInfo.removeCallbackLocked(callback); } } @@ -190,10 +187,7 @@ public class AppPredictionPerUserService extends */ @GuardedBy("mLock") public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.requestPredictionUpdate(sessionId); - } + resolveService(sessionId, s -> s.requestPredictionUpdate(sessionId)); } /** @@ -201,14 +195,11 @@ public class AppPredictionPerUserService extends */ @GuardedBy("mLock") public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.onDestroyPredictionSession(sessionId); - - AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); - if (sessionInfo != null) { - sessionInfo.destroy(); - } + final boolean serviceExists = resolveService(sessionId, s -> + s.onDestroyPredictionSession(sessionId)); + final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (serviceExists && sessionInfo != null) { + sessionInfo.destroy(); } } @@ -312,6 +303,33 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") @Nullable + protected boolean resolveService(@NonNull final AppPredictionSessionId sessionId, + @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb) { + final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo == null) return false; + if (sessionInfo.mUsesPeopleService) { + final IPredictionService service = + LocalServices.getService(PeopleServiceInternal.class); + if (service != null) { + try { + cb.run(service); + } catch (RemoteException e) { + // Shouldn't happen. + Slog.w(TAG, "Failed to invoke service:" + service, e); + } + } + return service != null; + } else { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.scheduleOnResolvedService(cb); + } + return service != null; + } + } + + @GuardedBy("mLock") + @Nullable private RemoteAppPredictionService getRemoteServiceLocked() { if (mRemoteService == null) { final String serviceName = getComponentNameLocked(); @@ -334,8 +352,12 @@ public class AppPredictionPerUserService extends private static final class AppPredictionSessionInfo { private static final boolean DEBUG = false; // Do not submit with true + @NonNull private final AppPredictionSessionId mSessionId; + @NonNull private final AppPredictionContext mPredictionContext; + private final boolean mUsesPeopleService; + @NonNull private final Consumer<AppPredictionSessionId> mRemoveSessionInfoAction; private final RemoteCallbackList<IPredictionCallback> mCallbacks = @@ -352,13 +374,17 @@ public class AppPredictionPerUserService extends } }; - AppPredictionSessionInfo(AppPredictionSessionId id, AppPredictionContext predictionContext, - Consumer<AppPredictionSessionId> removeSessionInfoAction) { + AppPredictionSessionInfo( + @NonNull final AppPredictionSessionId id, + @NonNull final AppPredictionContext predictionContext, + final boolean usesPeopleService, + @NonNull final Consumer<AppPredictionSessionId> removeSessionInfoAction) { if (DEBUG) { Slog.d(TAG, "Creating AppPredictionSessionInfo for session Id=" + id); } mSessionId = id; mPredictionContext = predictionContext; + mUsesPeopleService = usesPeopleService; mRemoveSessionInfoAction = removeSessionInfoAction; } diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java index 04e0e7f7102f..ceb1cafcebeb 100644 --- a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java +++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java @@ -16,13 +16,8 @@ package com.android.server.appprediction; import android.annotation.NonNull; -import android.app.prediction.AppPredictionContext; -import android.app.prediction.AppPredictionSessionId; -import android.app.prediction.AppTargetEvent; -import android.app.prediction.IPredictionCallback; import android.content.ComponentName; import android.content.Context; -import android.content.pm.ParceledListSlice; import android.os.IBinder; import android.service.appprediction.IPredictionService; import android.text.format.DateUtils; @@ -71,74 +66,17 @@ public class RemoteAppPredictionService extends } /** - * Notifies the service of a new prediction session. - */ - public void onCreatePredictionSession(@NonNull AppPredictionContext context, - @NonNull AppPredictionSessionId sessionId) { - scheduleAsyncRequest((s) -> s.onCreatePredictionSession(context, sessionId)); - } - - /** - * Records an app target event to the service. - */ - public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId, - @NonNull AppTargetEvent event) { - scheduleAsyncRequest((s) -> s.notifyAppTargetEvent(sessionId, event)); - } - - /** - * Records when a launch location is shown. - */ - public void notifyLaunchLocationShown(@NonNull AppPredictionSessionId sessionId, - @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { - scheduleAsyncRequest((s) - -> s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds)); - } - - /** - * Requests the service to sort a list of apps or shortcuts. - */ - public void sortAppTargets(@NonNull AppPredictionSessionId sessionId, - @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) { - scheduleAsyncRequest((s) -> s.sortAppTargets(sessionId, targets, callback)); - } - - - /** - * Registers a callback for continuous updates of predicted apps or shortcuts. - */ - public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId, - @NonNull IPredictionCallback callback) { - scheduleAsyncRequest((s) -> s.registerPredictionUpdates(sessionId, callback)); - } - - /** - * Unregisters a callback for continuous updates of predicted apps or shortcuts. - */ - public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId, - @NonNull IPredictionCallback callback) { - scheduleAsyncRequest((s) -> s.unregisterPredictionUpdates(sessionId, callback)); - } - - /** - * Requests a new set of predicted apps or shortcuts. - */ - public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) { - scheduleAsyncRequest((s) -> s.requestPredictionUpdate(sessionId)); - } - - /** - * Notifies the service of the end of an existing prediction session. + * Schedules a request to bind to the remote service. */ - public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) { - scheduleAsyncRequest((s) -> s.onDestroyPredictionSession(sessionId)); + public void reconnect() { + super.scheduleBind(); } /** - * Schedules a request to bind to the remote service. + * Schedule async request on remote service. */ - public void reconnect() { - super.scheduleBind(); + public void scheduleOnResolvedService(@NonNull AsyncRequest<IPredictionService> request) { + scheduleAsyncRequest(request); } /** diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 31ea5faa05f1..9a33fc9548a0 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -998,6 +998,11 @@ public final class ContentCaptureManagerService extends sendErrorSignal(mClientAdapterReference, serviceAdapterReference, ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN); + } finally { + synchronized (parentService.mLock) { + parentService.mPackagesWithShareRequests + .remove(mDataShareRequest.getPackageName()); + } } }); diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index 442c9e543b01..f688759e9f63 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -216,17 +216,11 @@ public abstract class UsageStatsManagerInternal { /** * Returns the events for the user in the given time period. * - * @param obfuscateInstantApps whether instant app package names need to be obfuscated in the - * 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. - * + * @param flags defines the visibility of certain usage events - see flags defined in + * {@link UsageEvents}. */ public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime, - long endTime, boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents, - boolean hideLocusIdEvents); + long endTime, int flags); /** * 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 c47cde34a9af..b334b26fa86c 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3749,6 +3749,7 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 c98762074b53..9540f4336464 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -1556,16 +1556,16 @@ public class IpSecService extends IIpSecService.Stub { } 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 75e310dd9202..b5b22f19d426 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1300,13 +1300,6 @@ class StorageManagerService extends IStorageManager.Stub 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 @@ class StorageManagerService extends IStorageManager.Stub 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); } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 0e5a6bb8bd1c..f85fc284330c 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.CellSignalStrengthTdscdma; 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private boolean[] mUserMobileDataState; + private DisplayInfo[] mDisplayInfos; + private SignalStrength[] mSignalStrength; private boolean[] mMessageWaiting; @@ -284,7 +287,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + /** + * 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + 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 000000000000..74f113f58c70 --- /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 085245835af3..ad550c5cedec 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; 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.SystemConfig; 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } + 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub } } }); + 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/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index abe0dd543deb..ffa7d9202371 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -59,6 +59,8 @@ import android.app.ApplicationExitInfo.Reason; import android.app.ApplicationExitInfo.SubReason; import android.app.IApplicationThread; import android.app.IUidObserver; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -119,6 +121,7 @@ import com.android.server.pm.dex.DexManager; import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.WindowManagerService; +import dalvik.annotation.compat.VersionCodes; import dalvik.system.VMRuntime; import java.io.File; @@ -327,6 +330,15 @@ public final class ProcessList { */ private static final int PROC_KILL_TIMEOUT = 2000; // 2 seconds; + /** + * Native heap allocations will now have a non-zero tag in the most significant byte. + * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged + * Pointers</a> + */ + @ChangeId + @EnabledAfter(targetSdkVersion = VersionCodes.Q) + private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. + ActivityManagerService mService = null; // To kill process groups asynchronously @@ -1768,6 +1780,13 @@ public final class ProcessList { runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE; } + // Enable heap pointer tagging, unless disabled by the app manifest, target sdk level, + // or the compat feature. + if (app.info.allowsNativeHeapPointerTagging() + && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) { + runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + } + String invokeWith = null; if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { // Debuggable apps may include a wrapper script with their library directory. diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 06561f57f495..3ffe1be89060 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.app.AppOpsManager.resolveFirstUnrestrictedUidState; 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 @@ public class AppOpsService extends IAppOpsService.Stub { 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 97a8b8764bcf..67d7530fa11e 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.Log; 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 @@ public class AudioService extends IAudioService.Stub // 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub streamState.applyAllVolumes(); } + // Restore audio volume groups + restoreVolumeGroups(); + // Restore mono mode updateMasterMono(mContentResolver); @@ -2288,20 +2297,20 @@ public class AudioService extends IAudioService.Stub 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 class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub enforceSafeMediaVolume(TAG); } } + + readVolumeGroupsSettings(); } /** @see AudioManager#setSpeakerphoneOn(boolean) */ @@ -4654,6 +4667,310 @@ public class AudioService extends IAudioService.Stub /////////////////////////////////////////////////////////////////////////// // 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub } 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 fcd8701f4ccf..add620e74df0 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 @@ public class AudioServiceEvents { 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 @@ public class AudioServiceEvents { mVal1 = val1; mVal2 = val2; mCaller = caller; + mGroupName = null; + mAudioAttributes = null; } /** used for VOL_SET_HEARING_AID_VOL*/ @@ -124,6 +130,8 @@ public class AudioServiceEvents { // unused mStream = -1; mCaller = null; + mGroupName = null; + mAudioAttributes = null; } /** used for VOL_SET_AVRCP_VOL */ @@ -134,6 +142,8 @@ public class AudioServiceEvents { mVal2 = 0; mStream = -1; mCaller = null; + mGroupName = null; + mAudioAttributes = null; } /** used for VOL_VOICE_ACTIVITY_HEARING_AID */ @@ -144,6 +154,8 @@ public class AudioServiceEvents { mVal2 = voiceActive ? 1 : 0; // unused mCaller = null; + mGroupName = null; + mAudioAttributes = null; } /** used for VOL_MODE_CHANGE_HEARING_AID */ @@ -154,6 +166,19 @@ public class AudioServiceEvents { 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 @@ public class AudioServiceEvents { .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/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index c9c2c96a642a..204f072a72fc 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -153,7 +153,12 @@ public class AuthService extends SystemService { // Only allow internal clients to enable non-public options. if (bundle.getBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS) - || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) { + || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false) + || bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE) != null + || bundle.getCharSequence( + BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE) != null + || bundle.getCharSequence( + BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION) != null) { checkInternalPermission(); } diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 2eec4199217f..8687f35d745e 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 @@ import java.util.Map; 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 cb88c4e8a739..1a68f1bff605 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.ResolveInfo; 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.RouteInfo; 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.Inet4Address; 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.Objects; 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 @@ public class Vpn { 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 @@ public class Vpn { @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 class Vpn { 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 @@ public class Vpn { 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 @@ public class Vpn { } 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 @@ public class Vpn { 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); + + 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(); - IkeV2VpnRunner(VpnProfile profile) { + /** 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 @@ public class Vpn { 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 class Vpn { 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 000000000000..33fc32b78df7 --- /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/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 7c2ec78c1cbc..d9e30250ba2d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -105,32 +105,57 @@ import java.util.stream.Collectors; */ public class HdmiControlService extends SystemService { private static final String TAG = "HdmiControlService"; - private final Locale HONG_KONG = new Locale("zh", "HK"); - private final Locale MACAU = new Locale("zh", "MO"); + private static final Locale HONG_KONG = new Locale("zh", "HK"); + private static final Locale MACAU = new Locale("zh", "MO"); - private static final Map<String, String> mTerminologyToBibliographicMap; - static { - mTerminologyToBibliographicMap = new HashMap<>(); + private static final Map<String, String> sTerminologyToBibliographicMap = + createsTerminologyToBibliographicMap(); + + private static Map<String, String> createsTerminologyToBibliographicMap() { + Map<String, String> temp = new HashMap<>(); // NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE) - mTerminologyToBibliographicMap.put("sqi", "alb"); // Albanian - mTerminologyToBibliographicMap.put("hye", "arm"); // Armenian - mTerminologyToBibliographicMap.put("eus", "baq"); // Basque - mTerminologyToBibliographicMap.put("mya", "bur"); // Burmese - mTerminologyToBibliographicMap.put("ces", "cze"); // Czech - mTerminologyToBibliographicMap.put("nld", "dut"); // Dutch - mTerminologyToBibliographicMap.put("kat", "geo"); // Georgian - mTerminologyToBibliographicMap.put("deu", "ger"); // German - mTerminologyToBibliographicMap.put("ell", "gre"); // Greek - mTerminologyToBibliographicMap.put("fra", "fre"); // French - mTerminologyToBibliographicMap.put("isl", "ice"); // Icelandic - mTerminologyToBibliographicMap.put("mkd", "mac"); // Macedonian - mTerminologyToBibliographicMap.put("mri", "mao"); // Maori - mTerminologyToBibliographicMap.put("msa", "may"); // Malay - mTerminologyToBibliographicMap.put("fas", "per"); // Persian - mTerminologyToBibliographicMap.put("ron", "rum"); // Romanian - mTerminologyToBibliographicMap.put("slk", "slo"); // Slovak - mTerminologyToBibliographicMap.put("bod", "tib"); // Tibetan - mTerminologyToBibliographicMap.put("cym", "wel"); // Welsh + temp.put("sqi", "alb"); // Albanian + temp.put("hye", "arm"); // Armenian + temp.put("eus", "baq"); // Basque + temp.put("mya", "bur"); // Burmese + temp.put("ces", "cze"); // Czech + temp.put("nld", "dut"); // Dutch + temp.put("kat", "geo"); // Georgian + temp.put("deu", "ger"); // German + temp.put("ell", "gre"); // Greek + temp.put("fra", "fre"); // French + temp.put("isl", "ice"); // Icelandic + temp.put("mkd", "mac"); // Macedonian + temp.put("mri", "mao"); // Maori + temp.put("msa", "may"); // Malay + temp.put("fas", "per"); // Persian + temp.put("ron", "rum"); // Romanian + temp.put("slk", "slo"); // Slovak + temp.put("bod", "tib"); // Tibetan + temp.put("cym", "wel"); // Welsh + return Collections.unmodifiableMap(temp); + } + + @VisibleForTesting static String localeToMenuLanguage(Locale locale) { + if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) { + // Android always returns "zho" for all Chinese variants. + // Use "bibliographic" code defined in CEC639-2 for traditional + // Chinese used in Taiwan/Hong Kong/Macau. + return "chi"; + } else { + String language = locale.getISO3Language(); + + // locale.getISO3Language() returns terminology code and need to + // send it as bibliographic code instead since the Bibliographic + // codes of ISO/FDIS 639-2 shall be used. + // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi" + // But, as it depends on the locale, is not handled here. + if (sTerminologyToBibliographicMap.containsKey(language)) { + language = sTerminologyToBibliographicMap.get(language); + } + + return language; + } } static final String PERMISSION = "android.permission.HDMI_CEC"; @@ -208,8 +233,8 @@ public class HdmiControlService extends SystemService { } break; case Intent.ACTION_CONFIGURATION_CHANGED: - String language = getMenuLanguage(); - if (!mLanguage.equals(language)) { + String language = HdmiControlService.localeToMenuLanguage(Locale.getDefault()); + if (!mMenuLanguage.equals(language)) { onLanguageChanged(language); } break; @@ -221,28 +246,6 @@ public class HdmiControlService extends SystemService { } } - private String getMenuLanguage() { - Locale locale = Locale.getDefault(); - if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) { - // Android always returns "zho" for all Chinese variants. - // Use "bibliographic" code defined in CEC639-2 for traditional - // Chinese used in Taiwan/Hong Kong/Macau. - return "chi"; - } else { - String language = locale.getISO3Language(); - - // locale.getISO3Language() returns terminology code and need to - // send it as bibliographic code instead since the Bibliographic - // codes of ISO/FDIS 639-2 shall be used. - // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi" - // But, as it depends on the locale, is not handled here. - if (mTerminologyToBibliographicMap.containsKey(language)) { - language = mTerminologyToBibliographicMap.get(language); - } - - return language; - } - } } // A thread to handle synchronous IO of CEC and MHL control service. @@ -339,7 +342,7 @@ public class HdmiControlService extends SystemService { private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; @ServiceThreadOnly - private String mLanguage = Locale.getDefault().getISO3Language(); + private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault()); @ServiceThreadOnly private boolean mStandbyMessageReceived = false; @@ -759,7 +762,7 @@ public class HdmiControlService extends SystemService { private void initializeCec(int initiatedBy) { mAddressAllocated = false; mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true); - mCecController.setLanguage(mLanguage); + mCecController.setLanguage(mMenuLanguage); initializeLocalDevices(initiatedBy); } @@ -2818,7 +2821,7 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly private void onLanguageChanged(String language) { assertRunOnServiceThread(); - mLanguage = language; + mMenuLanguage = language; if (isTvDeviceEnabled()) { tv().broadcastMenuLanguage(language); @@ -2826,10 +2829,19 @@ public class HdmiControlService extends SystemService { } } + /** + * Gets the CEC menu language. + * + * <p>This is the ISO/FDIS 639-2 3 letter language code sent in the CEC message @{code <Set Menu + * Language>}. + * See HDMI 1.4b section CEC 13.6.2 + * + * @see {@link Locale#getISO3Language()} + */ @ServiceThreadOnly String getLanguage() { assertRunOnServiceThread(); - return mLanguage; + return mMenuLanguage; } private void disableDevices(PendingActionClearedCallback callback) { @@ -2838,7 +2850,6 @@ public class HdmiControlService extends SystemService { device.disableDevice(mStandbyMessageReceived, callback); } } - mMhlController.clearAllLocalDevices(); } diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS new file mode 100644 index 000000000000..25ef9facb216 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/OWNERS @@ -0,0 +1,6 @@ +set noparent + +ogunwale@google.com +yukawa@google.com +tarandeep@google.com +lumark@google.com diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 68ced79b3e38..b9a30bb21edb 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.Intent.EXTRA_PACKAGE_NAME; 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 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { * 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 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { 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 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { 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 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { 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 79e69e15ff67..61da45ddbfef 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.IntegrityFileManager; 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 @@ public class RuleEvaluationEngine { * @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 @@ public class RuleEvaluationEngine { 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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 916b63bee0e8..d0d0f5a3a233 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 @@ public class NotificationManagerService extends SystemService { 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); @@ -1055,7 +1055,7 @@ public class NotificationManagerService extends SystemService { if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key); reportSeen(r); } - r.setVisibility(true, nv.rank, nv.count); + r.setVisibility(true, nv.rank, nv.count, mNotificationRecordLogger); mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), true); boolean isHun = (nv.location == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP); @@ -1074,7 +1074,7 @@ public class NotificationManagerService extends SystemService { for (NotificationVisibility nv : noLongerVisibleKeys) { NotificationRecord r = mNotificationsByKey.get(nv.key); if (r == null) continue; - r.setVisibility(false, nv.rank, nv.count); + r.setVisibility(false, nv.rank, nv.count, mNotificationRecordLogger); mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), false); nv.recycle(); } @@ -3979,6 +3979,29 @@ public class NotificationManagerService extends SystemService { } /** + * Allows the notification assistant to un-snooze a single notification. + * + * @param token The binder for the listener, to check that the caller is allowed + */ + @Override + public void unsnoozeNotificationFromSystemListener(INotificationListener token, + String key) { + long identity = Binder.clearCallingIdentity(); + try { + synchronized (mNotificationLock) { + final ManagedServiceInfo info = + mListeners.checkServiceTokenLocked(token); + if (!info.isSystem) { + throw new SecurityException("Not allowed to unsnooze before deadline"); + } + unsnoozeNotificationInt(key, info); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** * Allow an INotificationListener to simulate clearing (dismissing) a single notification. * * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear} @@ -6468,7 +6491,7 @@ public class NotificationManagerService extends SystemService { mUsageStats.registerPostedByApp(r); r.setInterruptive(isVisuallyInterruptive(null, r)); } else { - old = mNotificationList.get(index); + old = mNotificationList.get(index); // Potentially *changes* old mNotificationList.set(index, r); mUsageStats.registerUpdatedByApp(r, old); // Make sure we don't lose the foreground service state. @@ -6537,7 +6560,7 @@ public class NotificationManagerService extends SystemService { maybeRecordInterruptionLocked(r); // Log event to statsd - mNotificationRecordLogger.logNotificationReported(r, old, position, + mNotificationRecordLogger.maybeLogNotificationPosted(r, old, position, buzzBeepBlinkLoggingCode); } finally { int N = mEnqueuedNotifications.size(); @@ -7986,7 +8009,8 @@ public class NotificationManagerService extends SystemService { 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/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 4785da9a5922..0ada58e1ce16 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -909,7 +909,8 @@ public final class NotificationRecord { /** * Set the visibility of the notification. */ - public void setVisibility(boolean visible, int rank, int count) { + public void setVisibility(boolean visible, int rank, int count, + NotificationRecordLogger notificationRecordLogger) { final long now = System.currentTimeMillis(); mVisibleSinceMs = visible ? now : mVisibleSinceMs; stats.onVisibilityChanged(visible); @@ -927,6 +928,7 @@ public final class NotificationRecord { getFreshnessMs(now), 0, // exposure time rank); + notificationRecordLogger.logNotificationVisibility(this, visible); } /** diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index 2f7854226c5c..eaca066f026f 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -41,13 +41,14 @@ import java.util.Objects; public interface NotificationRecordLogger { /** - * Logs a NotificationReported atom reflecting the posting or update of a notification. + * May log a NotificationReported atom reflecting the posting or update of a notification. * @param r The new NotificationRecord. If null, no action is taken. * @param old The previous NotificationRecord. Null if there was no previous record. * @param position The position at which this notification is ranked. * @param buzzBeepBlink Logging code reflecting whether this notification alerted the user. */ - void logNotificationReported(@Nullable NotificationRecord r, @Nullable NotificationRecord old, + void maybeLogNotificationPosted(@Nullable NotificationRecord r, + @Nullable NotificationRecord old, int position, int buzzBeepBlink); /** @@ -62,6 +63,14 @@ public interface NotificationRecordLogger { @NotificationStats.DismissalSurface int dismissalSurface); /** + * Logs a notification visibility change event using UiEventReported (event ids from the + * NotificationEvents enum). + * @param r The NotificationRecord. If null, no action is taken. + * @param visible True if the notification became visible. + */ + void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible); + + /** * The UiEvent enums that this class can log. */ enum NotificationReportedEvent implements UiEventLogger.UiEventEnum { @@ -145,6 +154,7 @@ public interface NotificationRecordLogger { @Override public int getId() { return mId; } + public static NotificationCancelledEvent fromCancelReason( @NotificationListenerService.NotificationCancelReason int reason, @NotificationStats.DismissalSurface int surface) { @@ -191,6 +201,24 @@ public interface NotificationRecordLogger { } } + enum NotificationEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "Notification became visible.") + NOTIFICATION_OPEN(197), + @UiEvent(doc = "Notification stopped being visible.") + NOTIFICATION_CLOSE(198); + + private final int mId; + NotificationEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } + + public static NotificationEvent fromVisibility(boolean visible) { + return visible ? NOTIFICATION_OPEN : NOTIFICATION_CLOSE; + } + } /** * A helper for extracting logging information from one or two NotificationRecords. */ @@ -209,7 +237,7 @@ public interface NotificationRecordLogger { /** * @return True if old is null, alerted, or important logged fields have changed. */ - boolean shouldLog(int buzzBeepBlink) { + boolean shouldLogReported(int buzzBeepBlink) { if (r == null) { return false; } diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java index bb23d1e876dc..9fcac257d328 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java @@ -26,13 +26,13 @@ import com.android.internal.util.FrameworkStatsLog; */ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { - UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); @Override - public void logNotificationReported(NotificationRecord r, NotificationRecord old, + public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old, int position, int buzzBeepBlink) { NotificationRecordPair p = new NotificationRecordPair(r, old); - if (!p.shouldLog(buzzBeepBlink)) { + if (!p.shouldLogReported(buzzBeepBlink)) { return; } FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED, @@ -66,8 +66,19 @@ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { @Override public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) { - mUiEventLogger.logWithInstanceId( - NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), - r.getUid(), r.getSbn().getPackageName(), r.getSbn().getInstanceId()); + log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r); + } + + @Override + public void logNotificationVisibility(NotificationRecord r, boolean visible) { + log(NotificationEvent.fromVisibility(visible), r); + } + + void log(UiEventLogger.UiEventEnum event, NotificationRecord r) { + if (r == null) { + return; + } + mUiEventLogger.logWithInstanceId(event, r.getUid(), r.getSbn().getPackageName(), + r.getSbn().getInstanceId()); } } diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index 661297a7346e..bae1dd3c6cb6 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -16,6 +16,7 @@ package com.android.server.notification; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -42,7 +43,9 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; +import java.sql.Array; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -77,26 +80,26 @@ public class SnoozeHelper { private static final String REPOST_ACTION = SnoozeHelper.class.getSimpleName() + ".EVALUATE"; private static final int REQUEST_CODE_REPOST = 1; private static final String REPOST_SCHEME = "repost"; - private static final String EXTRA_KEY = "key"; + static final String EXTRA_KEY = "key"; private static final String EXTRA_USER_ID = "userId"; private final Context mContext; private AlarmManager mAm; private final ManagedServices.UserProfiles mUserProfiles; - // User id : package name : notification key : record. - private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, NotificationRecord>>> + // User id | package name : notification key : record. + private ArrayMap<String, ArrayMap<String, NotificationRecord>> mSnoozedNotifications = new ArrayMap<>(); - // User id : package name : notification key : time-milliseconds . + // User id | package name : notification key : time-milliseconds . // This member stores persisted snoozed notification trigger times. it persists through reboots // It should have the notifications that haven't expired or re-posted yet - private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, Long>>> + private final ArrayMap<String, ArrayMap<String, Long>> mPersistedSnoozedNotifications = new ArrayMap<>(); - // User id : package name : notification key : creation ID . + // User id | package name : notification key : creation ID . // This member stores persisted snoozed notification trigger context for the assistant // it persists through reboots. // It should have the notifications that haven't expired or re-posted yet - private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, String>>> + private final ArrayMap<String, ArrayMap<String, String>> mPersistedSnoozedNotificationsWithContext = new ArrayMap<>(); // notification key : package. private ArrayMap<String, String> mPackages = new ArrayMap<>(); @@ -115,6 +118,10 @@ public class SnoozeHelper { mUserProfiles = userProfiles; } + private String getPkgKey(@UserIdInt int userId, String pkg) { + return userId + "|" + pkg; + } + void cleanupPersistedContext(String key){ int userId = mUsers.get(key); String pkg = mPackages.get(key); @@ -144,15 +151,13 @@ public class SnoozeHelper { } protected boolean isSnoozed(int userId, String pkg, String key) { - return mSnoozedNotifications.containsKey(userId) - && mSnoozedNotifications.get(userId).containsKey(pkg) - && mSnoozedNotifications.get(userId).get(pkg).containsKey(key); + return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg)) + && mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key); } protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) { - if (mSnoozedNotifications.containsKey(userId) - && mSnoozedNotifications.get(userId).containsKey(pkg)) { - return mSnoozedNotifications.get(userId).get(pkg).values(); + if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) { + return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values(); } return Collections.EMPTY_LIST; } @@ -161,14 +166,14 @@ public class SnoozeHelper { ArrayList<NotificationRecord> getNotifications(String pkg, String groupKey, Integer userId) { ArrayList<NotificationRecord> records = new ArrayList<>(); - if (mSnoozedNotifications.containsKey(userId) - && mSnoozedNotifications.get(userId).containsKey(pkg)) { - ArrayMap<String, NotificationRecord> packages = - mSnoozedNotifications.get(userId).get(pkg); - for (int i = 0; i < packages.size(); i++) { - String currentGroupKey = packages.valueAt(i).getSbn().getGroup(); + ArrayMap<String, NotificationRecord> allRecords = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); + if (allRecords != null) { + for (int i = 0; i < allRecords.size(); i++) { + NotificationRecord r = allRecords.valueAt(i); + String currentGroupKey = r.getSbn().getGroup(); if (currentGroupKey.equals(groupKey)) { - records.add(packages.valueAt(i)); + records.add(r); } } } @@ -176,47 +181,30 @@ public class SnoozeHelper { } protected NotificationRecord getNotification(String key) { - List<NotificationRecord> snoozedForUser = new ArrayList<>(); - IntArray userIds = mUserProfiles.getCurrentProfileIds(); - if (userIds != null) { - final int userIdsSize = userIds.size(); - for (int i = 0; i < userIdsSize; i++) { - final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs = - mSnoozedNotifications.get(userIds.get(i)); - if (snoozedPkgs != null) { - final int snoozedPkgsSize = snoozedPkgs.size(); - for (int j = 0; j < snoozedPkgsSize; j++) { - final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j); - if (records != null) { - return records.get(key); - } - } - } - } + if (!mUsers.containsKey(key) || !mPackages.containsKey(key)) { + Slog.w(TAG, "Snoozed data sets no longer agree for " + key); + return null; + } + int userId = mUsers.get(key); + String pkg = mPackages.get(key); + ArrayMap<String, NotificationRecord> snoozed = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); + if (snoozed == null) { + return null; } - return null; + return snoozed.get(key); } protected @NonNull List<NotificationRecord> getSnoozed() { - List<NotificationRecord> snoozedForUser = new ArrayList<>(); - IntArray userIds = mUserProfiles.getCurrentProfileIds(); - if (userIds != null) { - final int N = userIds.size(); - for (int i = 0; i < N; i++) { - final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs = - mSnoozedNotifications.get(userIds.get(i)); - if (snoozedPkgs != null) { - final int M = snoozedPkgs.size(); - for (int j = 0; j < M; j++) { - final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j); - if (records != null) { - snoozedForUser.addAll(records.values()); - } - } - } - } - } - return snoozedForUser; + // caller filters records based on the current user profiles and listener access, so just + // return everything + List<NotificationRecord> snoozed= new ArrayList<>(); + for (String userPkgKey : mSnoozedNotifications.keySet()) { + ArrayMap<String, NotificationRecord> snoozedRecords = + mSnoozedNotifications.get(userPkgKey); + snoozed.addAll(snoozedRecords.values()); + } + return snoozed; } /** @@ -261,120 +249,88 @@ public class SnoozeHelper { } private <T> void storeRecord(String pkg, String key, Integer userId, - ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, T object) { + ArrayMap<String, ArrayMap<String, T>> targets, T object) { - ArrayMap<String, ArrayMap<String, T>> records = - targets.get(userId); - if (records == null) { - records = new ArrayMap<>(); - } - ArrayMap<String, T> pkgRecords = records.get(pkg); - if (pkgRecords == null) { - pkgRecords = new ArrayMap<>(); + ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg)); + if (keyToValue == null) { + keyToValue = new ArrayMap<>(); } - pkgRecords.put(key, object); - records.put(pkg, pkgRecords); - targets.put(userId, records); + keyToValue.put(key, object); + targets.put(getPkgKey(userId, pkg), keyToValue); } private <T> T removeRecord(String pkg, String key, Integer userId, - ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets) { + ArrayMap<String, ArrayMap<String, T>> targets) { T object = null; - - ArrayMap<String, ArrayMap<String, T>> records = - targets.get(userId); - if (records == null) { + ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg)); + if (keyToValue == null) { return null; } - ArrayMap<String, T> pkgRecords = records.get(pkg); - if (pkgRecords == null) { - return null; - } - object = pkgRecords.remove(key); - if (pkgRecords.size() == 0) { - records.remove(pkg); - } - if (records.size() == 0) { - targets.remove(userId); + object = keyToValue.remove(key); + if (keyToValue.size() == 0) { + targets.remove(getPkgKey(userId, pkg)); } return object; } protected boolean cancel(int userId, String pkg, String tag, int id) { - if (mSnoozedNotifications.containsKey(userId)) { - ArrayMap<String, NotificationRecord> recordsForPkg = - mSnoozedNotifications.get(userId).get(pkg); - if (recordsForPkg != null) { - final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet(); - for (Map.Entry<String, NotificationRecord> record : records) { - final StatusBarNotification sbn = record.getValue().getSbn(); - if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) { - record.getValue().isCanceled = true; - return true; - } + ArrayMap<String, NotificationRecord> recordsForPkg = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); + if (recordsForPkg != null) { + final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet(); + for (Map.Entry<String, NotificationRecord> record : records) { + final StatusBarNotification sbn = record.getValue().getSbn(); + if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) { + record.getValue().isCanceled = true; + return true; } } } return false; } - protected boolean cancel(int userId, boolean includeCurrentProfiles) { - int[] userIds = {userId}; + protected void cancel(int userId, boolean includeCurrentProfiles) { + if (mSnoozedNotifications.size() == 0) { + return; + } + IntArray userIds = new IntArray(); + userIds.add(userId); if (includeCurrentProfiles) { - userIds = mUserProfiles.getCurrentProfileIds().toArray(); + userIds = mUserProfiles.getCurrentProfileIds(); } - final int N = userIds.length; - for (int i = 0; i < N; i++) { - final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs = - mSnoozedNotifications.get(userIds[i]); - if (snoozedPkgs != null) { - final int M = snoozedPkgs.size(); - for (int j = 0; j < M; j++) { - final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j); - if (records != null) { - int P = records.size(); - for (int k = 0; k < P; k++) { - records.valueAt(k).isCanceled = true; - } - } + for (ArrayMap<String, NotificationRecord> snoozedRecords : mSnoozedNotifications.values()) { + for (NotificationRecord r : snoozedRecords.values()) { + if (userIds.binarySearch(r.getUserId()) >= 0) { + r.isCanceled = true; } - return true; } } - return false; } protected boolean cancel(int userId, String pkg) { - if (mSnoozedNotifications.containsKey(userId)) { - if (mSnoozedNotifications.get(userId).containsKey(pkg)) { - ArrayMap<String, NotificationRecord> records = - mSnoozedNotifications.get(userId).get(pkg); - int N = records.size(); - for (int i = 0; i < N; i++) { - records.valueAt(i).isCanceled = true; - } - return true; - } + ArrayMap<String, NotificationRecord> records = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); + if (records == null) { + return false; } - return false; + int N = records.size(); + for (int i = 0; i < N; i++) { + records.valueAt(i).isCanceled = true; + } + return true; } /** * Updates the notification record so the most up to date information is shown on re-post. */ protected void update(int userId, NotificationRecord record) { - ArrayMap<String, ArrayMap<String, NotificationRecord>> records = - mSnoozedNotifications.get(userId); + ArrayMap<String, NotificationRecord> records = + mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName())); if (records == null) { return; } - ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.getSbn().getPackageName()); - if (pkgRecords == null) { - return; - } - NotificationRecord existing = pkgRecords.get(record.getKey()); - pkgRecords.put(record.getKey(), record); + records.put(record.getKey(), record); } protected void repost(String key) { @@ -386,20 +342,18 @@ public class SnoozeHelper { protected void repost(String key, int userId) { final String pkg = mPackages.remove(key); - ArrayMap<String, ArrayMap<String, NotificationRecord>> records = - mSnoozedNotifications.get(userId); + ArrayMap<String, NotificationRecord> records = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { return; } - ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg); - if (pkgRecords == null) { - return; - } - final NotificationRecord record = pkgRecords.remove(key); + final NotificationRecord record = records.remove(key); mPackages.remove(key); mUsers.remove(key); if (record != null && !record.isCanceled) { + final PendingIntent pi = createPendingIntent(pkg, record.getKey(), userId); + mAm.cancel(pi); MetricsLogger.action(record.getLogMaker() .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); @@ -408,55 +362,46 @@ public class SnoozeHelper { } protected void repostGroupSummary(String pkg, int userId, String groupKey) { - if (mSnoozedNotifications.containsKey(userId)) { - ArrayMap<String, ArrayMap<String, NotificationRecord>> keysByPackage - = mSnoozedNotifications.get(userId); - - if (keysByPackage != null && keysByPackage.containsKey(pkg)) { - ArrayMap<String, NotificationRecord> recordsByKey = keysByPackage.get(pkg); - - if (recordsByKey != null) { - String groupSummaryKey = null; - int N = recordsByKey.size(); - for (int i = 0; i < N; i++) { - final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i); - if (potentialGroupSummary.getSbn().isGroup() - && potentialGroupSummary.getNotification().isGroupSummary() - && groupKey.equals(potentialGroupSummary.getGroupKey())) { - groupSummaryKey = potentialGroupSummary.getKey(); - break; - } - } + ArrayMap<String, NotificationRecord> recordsByKey + = mSnoozedNotifications.get(getPkgKey(userId, pkg)); + if (recordsByKey == null) { + return; + } - if (groupSummaryKey != null) { - NotificationRecord record = recordsByKey.remove(groupSummaryKey); - mPackages.remove(groupSummaryKey); - mUsers.remove(groupSummaryKey); + String groupSummaryKey = null; + int N = recordsByKey.size(); + for (int i = 0; i < N; i++) { + final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i); + if (potentialGroupSummary.getSbn().isGroup() + && potentialGroupSummary.getNotification().isGroupSummary() + && groupKey.equals(potentialGroupSummary.getGroupKey())) { + groupSummaryKey = potentialGroupSummary.getKey(); + break; + } + } - if (record != null && !record.isCanceled) { - MetricsLogger.action(record.getLogMaker() - .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) - .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); - mCallback.repost(userId, record); - } - } - } + if (groupSummaryKey != null) { + NotificationRecord record = recordsByKey.remove(groupSummaryKey); + mPackages.remove(groupSummaryKey); + mUsers.remove(groupSummaryKey); + + if (record != null && !record.isCanceled) { + MetricsLogger.action(record.getLogMaker() + .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) + .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); + mCallback.repost(userId, record); } } } protected void clearData(int userId, String pkg) { - ArrayMap<String, ArrayMap<String, NotificationRecord>> records = - mSnoozedNotifications.get(userId); + ArrayMap<String, NotificationRecord> records = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { return; } - ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg); - if (pkgRecords == null) { - return; - } - for (int i = pkgRecords.size() - 1; i >= 0; i--) { - final NotificationRecord r = pkgRecords.removeAt(i); + for (int i = records.size() - 1; i >= 0; i--) { + final NotificationRecord r = records.removeAt(i); if (r != null) { mPackages.remove(r.getKey()); mUsers.remove(r.getKey()); @@ -495,22 +440,36 @@ public class SnoozeHelper { public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) { pw.println("\n Snoozed notifications:"); - for (int userId : mSnoozedNotifications.keySet()) { + for (String userPkgKey : mSnoozedNotifications.keySet()) { pw.print(INDENT); - pw.println("user: " + userId); - ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs = - mSnoozedNotifications.get(userId); - for (String pkg : snoozedPkgs.keySet()) { + pw.println("key: " + userPkgKey); + ArrayMap<String, NotificationRecord> snoozedRecords = + mSnoozedNotifications.get(userPkgKey); + Set<String> snoozedKeys = snoozedRecords.keySet(); + for (String key : snoozedKeys) { pw.print(INDENT); pw.print(INDENT); - pw.println("package: " + pkg); - Set<String> snoozedKeys = snoozedPkgs.get(pkg).keySet(); - for (String key : snoozedKeys) { - pw.print(INDENT); - pw.print(INDENT); - pw.print(INDENT); - pw.println(key); - } + pw.print(INDENT); + pw.println(key); + } + } + pw.println("\n Pending snoozed notifications"); + for (String userPkgKey : mPersistedSnoozedNotifications.keySet()) { + pw.print(INDENT); + pw.println("key: " + userPkgKey); + ArrayMap<String, Long> snoozedRecords = + mPersistedSnoozedNotifications.get(userPkgKey); + if (snoozedRecords == null) { + continue; + } + Set<String> snoozedKeys = snoozedRecords.keySet(); + for (String key : snoozedKeys) { + pw.print(INDENT); + pw.print(INDENT); + pw.print(INDENT); + pw.print(key); + pw.print(INDENT); + pw.println(snoozedRecords.get(key)); } } } @@ -538,41 +497,34 @@ public class SnoozeHelper { void insert(T t) throws IOException; } private <T> void writeXml(XmlSerializer out, - ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, String tag, + ArrayMap<String, ArrayMap<String, T>> targets, String tag, Inserter<T> attributeInserter) throws IOException { synchronized (targets) { final int M = targets.size(); for (int i = 0; i < M; i++) { - final ArrayMap<String, ArrayMap<String, T>> packages = - targets.valueAt(i); - if (packages == null) { - continue; - } - final int N = packages.size(); - for (int j = 0; j < N; j++) { - final ArrayMap<String, T> keyToValue = packages.valueAt(j); - if (keyToValue == null) { - continue; - } - final int O = keyToValue.size(); - for (int k = 0; k < O; k++) { - T value = keyToValue.valueAt(k); + String userIdPkgKey = targets.keyAt(i); + // T is a String (snoozed until context) or Long (snoozed until time) + ArrayMap<String, T> keyToValue = targets.valueAt(i); + for (int j = 0; j < keyToValue.size(); j++) { + String key = keyToValue.keyAt(j); + T value = keyToValue.valueAt(j); - out.startTag(null, tag); + out.startTag(null, tag); - attributeInserter.insert(value); + attributeInserter.insert(value); - out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, - XML_SNOOZED_NOTIFICATION_VERSION); - out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, keyToValue.keyAt(k)); - out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, packages.keyAt(j)); - out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID, - targets.keyAt(i).toString()); + out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, + XML_SNOOZED_NOTIFICATION_VERSION); + out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key); - out.endTag(null, tag); + String pkg = mPackages.get(key); + int userId = mUsers.get(key); + out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg); + out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID, + String.valueOf(userId)); - } + out.endTag(null, tag); } } } @@ -606,7 +558,6 @@ public class SnoozeHelper { } scheduleRepost(pkg, key, userId, time - System.currentTimeMillis()); } - continue; } if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) { final String creationId = parser.getAttributeValue( @@ -615,18 +566,9 @@ public class SnoozeHelper { storeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext, creationId); } - continue; } - - } catch (Exception e) { - //we dont cre if it is a number format exception or a null pointer exception. - //we just want to debug it and continue with our lives - if (DEBUG) { - Slog.d(TAG, - "Exception in reading snooze data from policy xml: " - + e.getMessage()); - } + Slog.e(TAG, "Exception in reading snooze data from policy xml", e); } } } diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index d629b547992b..0fb889c8da22 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -69,7 +69,7 @@ public class AppsFilter { // Logs all filtering instead of enforcing private static final boolean DEBUG_ALLOW_ALL = false; private static final boolean DEBUG_LOGGING = false; - private static final boolean FEATURE_ENABLED_BY_DEFAULT = false; + private static final boolean FEATURE_ENABLED_BY_DEFAULT = true; /** * This contains a list of app UIDs that are implicitly queryable because another app explicitly diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index b98bb0831b0e..8ad3e9df8bdf 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 @@ public class Installer extends SystemService { /** * 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 2c85d067a3ca..064fd3f4e0b2 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub */ @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 @@ public class PackageManagerService extends IPackageManager.Stub @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 @@ public class PackageManagerService extends IPackageManager.Stub // 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 @@ public class PackageManagerService extends IPackageManager.Stub @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 @@ public class PackageManagerService extends IPackageManager.Stub @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/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index c267cea163d5..f1e403b1bc63 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -119,6 +119,7 @@ import java.io.PrintWriter; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -135,7 +136,6 @@ import java.util.concurrent.TimeUnit; class PackageManagerShellCommand extends ShellCommand { /** Path for streaming APK content */ private static final String STDIN_PATH = "-"; - private static final byte[] STDIN_PATH_BYTES = "-".getBytes(StandardCharsets.UTF_8); /** Path where ART profiles snapshots are dumped for the shell user */ private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/"; private static final int DEFAULT_WAIT_MS = 60 * 1000; @@ -2988,8 +2988,10 @@ class PackageManagerShellCommand extends ShellCommand { try { // 1. Single file from stdin. if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) { - String name = "base." + (isApex ? "apex" : "apk"); - session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes, STDIN_PATH_BYTES, null); + final String name = "base." + (isApex ? "apex" : "apk"); + final String metadata = "-" + name; + session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes, + metadata.getBytes(StandardCharsets.UTF_8), null); return 0; } @@ -2998,24 +3000,58 @@ class PackageManagerShellCommand extends ShellCommand { // 2. File with specified size read from stdin. if (delimLocation != -1) { - String name = arg.substring(0, delimLocation); - String sizeStr = arg.substring(delimLocation + 1); - long sizeBytes; + final String[] fileDesc = arg.split(":"); + String name = null; + long sizeBytes = -1; + String metadata; + byte[] signature = null; + + try { + if (fileDesc.length > 0) { + name = fileDesc[0]; + } + if (fileDesc.length > 1) { + sizeBytes = Long.parseUnsignedLong(fileDesc[1]); + } + if (fileDesc.length > 2 && !TextUtils.isEmpty(fileDesc[2])) { + metadata = fileDesc[2]; + } else { + metadata = name; + } + if (fileDesc.length > 3) { + signature = Base64.getDecoder().decode(fileDesc[3]); + } + } catch (IllegalArgumentException e) { + getErrPrintWriter().println( + "Unable to parse file parameters: " + arg + ", reason: " + e); + return 1; + } if (TextUtils.isEmpty(name)) { getErrPrintWriter().println("Empty file name in: " + arg); return 1; } + + if (signature != null) { + // Streaming/adb mode. + metadata = "+" + metadata; + } else { + // Singleshot read from stdin. + metadata = "-" + metadata; + } + try { - sizeBytes = Long.parseUnsignedLong(sizeStr); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Unable to parse size from: " + arg); + if (V4Signature.readFrom(signature) == null) { + getErrPrintWriter().println("V4 signature is invalid in: " + arg); + return 1; + } + } catch (Exception e) { + getErrPrintWriter().println("V4 signature is invalid: " + e + " in " + arg); return 1; } - // Incremental requires unique metadatas, let's add a name to the dash. session.addFile(LOCATION_DATA_APP, name, sizeBytes, - ("-" + name).getBytes(StandardCharsets.UTF_8), null); + metadata.getBytes(StandardCharsets.UTF_8), signature); continue; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 0fb4cb036282..832c9b788b0b 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4393,6 +4393,7 @@ public final class Settings { ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT, "SYSTEM_EXT", ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD, "VIRTUAL_PRELOAD", ApplicationInfo.PRIVATE_FLAG_ODM, "ODM", + ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING, "PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING", }; void dumpVersionLPr(IndentingPrintWriter pw) { diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 67a22d3e477c..39093aec07c0 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -866,6 +866,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { } } + default int getMaxWindowLayer() { + return 35; + } + /** * Return how to Z-order sub-windows in relation to the window they are attached to. * Return positive to have them ordered in front, negative for behind. diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 002ab9ccc57c..a7b0d84ef040 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 static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; 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.RescueParty; 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService } 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 @@ public final class PowerManagerService extends SystemService * @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/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 9e150fd6a8b3..f9981d0d9c96 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -802,7 +802,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { newRollback = getRollbackForSessionLocked(packageSession.getSessionId()); if (newRollback == null) { newRollback = createNewRollbackLocked(parentSession); - mRollbacks.add(newRollback); } } newRollback.addToken(token); @@ -1002,11 +1001,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } } - Rollback rollback = completeEnableRollback(newRollback); - if (rollback == null) { + if (!completeEnableRollback(newRollback)) { result.offer(-1); } else { - result.offer(rollback.info.getRollbackId()); + result.offer(newRollback.info.getRollbackId()); } }); @@ -1158,19 +1156,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { Rollback rollback; synchronized (mLock) { rollback = getRollbackForSessionLocked(sessionId); - if (rollback == null || rollback.isStaged() || !rollback.isEnabling() - || !rollback.notifySessionWithSuccess()) { - return; - } - // All child sessions finished with success. We can enable this rollback now. - // TODO: refactor #completeEnableRollback so we won't remove 'rollback' from - // mRollbacks here and add it back in #completeEnableRollback later. - mRollbacks.remove(rollback); } - // TODO: Now #completeEnableRollback returns the same rollback object as the - // parameter on success. It would be more readable to return a boolean to indicate - // success or failure. - if (completeEnableRollback(rollback) != null) { + if (rollback != null && !rollback.isStaged() && rollback.isEnabling() + && rollback.notifySessionWithSuccess() + && completeEnableRollback(rollback)) { makeRollbackAvailable(rollback); } } else { @@ -1188,13 +1177,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } /** - * Add a rollback to the list of rollbacks. It does not make the rollback available yet. + * Persist a rollback as enable-completed. It does not make the rollback available yet. + * This rollback will be deleted and removed from {@link #mRollbacks} should any error happens. * - * @return the Rollback instance for a successfully enable-completed rollback, - * or null on error. + * @return {code true} if {code rollback} is successfully enable-completed, + * or {code false} otherwise. */ @WorkerThread - private Rollback completeEnableRollback(Rollback rollback) { + private boolean completeEnableRollback(Rollback rollback) { if (LOCAL_LOGV) { Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId()); } @@ -1205,26 +1195,24 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // rollback for the embedded apk-in-apex, if any. if (!rollback.allPackagesEnabled()) { Slog.e(TAG, "Failed to enable rollback for all packages in session."); + mRollbacks.remove(rollback); rollback.delete(mAppDataRollbackHelper); - return null; + return false; } + // Note: There is a small window of time between when + // the session has been committed by the package + // manager and when we make the rollback available + // here. Presumably the window is small enough that + // nobody will want to roll back the newly installed + // package before we make the rollback available. + // TODO: We'll lose the rollback if the + // device reboots between when the session is + // committed and this point. Revisit this after + // adding support for rollback of staged installs. rollback.saveRollback(); - synchronized (mLock) { - // Note: There is a small window of time between when - // the session has been committed by the package - // manager and when we make the rollback available - // here. Presumably the window is small enough that - // nobody will want to roll back the newly installed - // package before we make the rollback available. - // TODO: We'll lose the rollback if the - // device reboots between when the session is - // committed and this point. Revisit this after - // adding support for rollback of staged installs. - mRollbacks.add(rollback); - } - return rollback; + return true; } @WorkerThread @@ -1304,6 +1292,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } } + /** + * Creates and returns a Rollback according to the given SessionInfo + * and adds it to {@link #mRollbacks}. + */ @WorkerThread @GuardedBy("mLock") private Rollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) { @@ -1338,6 +1330,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { installerPackageName, packageSessionIds); } + mRollbacks.add(rollback); return rollback; } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index b3edc91a4129..0d365b16e228 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -102,6 +102,11 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { } @Override + public String toString() { + return mName + "@" + System.identityHashCode(this); + } + + @Override public final void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) { final long token = proto.start(fieldId); super.dumpDebug(proto, WINDOW_CONTAINER, logLevel); diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 9e93e1455f2c..0ec0c7b53875 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -16,8 +16,6 @@ package com.android.server.wm; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; - import android.content.res.Resources; import android.text.TextUtils; @@ -43,7 +41,7 @@ public abstract class DisplayAreaPolicy { /** * The Tasks container. Tasks etc. are automatically added to this container. */ - protected final TaskContainers mTaskContainers; + protected final DisplayArea<? extends ActivityStack> mTaskContainers; /** * Construct a new {@link DisplayAreaPolicy} @@ -58,7 +56,8 @@ public abstract class DisplayAreaPolicy { */ protected DisplayAreaPolicy(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, - DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) { + DisplayArea<? extends WindowContainer> imeContainer, + DisplayArea<? extends ActivityStack> taskContainers) { mWmService = wmService; mContent = content; mRoot = root; @@ -83,67 +82,15 @@ public abstract class DisplayAreaPolicy { */ public abstract void addWindow(WindowToken token); - /** - * Default policy that has no special features. - */ - public static class Default extends DisplayAreaPolicy { - - public Default(WindowManagerService wmService, DisplayContent content, - DisplayArea.Root root, + /** Provider for platform-default display area policy. */ + static final class DefaultProvider implements DisplayAreaPolicy.Provider { + @Override + public DisplayAreaPolicy instantiate(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) { - super(wmService, content, root, imeContainer, taskContainers); - } - - private final DisplayArea.Tokens mBelow = new DisplayArea.Tokens(mWmService, - DisplayArea.Type.BELOW_TASKS, "BelowTasks"); - private final DisplayArea<DisplayArea> mAbove = new DisplayArea<>(mWmService, - DisplayArea.Type.ABOVE_TASKS, "AboveTasks"); - private final DisplayArea.Tokens mAboveBelowIme = new DisplayArea.Tokens(mWmService, - DisplayArea.Type.ABOVE_TASKS, "AboveTasksBelowIme"); - private final DisplayArea.Tokens mAboveAboveIme = new DisplayArea.Tokens(mWmService, - DisplayArea.Type.ABOVE_TASKS, "AboveTasksAboveIme"); - - @Override - public void attachDisplayAreas() { - mRoot.addChild(mBelow, 0); - mRoot.addChild(mTaskContainers, 1); - mRoot.addChild(mAbove, 2); - - mAbove.addChild(mAboveBelowIme, 0); - mAbove.addChild(mImeContainer, 1); - mAbove.addChild(mAboveAboveIme, 2); - } - - @Override - public void addWindow(WindowToken token) { - switch (DisplayArea.Type.typeOf(token)) { - case ABOVE_TASKS: - if (token.getWindowLayerFromType() - < mWmService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)) { - mAboveBelowIme.addChild(token); - } else { - mAboveAboveIme.addChild(token); - } - break; - case BELOW_TASKS: - mBelow.addChild(token); - break; - default: - throw new IllegalArgumentException("don't know how to sort " + token); - } - } - - /** Provider for {@link DisplayAreaPolicy.Default platform-default display area policy}. */ - static class Provider implements DisplayAreaPolicy.Provider { - @Override - public DisplayAreaPolicy instantiate(WindowManagerService wmService, - DisplayContent content, DisplayArea.Root root, - DisplayArea<? extends WindowContainer> imeContainer, - TaskContainers taskContainers) { - return new DisplayAreaPolicy.Default(wmService, content, root, imeContainer, - taskContainers); - } + return new DisplayAreaPolicyBuilder() + .build(wmService, content, root, imeContainer, taskContainers); } } @@ -172,7 +119,7 @@ public abstract class DisplayAreaPolicy { String name = res.getString( com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider); if (TextUtils.isEmpty(name)) { - return new DisplayAreaPolicy.Default.Provider(); + return new DisplayAreaPolicy.DefaultProvider(); } try { return (Provider) Class.forName(name).newInstance(); diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java new file mode 100644 index 000000000000..885456a8488c --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -0,0 +1,371 @@ +/* + * 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.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; +import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.policy.WindowManagerPolicy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A builder for instantiating a complex {@link DisplayAreaPolicy} + * + * <p>Given a set of features (that each target a set of window types), it builds the necessary + * DisplayArea hierarchy. + * + * <p>Example: <br /> + * + * <pre> + * // Feature for targeting everything below the magnification overlay: + * new DisplayAreaPolicyBuilder(...) + * .addFeature(new Feature.Builder(..., "Magnification") + * .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY) + * .build()) + * .build(...) + * + * // Builds a policy with the following hierarchy: + * - DisplayArea.Root + * - Magnification + * - DisplayArea.Tokens (Wallpapers are attached here) + * - TaskContainers + * - DisplayArea.Tokens (windows above Tasks up to IME are attached here) + * - ImeContainers + * - DisplayArea.Tokens (windows above IME up to TYPE_ACCESSIBILITY_OVERLAY attached here) + * - DisplayArea.Tokens (TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY and up are attached here) + * + * </pre> + * + * // TODO(display-area): document more complex scenarios where we need multiple areas per feature. + */ +class DisplayAreaPolicyBuilder { + + private final ArrayList<Feature> mFeatures = new ArrayList<>(); + + /** + * A feature that requires {@link DisplayArea DisplayArea(s)}. + */ + static class Feature { + private final String mName; + private final boolean[] mWindowLayers; + + private Feature(String name, boolean[] windowLayers) { + mName = name; + mWindowLayers = windowLayers; + } + + static class Builder { + private final WindowManagerPolicy mPolicy; + private final String mName; + private final boolean[] mLayers; + + /** + * Build a new feature that applies to a set of window types as specified by the builder + * methods. + * + * <p>The set of types is updated iteratively in the order of the method invocations. + * For example, {@code all().except(TYPE_STATUS_BAR)} expresses that a feature should + * apply to all types except TYPE_STATUS_BAR. + * + * The builder starts out with the feature not applying to any types. + * + * @param name the name of the feature. + */ + Builder(WindowManagerPolicy policy, String name) { + mPolicy = policy; + mName = name; + mLayers = new boolean[mPolicy.getMaxWindowLayer()]; + } + + /** + * Set that the feature applies to all window types. + */ + Builder all() { + Arrays.fill(mLayers, true); + return this; + } + + /** + * Set that the feature applies to the given window types. + */ + Builder and(int... types) { + for (int i = 0; i < types.length; i++) { + int type = types[i]; + set(type, true); + } + return this; + } + + /** + * Set that the feature does not apply to the given window types. + */ + Builder except(int... types) { + for (int i = 0; i < types.length; i++) { + int type = types[i]; + set(type, false); + } + return this; + } + + /** + * Set that the feature applies window types that are layerd at or below the layer of + * the given window type. + */ + Builder upTo(int typeInclusive) { + final int max = layerFromType(typeInclusive, false); + for (int i = 0; i < max; i++) { + mLayers[i] = true; + } + set(typeInclusive, true); + return this; + } + + Feature build() { + return new Feature(mName, mLayers.clone()); + } + + private void set(int type, boolean value) { + mLayers[layerFromType(type, true)] = value; + if (type == TYPE_APPLICATION_OVERLAY) { + mLayers[layerFromType(type, true)] = value; + mLayers[layerFromType(TYPE_SYSTEM_ALERT, false)] = value; + mLayers[layerFromType(TYPE_SYSTEM_OVERLAY, false)] = value; + mLayers[layerFromType(TYPE_SYSTEM_ERROR, false)] = value; + } + } + + private int layerFromType(int type, boolean internalWindows) { + return mPolicy.getWindowLayerFromTypeLw(type, internalWindows); + } + } + } + + static class Result extends DisplayAreaPolicy { + private static final int LEAF_TYPE_TASK_CONTAINERS = 1; + private static final int LEAF_TYPE_IME_CONTAINERS = 2; + private static final int LEAF_TYPE_TOKENS = 0; + + private final int mMaxWindowLayer = mWmService.mPolicy.getMaxWindowLayer(); + + private final ArrayList<Feature> mFeatures; + private final Map<Feature, List<DisplayArea<? extends WindowContainer>>> mAreas; + private final DisplayArea.Tokens[] mAreaForLayer = new DisplayArea.Tokens[mMaxWindowLayer]; + + Result(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + DisplayArea<? extends ActivityStack> taskStacks, ArrayList<Feature> features) { + super(wmService, content, root, imeContainer, taskStacks); + mFeatures = features; + mAreas = new HashMap<>(features.size()); + for (int i = 0; i < mFeatures.size(); i++) { + mAreas.put(mFeatures.get(i), new ArrayList<>()); + } + } + + @Override + public void attachDisplayAreas() { + // This method constructs the layer hierarchy with the following properties: + // (1) Every feature maps to a set of DisplayAreas + // (2) After adding a window, for every feature the window's type belongs to, + // it is a descendant of one of the corresponding DisplayAreas of the feature. + // (3) Z-order is maintained, i.e. if z-range(area) denotes the set of layers of windows + // within a DisplayArea: + // for every pair of DisplayArea siblings (a,b), where a is below b, it holds that + // max(z-range(a)) <= min(z-range(b)) + // + // The algorithm below iteratively creates such a hierarchy: + // - Initially, all windows are attached to the root. + // - For each feature we create a set of DisplayAreas, by looping over the layers + // - if the feature does apply to the current layer, we need to find a DisplayArea + // for it to satisfy (2) + // - we can re-use the previous layer's area if: + // the current feature also applies to the previous layer, (to satisfy (3)) + // and the last feature that applied to the previous layer is the same as + // the last feature that applied to the current layer (to satisfy (2)) + // - otherwise we create a new DisplayArea below the last feature that applied + // to the current layer + + + PendingArea[] areaForLayer = new PendingArea[mMaxWindowLayer]; + final PendingArea root = new PendingArea(null, 0, null); + Arrays.fill(areaForLayer, root); + + final int size = mFeatures.size(); + for (int i = 0; i < size; i++) { + PendingArea featureArea = null; + for (int layer = 0; layer < mMaxWindowLayer; layer++) { + final Feature feature = mFeatures.get(i); + if (feature.mWindowLayers[layer]) { + if (featureArea == null || featureArea.mParent != areaForLayer[layer]) { + // No suitable DisplayArea - create a new one under the previous area + // for this layer. + featureArea = new PendingArea(feature, layer, areaForLayer[layer]); + areaForLayer[layer].mChildren.add(featureArea); + } + areaForLayer[layer] = featureArea; + } else { + featureArea = null; + } + } + } + + PendingArea leafArea = null; + int leafType = LEAF_TYPE_TOKENS; + for (int layer = 0; layer < mMaxWindowLayer; layer++) { + int type = typeOfLayer(mWmService.mPolicy, layer); + if (leafArea == null || leafArea.mParent != areaForLayer[layer] + || type != leafType) { + leafArea = new PendingArea(null, layer, areaForLayer[layer]); + areaForLayer[layer].mChildren.add(leafArea); + leafType = type; + if (leafType == LEAF_TYPE_TASK_CONTAINERS) { + leafArea.mExisting = mTaskContainers; + } else if (leafType == LEAF_TYPE_IME_CONTAINERS) { + leafArea.mExisting = mImeContainer; + } + } + leafArea.mMaxLayer = layer; + } + root.computeMaxLayer(); + root.instantiateChildren(mRoot, mAreaForLayer, 0, mAreas); + } + + @Override + public void addWindow(WindowToken token) { + DisplayArea.Tokens area = findAreaForToken(token); + area.addChild(token); + } + + @VisibleForTesting + DisplayArea.Tokens findAreaForToken(WindowToken token) { + int windowLayerFromType = token.getWindowLayerFromType(); + if (windowLayerFromType == APPLICATION_LAYER) { + // TODO(display-area): Better handle AboveAppWindows in APPLICATION_LAYER + windowLayerFromType += 1; + } else if (token.mRoundedCornerOverlay) { + windowLayerFromType = mMaxWindowLayer - 1; + } + return mAreaForLayer[windowLayerFromType]; + } + + public List<DisplayArea<? extends WindowContainer>> getDisplayAreas(Feature feature) { + return mAreas.get(feature); + } + + private static int typeOfLayer(WindowManagerPolicy policy, int layer) { + if (layer == APPLICATION_LAYER) { + return LEAF_TYPE_TASK_CONTAINERS; + } else if (layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD) + || layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD_DIALOG)) { + return LEAF_TYPE_IME_CONTAINERS; + } else { + return LEAF_TYPE_TOKENS; + } + } + } + + DisplayAreaPolicyBuilder addFeature(Feature feature) { + mFeatures.add(feature); + return this; + } + + Result build(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + DisplayArea<? extends ActivityStack> taskContainers) { + + return new Result(wmService, content, root, imeContainer, taskContainers, new ArrayList<>( + mFeatures)); + } + + static class PendingArea { + final int mMinLayer; + final ArrayList<PendingArea> mChildren = new ArrayList<>(); + final Feature mFeature; + final PendingArea mParent; + int mMaxLayer; + DisplayArea mExisting; + + PendingArea(Feature feature, + int minLayer, + PendingArea parent) { + mMinLayer = minLayer; + mFeature = feature; + mParent = parent; + } + + int computeMaxLayer() { + for (int i = 0; i < mChildren.size(); i++) { + mMaxLayer = Math.max(mMaxLayer, mChildren.get(i).computeMaxLayer()); + } + return mMaxLayer; + } + + void instantiateChildren(DisplayArea<DisplayArea> parent, + DisplayArea.Tokens[] areaForLayer, int level, Map<Feature, List<DisplayArea<? + extends WindowContainer>>> areas) { + mChildren.sort(Comparator.comparingInt(pendingArea -> pendingArea.mMinLayer)); + for (int i = 0; i < mChildren.size(); i++) { + final PendingArea child = mChildren.get(i); + final DisplayArea area = child.createArea(parent, areaForLayer); + parent.addChild(area, WindowContainer.POSITION_TOP); + if (mFeature != null) { + areas.get(mFeature).add(area); + } + child.instantiateChildren(area, areaForLayer, level + 1, areas); + } + } + + private DisplayArea createArea(DisplayArea<DisplayArea> parent, + DisplayArea.Tokens[] areaForLayer) { + if (mExisting != null) { + return mExisting; + } + DisplayArea.Type type; + if (mMinLayer > APPLICATION_LAYER) { + type = DisplayArea.Type.ABOVE_TASKS; + } else if (mMaxLayer < APPLICATION_LAYER) { + type = DisplayArea.Type.BELOW_TASKS; + } else { + type = DisplayArea.Type.ANY; + } + if (mFeature == null) { + final DisplayArea.Tokens leaf = new DisplayArea.Tokens(parent.mWmService, type, + "Leaf:" + mMinLayer + ":" + mMaxLayer); + for (int i = mMinLayer; i <= mMaxLayer; i++) { + areaForLayer[i] = leaf; + } + return leaf; + } else { + return new DisplayArea(parent.mWmService, type, mFeature.mName + ":" + + mMinLayer + ":" + mMaxLayer); + } + } + } +} diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index ca6bd2d1e3b4..38b0ca8dcccd 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.PrintWriterPrinter; 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 @@ public class DisplayPolicy { 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 @@ public class DisplayPolicy { && mLastBehavior == behavior && mLastFocusIsFullscreen == isFullscreen && mLastFocusIsImmersive == isImmersive - && mFocusedApp == win.getAppToken() && mLastNonDockedStackBounds.equals(mNonDockedStackBounds) && mLastDockedStackBounds.equals(mDockedStackBounds)) { return 0; @@ -3304,7 +3300,6 @@ public class DisplayPolicy { 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 @@ public class DisplayPolicy { 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/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2196d899406d..e3d85c84b50c 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2008,10 +2008,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getStackAt(stackNdx); stack.switchUser(userId); - Task task = stack.getTopMostTask(); - if (task != null && task != stack) { - stack.positionChildAtTop(task); - } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8f9caea26534..2bb67035f44b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7317,9 +7317,11 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG_WM, "updateInputMethodTargetWindow: imeToken=" + imeToken + " imeTargetWindowToken=" + imeTargetWindowToken); } - final WindowState imeTarget = mWindowMap.get(imeTargetWindowToken); - if (imeTarget != null) { - imeTarget.getDisplayContent().updateImeControlTarget(imeTarget); + synchronized (mGlobalLock) { + final WindowState imeTarget = mWindowMap.get(imeTargetWindowToken); + if (imeTarget != null) { + imeTarget.getDisplayContent().updateImeControlTarget(imeTarget); + } } } diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp index 7e6f79f0d407..70a9c09c815d 100644 --- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp +++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp @@ -18,18 +18,27 @@ #define LOG_TAG "PackageManagerShellCommandDataLoader-jni" #include <android-base/logging.h> +#include <android-base/file.h> +#include <android-base/stringprintf.h> #include <android-base/unique_fd.h> -#include <nativehelper/JNIHelp.h> -#include "android-base/file.h" +#include <cutils/trace.h> +#include <sys/eventfd.h> +#include <sys/poll.h> -#include <endian.h> +#include <nativehelper/JNIHelp.h> #include <core_jni_helpers.h> +#include <endian.h> #include "dataloader.h" +#include <charconv> #include <chrono> +#include <span> +#include <string> #include <thread> +#include <unordered_map> +#include <unordered_set> namespace android { @@ -39,9 +48,26 @@ using android::base::borrowed_fd; using android::base::ReadFully; using android::base::unique_fd; +using namespace std::literals; + +using BlockSize = int16_t; +using FileIdx = int16_t; +using BlockIdx = int32_t; +using NumBlocks = int32_t; +using CompressionType = int16_t; +using RequestType = int16_t; +using MagicType = uint32_t; + static constexpr int BUFFER_SIZE = 256 * 1024; static constexpr int BLOCKS_COUNT = BUFFER_SIZE / INCFS_DATA_FILE_BLOCK_SIZE; +static constexpr int COMMAND_SIZE = 4 + 2 + 2 + 4; // bytes +static constexpr int HEADER_SIZE = 2 + 2 + 4 + 2; // bytes +static constexpr std::string_view OKAY = "OKAY"sv; +static constexpr MagicType INCR = 0x52434e49; // BE INCR + +static constexpr auto PollTimeoutMs = 5000; + struct JniIds { jclass packageManagerShellCommandDataLoader; jmethodID pmscdLookupShellCommand; @@ -85,6 +111,70 @@ const JniIds& jniIds(JNIEnv* env) { return ids; } +struct BlockHeader { + FileIdx fileIdx = -1; + CompressionType compressionType = -1; + BlockIdx blockIdx = -1; + BlockSize blockSize = -1; +} __attribute__((packed)); + +static_assert(sizeof(BlockHeader) == HEADER_SIZE); + +static constexpr RequestType EXIT = 0; +static constexpr RequestType BLOCK_MISSING = 1; +static constexpr RequestType PREFETCH = 2; + +struct RequestCommand { + MagicType magic; + RequestType requestType; + FileIdx fileIdx; + BlockIdx blockIdx; +} __attribute__((packed)); + +static_assert(COMMAND_SIZE == sizeof(RequestCommand)); + +static bool sendRequest(int fd, RequestType requestType, FileIdx fileIdx = -1, + BlockIdx blockIdx = -1) { + const RequestCommand command{.magic = INCR, + .requestType = static_cast<int16_t>(be16toh(requestType)), + .fileIdx = static_cast<int16_t>(be16toh(fileIdx)), + .blockIdx = static_cast<int32_t>(be32toh(blockIdx))}; + return android::base::WriteFully(fd, &command, sizeof(command)); +} + +static int waitForDataOrSignal(int fd, int event_fd) { + struct pollfd pfds[2] = {{fd, POLLIN, 0}, {event_fd, POLLIN, 0}}; + // Wait indefinitely until either data is ready or stop signal is received + int res = poll(pfds, 2, PollTimeoutMs); + if (res <= 0) { + return res; + } + // First check if there is a stop signal + if (pfds[1].revents == POLLIN) { + return event_fd; + } + // Otherwise check if incoming data is ready + if (pfds[0].revents == POLLIN) { + return fd; + } + return -1; +} + +static bool readChunk(int fd, std::vector<uint8_t>& data) { + int32_t size; + if (!android::base::ReadFully(fd, &size, sizeof(size))) { + return false; + } + size = int32_t(be32toh(size)); + if (size <= 0) { + return false; + } + data.resize(size); + return android::base::ReadFully(fd, data.data(), data.size()); +} + +BlockHeader readHeader(std::span<uint8_t>& data); + static inline int32_t readBEInt32(borrowed_fd fd) { int32_t result; ReadFully(fd, &result, sizeof(result)); @@ -106,6 +196,22 @@ static inline int32_t skipIdSigHeaders(borrowed_fd fd) { return readBEInt32(fd); // size of the verity tree } +static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) { + constexpr int SHA256_DIGEST_SIZE = 32; + constexpr int digest_size = SHA256_DIGEST_SIZE; + constexpr int hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / digest_size; + + IncFsSize total_tree_block_count = 0; + + auto block_count = 1 + (fileSize - 1) / INCFS_DATA_FILE_BLOCK_SIZE; + auto hash_block_count = block_count; + for (auto i = 0; hash_block_count > 1; i++) { + hash_block_count = (hash_block_count + hash_per_block - 1) / hash_per_block; + total_tree_block_count += hash_block_count; + } + return total_tree_block_count * INCFS_DATA_FILE_BLOCK_SIZE; +} + static inline unique_fd convertPfdToFdAndDup(JNIEnv* env, const JniIds& jni, jobject pfd) { if (!pfd) { ALOGE("Missing In ParcelFileDescriptor."); @@ -125,8 +231,9 @@ static inline unique_fd convertPfdToFdAndDup(JNIEnv* env, const JniIds& jni, job struct InputDesc { unique_fd fd; IncFsSize size; - IncFsBlockKind kind; - bool waitOnEof; + IncFsBlockKind kind = INCFS_BLOCK_KIND_DATA; + bool waitOnEof = false; + bool streaming = false; }; using InputDescs = std::vector<InputDesc>; @@ -135,8 +242,7 @@ static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shel InputDescs result; result.reserve(2); - const bool fromStdin = (metadata.size == 0 || *metadata.data == '-'); - if (fromStdin) { + if (metadata.size == 0 || *metadata.data == '-') { // stdin auto fd = convertPfdToFdAndDup( env, jni, @@ -146,12 +252,29 @@ static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shel result.push_back(InputDesc{ .fd = std::move(fd), .size = size, - .kind = INCFS_BLOCK_KIND_DATA, .waitOnEof = true, }); } return result; } + if (*metadata.data == '+') { + // verity tree from stdin, rest is streaming + auto fd = convertPfdToFdAndDup( + env, jni, + env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader, + jni.pmscdGetStdInPFD, shellCommand)); + if (fd.ok()) { + auto treeSize = verityTreeSizeForFile(size); + result.push_back(InputDesc{ + .fd = std::move(fd), + .size = treeSize, + .kind = INCFS_BLOCK_KIND_HASH, + .waitOnEof = true, + .streaming = true, + }); + } + return result; + } // local file and possibly signature const std::string filePath(metadata.data, metadata.size); @@ -163,13 +286,17 @@ static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shel jni.pmscdGetLocalFile, shellCommand, env->NewStringUTF(idsigPath.c_str()))); if (idsigFd.ok()) { - ALOGE("idsig found, skipping to the tree"); - auto treeSize = skipIdSigHeaders(idsigFd); + auto treeSize = verityTreeSizeForFile(size); + auto actualTreeSize = skipIdSigHeaders(idsigFd); + if (treeSize != actualTreeSize) { + ALOGE("Verity tree size mismatch: %d vs .idsig: %d.", int(treeSize), + int(actualTreeSize)); + return {}; + } result.push_back(InputDesc{ .fd = std::move(idsigFd), .size = treeSize, .kind = INCFS_BLOCK_KIND_HASH, - .waitOnEof = false, }); } @@ -182,8 +309,6 @@ static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shel result.push_back(InputDesc{ .fd = std::move(fileFd), .size = size, - .kind = INCFS_BLOCK_KIND_DATA, - .waitOnEof = false, }); } @@ -226,19 +351,32 @@ private: android::dataloader::StatusListenerPtr statusListener, android::dataloader::ServiceConnectorPtr, android::dataloader::ServiceParamsPtr) final { + CHECK(ifs) << "ifs can't be null"; + CHECK(statusListener) << "statusListener can't be null"; mArgs = params.arguments(); mIfs = ifs; + mStatusListener = statusListener; return true; } bool onStart() final { return true; } - void onStop() final {} - void onDestroy() final {} - - // IFS callbacks. - void onPendingReads(const dataloader::PendingReads& pendingReads) final {} - void onPageReads(const dataloader::PageReads& pageReads) final {} + void onStop() final { + mStopReceiving = true; + eventfd_write(mEventFd, 1); + if (mReceiverThread.joinable()) { + mReceiverThread.join(); + } + } + void onDestroy() final { + ALOGE("Sending EXIT to server."); + sendRequest(mOutFd, EXIT); + // Make sure the receiver thread stopped. + CHECK(!mReceiverThread.joinable()); + + mInFd.reset(); + mOutFd.reset(); + } - // FS callbacks. + // Installation. bool onPrepareImage(const dataloader::DataLoaderInstallationFiles& addedFiles) final { JNIEnv* env = GetOrAttachJNIEnvironment(mJvm); const auto& jni = jniIds(env); @@ -257,6 +395,7 @@ private: std::vector<IncFsDataBlock> blocks; blocks.reserve(BLOCKS_COUNT); + unique_fd streamingFd; for (auto&& file : addedFiles) { auto inputs = openInputs(env, jni, shellCommand, file.size, file.metadata); if (inputs.empty()) { @@ -267,7 +406,6 @@ private: } const auto fileId = IncFs_FileIdFromMetadata(file.metadata); - const auto incfsFd(mIfs->openWrite(fileId)); if (incfsFd < 0) { ALOGE("Failed to open an IncFS file for metadata: %.*s, final file name is: %s. " @@ -277,6 +415,9 @@ private: } for (auto&& input : inputs) { + if (input.streaming && !streamingFd.ok()) { + streamingFd.reset(dup(input.fd)); + } if (!copyToIncFs(incfsFd, input.size, input.kind, input.fd, input.waitOnEof, &buffer, &blocks)) { ALOGE("Failed to copy data to IncFS file for metadata: %.*s, final file name " @@ -288,7 +429,12 @@ private: } } - ALOGE("All done."); + if (streamingFd.ok()) { + ALOGE("onPrepareImage: done, proceeding to streaming."); + return initStreaming(std::move(streamingFd)); + } + + ALOGE("onPrepareImage: done."); return true; } @@ -378,11 +524,253 @@ private: return true; } + // Read tracing. + struct TracedRead { + uint64_t timestampUs; + android::dataloader::FileId fileId; + uint32_t firstBlockIdx; + uint32_t count; + }; + + void onPageReads(const android::dataloader::PageReads& pageReads) final { + auto trace = atrace_is_tag_enabled(ATRACE_TAG); + if (CC_LIKELY(!trace)) { + return; + } + + TracedRead last = {}; + for (auto&& read : pageReads) { + if (read.id != last.fileId || read.block != last.firstBlockIdx + last.count) { + traceRead(last); + last = TracedRead{ + .timestampUs = read.bootClockTsUs, + .fileId = read.id, + .firstBlockIdx = (uint32_t)read.block, + .count = 1, + }; + } else { + ++last.count; + } + } + traceRead(last); + } + + void traceRead(const TracedRead& read) { + if (!read.count) { + return; + } + + FileIdx fileIdx = convertFileIdToFileIndex(read.fileId); + auto str = android::base::StringPrintf("page_read: index=%lld count=%lld file=%d", + static_cast<long long>(read.firstBlockIdx), + static_cast<long long>(read.count), + static_cast<int>(fileIdx)); + ATRACE_BEGIN(str.c_str()); + ATRACE_END(); + } + + // Streaming. + bool initStreaming(unique_fd inout) { + mInFd.reset(dup(inout)); + mOutFd.reset(dup(inout)); + if (mInFd < 0 || mOutFd < 0) { + ALOGE("Failed to dup FDs."); + return false; + } + + mEventFd.reset(eventfd(0, EFD_CLOEXEC)); + if (mEventFd < 0) { + ALOGE("Failed to create eventfd."); + return false; + } + + // Awaiting adb handshake. + char okay_buf[OKAY.size()]; + if (!android::base::ReadFully(mInFd, okay_buf, OKAY.size())) { + ALOGE("Failed to receive OKAY. Abort."); + return false; + } + if (std::string_view(okay_buf, OKAY.size()) != OKAY) { + ALOGE("Received '%.*s', expecting '%.*s'", (int)OKAY.size(), okay_buf, (int)OKAY.size(), + OKAY.data()); + return false; + } + + mReceiverThread = std::thread([this]() { receiver(); }); + ALOGI("Started streaming..."); + return true; + } + + // IFS callbacks. + void onPendingReads(const dataloader::PendingReads& pendingReads) final { + CHECK(mIfs); + for (auto&& pendingRead : pendingReads) { + const android::dataloader::FileId& fileId = pendingRead.id; + const auto blockIdx = static_cast<BlockIdx>(pendingRead.block); + /* + ALOGI("Missing: %d", (int) blockIdx); + */ + FileIdx fileIdx = convertFileIdToFileIndex(fileId); + if (fileIdx < 0) { + ALOGE("Failed to handle event for fileid=%s. Ignore.", + android::incfs::toString(fileId).c_str()); + continue; + } + if (mRequestedFiles.insert(fileIdx).second) { + if (!sendRequest(mOutFd, PREFETCH, fileIdx, blockIdx)) { + ALOGE("Failed to request prefetch for fileid=%s. Ignore.", + android::incfs::toString(fileId).c_str()); + mRequestedFiles.erase(fileIdx); + mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION); + } + } + sendRequest(mOutFd, BLOCK_MISSING, fileIdx, blockIdx); + } + } + + void receiver() { + std::vector<uint8_t> data; + std::vector<IncFsDataBlock> instructions; + std::unordered_map<FileIdx, unique_fd> writeFds; + while (!mStopReceiving) { + const int res = waitForDataOrSignal(mInFd, mEventFd); + if (res == 0) { + continue; + } + if (res < 0) { + ALOGE("Failed to poll. Abort."); + mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION); + break; + } + if (res == mEventFd) { + ALOGE("Received stop signal. Exit."); + break; + } + if (!readChunk(mInFd, data)) { + ALOGE("Failed to read a message. Abort."); + mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION); + break; + } + auto remainingData = std::span(data); + while (!remainingData.empty()) { + auto header = readHeader(remainingData); + if (header.fileIdx == -1 && header.compressionType == 0 && header.blockIdx == 0 && + header.blockSize == 0) { + ALOGI("Stop signal received. Sending exit command (remaining bytes: %d).", + int(remainingData.size())); + + sendRequest(mOutFd, EXIT); + mStopReceiving = true; + break; + } + if (header.fileIdx < 0 || header.blockSize <= 0 || header.compressionType < 0 || + header.blockIdx < 0) { + ALOGE("invalid header received. Abort."); + mStopReceiving = true; + break; + } + const FileIdx fileIdx = header.fileIdx; + const android::dataloader::FileId fileId = convertFileIndexToFileId(fileIdx); + if (!android::incfs::isValidFileId(fileId)) { + ALOGE("Unknown data destination for file ID %d. " + "Ignore.", + header.fileIdx); + continue; + } + + auto& writeFd = writeFds[fileIdx]; + if (writeFd < 0) { + writeFd = this->mIfs->openWrite(fileId); + if (writeFd < 0) { + ALOGE("Failed to open file %d for writing (%d). Aboring.", header.fileIdx, + -writeFd); + break; + } + } + + const auto inst = IncFsDataBlock{ + .fileFd = writeFd, + .pageIndex = static_cast<IncFsBlockIndex>(header.blockIdx), + .compression = static_cast<IncFsCompressionKind>(header.compressionType), + .kind = INCFS_BLOCK_KIND_DATA, + .dataSize = static_cast<uint16_t>(header.blockSize), + .data = (const char*)remainingData.data(), + }; + instructions.push_back(inst); + remainingData = remainingData.subspan(header.blockSize); + } + writeInstructions(instructions); + } + writeInstructions(instructions); + } + + void writeInstructions(std::vector<IncFsDataBlock>& instructions) { + auto res = this->mIfs->writeBlocks(instructions); + if (res != instructions.size()) { + ALOGE("Dailed to write data to Incfs (res=%d when expecting %d)", res, + int(instructions.size())); + } + instructions.clear(); + } + + FileIdx convertFileIdToFileIndex(android::dataloader::FileId fileId) { + // FileId is a string in format '+FileIdx\0'. + const char* meta = (const char*)&fileId; + if (*meta != '+') { + return -1; + } + + int fileIdx; + auto res = std::from_chars(meta + 1, meta + sizeof(fileId), fileIdx); + if (res.ec != std::errc{} || fileIdx < std::numeric_limits<FileIdx>::min() || + fileIdx > std::numeric_limits<FileIdx>::max()) { + return -1; + } + + return FileIdx(fileIdx); + } + + android::dataloader::FileId convertFileIndexToFileId(FileIdx fileIdx) { + IncFsFileId fileId = {}; + char* meta = (char*)&fileId; + *meta = '+'; + if (auto [p, ec] = std::to_chars(meta + 1, meta + sizeof(fileId), fileIdx); + ec != std::errc()) { + return {}; + } + return fileId; + } + JavaVM* const mJvm; std::string mArgs; - android::dataloader::FilesystemConnectorPtr mIfs; + android::dataloader::FilesystemConnectorPtr mIfs = nullptr; + android::dataloader::StatusListenerPtr mStatusListener = nullptr; + android::base::unique_fd mInFd; + android::base::unique_fd mOutFd; + android::base::unique_fd mEventFd; + std::thread mReceiverThread; + std::atomic<bool> mStopReceiving = false; + /** Tracks which files have been requested */ + std::unordered_set<FileIdx> mRequestedFiles; }; +BlockHeader readHeader(std::span<uint8_t>& data) { + BlockHeader header; + if (data.size() < sizeof(header)) { + return header; + } + + header.fileIdx = static_cast<FileIdx>(be16toh(*reinterpret_cast<const uint16_t*>(&data[0]))); + header.compressionType = + static_cast<CompressionType>(be16toh(*reinterpret_cast<const uint16_t*>(&data[2]))); + header.blockIdx = static_cast<BlockIdx>(be32toh(*reinterpret_cast<const uint32_t*>(&data[4]))); + header.blockSize = + static_cast<BlockSize>(be16toh(*reinterpret_cast<const uint16_t*>(&data[8]))); + data = data.subspan(sizeof(header)); + + return header; +} + static void nativeInitialize(JNIEnv* env, jclass klass) { jniIds(env); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d2ec4360946b..aa5dafce0450 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.LEAVE_ALL_SYSTEM_APPS_ENABLE 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void setLockdownAdminConfiguredNetworks(ComponentName who, boolean lockdown) { + public void setConfiguredNetworksLockdownState(ComponentName who, boolean lockdown) { if (!mHasFeature) { return; } @@ -11572,7 +11583,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean isLockdownAdminConfiguredNetworks(ComponentName who) { + public boolean hasLockdownAdminConfiguredNetworks(ComponentName who) { if (!mHasFeature) { return false; } @@ -15551,6 +15562,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderWithCleanCallingIdentity( () -> applyPersonalAppsSuspension(callingUserId, suspended)); + + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PERSONAL_APPS_SUSPENDED) + .setAdmin(who) + .setBoolean(suspended) + .write(); } /** @@ -15722,9 +15739,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index c8673f833e71..a904b42f47db 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 @@ public class DataManager { 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 644c155b4158..fba4f4e6a89c 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 @@ class UsageStatsQueryHelper { */ boolean querySince(long sinceTime) { UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser( - mUserId, sinceTime, System.currentTimeMillis(), false, false, false); + mUserId, sinceTime, System.currentTimeMillis(), UsageEvents.SHOW_ALL_EVENT_DATA); if (usageEvents == null) { return false; } diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk Binary files differnew file mode 100644 index 000000000000..9161d869f3a1 --- /dev/null +++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk 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 3f74681cbe1d..8f70ccaaa9ba 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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/hdmi/HdmiControlServiceStaticTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java new file mode 100644 index 000000000000..607cd816d7dd --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceStaticTest.java @@ -0,0 +1,67 @@ +/* + * 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.hdmi; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Locale; + +/** + * Tests for static methods of {@link HdmiControlService} class. + */ +@SmallTest +@RunWith(JUnit4.class) +public class HdmiControlServiceStaticTest { + + @Test + public void localToMenuLanguage_english() { + assertThat(HdmiControlService.localeToMenuLanguage(Locale.ENGLISH)).isEqualTo("eng"); + } + + @Test + public void localToMenuLanguage_german() { + assertThat(HdmiControlService.localeToMenuLanguage(Locale.GERMAN)).isEqualTo("ger"); + } + + @Test + public void localToMenuLanguage_taiwan() { + assertThat(HdmiControlService.localeToMenuLanguage(Locale.TAIWAN)).isEqualTo("chi"); + } + + @Test + public void localToMenuLanguage_macau() { + assertThat(HdmiControlService.localeToMenuLanguage(new Locale("zh", "MO"))).isEqualTo( + "chi"); + } + + @Test + public void localToMenuLanguage_hongkong() { + assertThat(HdmiControlService.localeToMenuLanguage(new Locale("zh", "HK"))).isEqualTo( + "chi"); + } + + @Test + public void localToMenuLanguage_chinese() { + assertThat(HdmiControlService.localeToMenuLanguage(Locale.CHINESE)).isEqualTo("zho"); + } + +} 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 8dae48cafd7b..0d4c6e82d951 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 @@ package com.android.server.integrity; 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.spy; 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.engine.RuleEvaluationEngine; 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 org.mockito.junit.MockitoRule; 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 @@ public class AppIntegrityManagerServiceImplTest { 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 @@ public class AppIntegrityManagerServiceImplTest { 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 @@ public class AppIntegrityManagerServiceImplTest { private PackageManager mSpyPackageManager; private File mTestApk; + private File mTestApkTwoCerts; private final Context mRealContext = InstrumentationRegistry.getTargetContext(); // under test @@ -136,6 +150,10 @@ public class AppIntegrityManagerServiceImplTest { @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 @@ public class AppIntegrityManagerServiceImplTest { 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 @@ public class AppIntegrityManagerServiceImplTest { 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 @@ public class AppIntegrityManagerServiceImplTest { 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 @@ public class AppIntegrityManagerServiceImplTest { 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 @@ public class AppIntegrityManagerServiceImplTest { 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 @@ public class AppIntegrityManagerServiceImplTest { 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 @@ public class AppIntegrityManagerServiceImplTest { 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 b0b9596829f1..0488745c2434 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.ArgumentMatchers.any; 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.junit.runners.JUnit4; 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 @@ public class RuleEvaluationEngineTest { 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 @@ public class RuleEvaluationEngineTest { .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 @@ public class RuleEvaluationEngineTest { 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 @@ public class RuleEvaluationEngineTest { 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 498d8886eec3..6769faaa4c5d 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 @@ public final class DataManagerTest { 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 01d9dc00cf47..c8543c4d5383 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 @@ -19,9 +19,9 @@ package com.android.server.people.data; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import android.annotation.NonNull; @@ -189,7 +189,7 @@ public final class UsageStatsQueryHelperTest { private void addUsageEvents(UsageEvents.Event... events) { UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{}); when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(), - anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(usageEvents); + eq(UsageEvents.SHOW_ALL_EVENT_DATA))).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 e768205c2cf4..9e577636c1b3 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 @@ public class AppStandbyControllerTests { 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 @@ public class AppStandbyControllerTests { } @Override + boolean isCharging() { + return mIsCharging; + } + + @Override boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException { return mPowerSaveWhitelistExceptIdle.contains(packageName); } @@ -281,6 +287,13 @@ public class AppStandbyControllerTests { } 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 @@ public class AppStandbyControllerTests { 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 @@ public class AppStandbyControllerTests { 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 f5dbf43cdfd6..98572d4df369 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 a0f7f5b801ec..bc33f0824214 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -51,8 +51,6 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; -import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; -import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -1144,14 +1142,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testEnqueueNotificationWithTag_WritesExpectedLog() throws Exception { + public void testEnqueueNotificationWithTag_WritesExpectedLogs() throws Exception { final String tag = "testEnqueueNotificationWithTag_WritesExpectedLog"; mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, generateNotificationRecord(null).getNotification(), 0); waitForIdle(); assertEquals(1, mNotificationRecordLogger.getCalls().size()); + NotificationRecordLoggerFake.CallRecord call = mNotificationRecordLogger.get(0); - assertTrue(call.shouldLog); + assertTrue(call.shouldLogReported); assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, call.event); assertNotNull(call.r); @@ -1161,7 +1160,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(PKG, call.r.getSbn().getPackageName()); assertEquals(0, call.r.getSbn().getId()); assertEquals(tag, call.r.getSbn().getTag()); - assertNotNull(call.r.getSbn().getInstanceId()); assertEquals(0, call.getInstanceId()); // Fake instance IDs are assigned in order } @@ -1180,13 +1178,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); assertEquals(2, mNotificationRecordLogger.getCalls().size()); - assertTrue(mNotificationRecordLogger.get(0).shouldLog); + assertTrue(mNotificationRecordLogger.get(0).shouldLogReported); assertEquals( NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, mNotificationRecordLogger.get(0).event); assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); - assertTrue(mNotificationRecordLogger.get(1).shouldLog); + assertTrue(mNotificationRecordLogger.get(1).shouldLogReported); assertEquals( NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED, mNotificationRecordLogger.get(1).event); @@ -1195,16 +1193,37 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdates() throws Exception { - final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdates"; + public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate() throws Exception { + final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate"; mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, generateNotificationRecord(null).getNotification(), 0); mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, generateNotificationRecord(null).getNotification(), 0); waitForIdle(); assertEquals(2, mNotificationRecordLogger.getCalls().size()); - assertTrue(mNotificationRecordLogger.get(0).shouldLog); - assertFalse(mNotificationRecordLogger.get(1).shouldLog); + assertTrue(mNotificationRecordLogger.get(0).shouldLogReported); + assertEquals( + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + mNotificationRecordLogger.get(0).event); + assertFalse(mNotificationRecordLogger.get(1).shouldLogReported); + assertNull(mNotificationRecordLogger.get(1).event); + } + + @Test + public void testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate() throws Exception { + final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate"; + mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, + generateNotificationRecord(null).getNotification(), + 0); + final Notification notif = generateNotificationRecord(null).getNotification(); + notif.extras.putString(Notification.EXTRA_TITLE, "Changed title"); + mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, 0); + waitForIdle(); + assertEquals(2, mNotificationRecordLogger.getCalls().size()); + assertEquals( + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + mNotificationRecordLogger.get(0).event); + assertNull(mNotificationRecordLogger.get(1).event); } @Test @@ -1224,20 +1243,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals( NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, mNotificationRecordLogger.get(0).event); - assertTrue(mNotificationRecordLogger.get(0).shouldLog); + assertTrue(mNotificationRecordLogger.get(0).shouldLogReported); assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); - assertEquals(REASON_APP_CANCEL, mNotificationRecordLogger.get(1).reason); assertEquals( NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_APP_CANCEL, mNotificationRecordLogger.get(1).event); - assertTrue(mNotificationRecordLogger.get(1).shouldLog); assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId()); assertEquals( NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, mNotificationRecordLogger.get(2).event); - assertTrue(mNotificationRecordLogger.get(2).shouldLog); + assertTrue(mNotificationRecordLogger.get(2).shouldLogReported); // New instance ID because notification was canceled before re-post assertEquals(1, mNotificationRecordLogger.get(2).getInstanceId()); } @@ -2605,6 +2622,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testSystemNotificationListenerCanUnsnooze() throws Exception { + final NotificationRecord nr = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "testSystemNotificationListenerCanUnsnooze", + nr.getSbn().getId(), nr.getSbn().getNotification(), + nr.getSbn().getUserId()); + waitForIdle(); + NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = + mService.new SnoozeNotificationRunnable( + nr.getKey(), 100, null); + snoozeNotificationRunnable.run(); + + ManagedServices.ManagedServiceInfo listener = mListeners.new ManagedServiceInfo( + null, new ComponentName(PKG, "test_class"), mUid, true, null, 0); + listener.isSystem = true; + when(mListeners.checkServiceTokenLocked(any())).thenReturn(listener); + + mBinderService.unsnoozeNotificationFromSystemListener(null, nr.getKey()); + waitForIdle(); + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifs.length); + assertNotNull(notifs[0].getKey());//mService.getNotificationRecord(nr.getSbn().getKey())); + } + + @Test public void testSetListenerAccessForUser() throws Exception { UserHandle user = UserHandle.of(10); ComponentName c = ComponentName.unflattenFromString("package/Component"); @@ -3396,11 +3440,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // so we only get the cancel notification. assertEquals(1, mNotificationRecordLogger.getCalls().size()); - assertEquals(REASON_CANCEL, mNotificationRecordLogger.get(0).reason); assertEquals( NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_AOD, mNotificationRecordLogger.get(0).event); - assertTrue(mNotificationRecordLogger.get(0).shouldLog); assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); } @@ -4326,6 +4368,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testOnNotificationVisibilityChanged_triggersVisibilityLog() { + final NotificationRecord r = generateNotificationRecord( + mTestNotificationChannel, 1, null, true); + r.setTextChanged(true); + mService.addNotification(r); + + mService.mNotificationDelegate.onNotificationVisibilityChanged(new NotificationVisibility[] + {NotificationVisibility.obtain(r.getKey(), 1, 1, true)}, + new NotificationVisibility[]{}); + + assertEquals(1, mNotificationRecordLogger.getCalls().size()); + assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_OPEN, + mNotificationRecordLogger.get(0).event); + assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); + + mService.mNotificationDelegate.onNotificationVisibilityChanged( + new NotificationVisibility[]{}, + new NotificationVisibility[] + {NotificationVisibility.obtain(r.getKey(), 1, 1, true)} + ); + + assertEquals(2, mNotificationRecordLogger.getCalls().size()); + assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLOSE, + mNotificationRecordLogger.get(1).event); + assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId()); + } + + @Test public void testSetNotificationsShownFromListener_triggersInterruptionUsageStat() throws RemoteException { final NotificationRecord r = generateNotificationRecord( @@ -5373,21 +5443,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 +5469,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 +5518,25 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 +6194,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java index b120dbee03c5..2a17bae57c8e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java @@ -26,24 +26,26 @@ import java.util.List; */ class NotificationRecordLoggerFake implements NotificationRecordLogger { static class CallRecord extends NotificationRecordPair { - static final int INVALID = -1; - public int position = INVALID, buzzBeepBlink = INVALID, reason = INVALID; - public boolean shouldLog; public UiEventLogger.UiEventEnum event; + + // The following fields are only relevant to maybeLogNotificationPosted() calls. + static final int INVALID = -1; + public int position = INVALID, buzzBeepBlink = INVALID; + public boolean shouldLogReported; + CallRecord(NotificationRecord r, NotificationRecord old, int position, int buzzBeepBlink) { super(r, old); - this.position = position; this.buzzBeepBlink = buzzBeepBlink; - shouldLog = shouldLog(buzzBeepBlink); - event = NotificationReportedEvent.fromRecordPair(this); + shouldLogReported = shouldLogReported(buzzBeepBlink); + event = shouldLogReported ? NotificationReportedEvent.fromRecordPair(this) : null; } - CallRecord(NotificationRecord r, int reason, int dismissalSurface) { + + CallRecord(NotificationRecord r, UiEventLogger.UiEventEnum event) { super(r, null); - this.reason = reason; - shouldLog = true; - event = NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface); + shouldLogReported = false; + this.event = event; } } private List<CallRecord> mCalls = new ArrayList<>(); @@ -57,14 +59,19 @@ class NotificationRecordLoggerFake implements NotificationRecordLogger { } @Override - public void logNotificationReported(NotificationRecord r, NotificationRecord old, + public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old, int position, int buzzBeepBlink) { mCalls.add(new CallRecord(r, old, position, buzzBeepBlink)); } @Override public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) { - mCalls.add(new CallRecord(r, reason, dismissalSurface)); + mCalls.add(new CallRecord(r, + NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface))); } + @Override + public void logNotificationVisibility(NotificationRecord r, boolean visible) { + mCalls.add(new CallRecord(r, NotificationEvent.fromVisibility(visible))); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index 3186d539b817..1dd0b1a6f359 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -15,15 +15,18 @@ */ package com.android.server.notification; +import static com.android.server.notification.SnoozeHelper.EXTRA_KEY; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -322,8 +325,12 @@ public class SnoozeHelperTest extends UiServiceTestCase { mSnoozeHelper.snooze(r, 1000); NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL); mSnoozeHelper.snooze(r2, 1000); + reset(mAm); mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM); verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r); + ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class); + verify(mAm).cancel(captor.capture()); + assertEquals(r.getKey(), captor.getValue().getIntent().getStringExtra(EXTRA_KEY)); } @Test @@ -332,8 +339,10 @@ public class SnoozeHelperTest extends UiServiceTestCase { mSnoozeHelper.snooze(r, 1000); NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL); mSnoozeHelper.snooze(r2, 1000); + reset(mAm); mSnoozeHelper.repost(r.getKey()); verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r); + verify(mAm).cancel(any(PendingIntent.class)); } @Test @@ -370,31 +379,7 @@ public class SnoozeHelperTest extends UiServiceTestCase { } @Test - public void testGetSnoozedByUser() throws Exception { - NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); - NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); - NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM); - NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT); - mSnoozeHelper.snooze(r, 1000); - mSnoozeHelper.snooze(r2, 1000); - mSnoozeHelper.snooze(r3, 1000); - mSnoozeHelper.snooze(r4, 1000); - IntArray profileIds = new IntArray(); - profileIds.add(UserHandle.USER_SYSTEM); - when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); - assertEquals(3, mSnoozeHelper.getSnoozed().size()); - profileIds = new IntArray(); - profileIds.add(UserHandle.USER_CURRENT); - when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); - assertEquals(1, mSnoozeHelper.getSnoozed().size()); - } - - @Test - public void testGetSnoozedByUser_managedProfiles() throws Exception { - IntArray profileIds = new IntArray(); - profileIds.add(UserHandle.USER_CURRENT); - profileIds.add(UserHandle.USER_SYSTEM); - when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); + public void testGetSnoozedBy() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java new file mode 100644 index 000000000000..8ac1d24333be --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -0,0 +1,214 @@ +/* + * 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.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; + +import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS; +import static com.android.server.wm.DisplayArea.Type.ANY; +import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import static java.util.stream.Collectors.toList; + +import android.platform.test.annotations.Presubmit; +import android.view.SurfaceControl; + +import org.hamcrest.CustomTypeSafeMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.junit.Rule; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Presubmit +public class DisplayAreaPolicyBuilderTest { + + @Rule + public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule(); + + private TestWindowManagerPolicy mPolicy = new TestWindowManagerPolicy(null, null); + + @Test + public void testBuilder() { + WindowManagerService wms = mSystemServices.getWindowManagerService(); + DisplayArea.Root root = new SurfacelessDisplayAreaRoot(wms); + DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime"); + DisplayArea<ActivityStack> tasks = new DisplayArea<>(wms, ANY, "Tasks"); + + final Feature foo; + final Feature bar; + + DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder() + .addFeature(foo = new Feature.Builder(mPolicy, "Foo") + .upTo(TYPE_STATUS_BAR) + .and(TYPE_NAVIGATION_BAR) + .build()) + .addFeature(bar = new Feature.Builder(mPolicy, "Bar") + .all() + .except(TYPE_STATUS_BAR) + .build()) + .build(wms, mock(DisplayContent.class), root, ime, tasks); + + policy.attachDisplayAreas(); + + assertThat(policy.getDisplayAreas(foo), is(not(empty()))); + assertThat(policy.getDisplayAreas(bar), is(not(empty()))); + + assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)), + is(decendantOfOneOf(policy.getDisplayAreas(foo)))); + assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)), + is(not(decendantOfOneOf(policy.getDisplayAreas(bar))))); + + assertThat(tasks, + is(decendantOfOneOf(policy.getDisplayAreas(foo)))); + assertThat(tasks, + is(decendantOfOneOf(policy.getDisplayAreas(bar)))); + + assertThat(ime, + is(decendantOfOneOf(policy.getDisplayAreas(foo)))); + assertThat(ime, + is(decendantOfOneOf(policy.getDisplayAreas(bar)))); + + List<DisplayArea<?>> actualOrder = collectLeafAreas(root); + Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime, tasks); + actualOrder = actualOrder.stream().filter(zSets::containsKey).collect(toList()); + + Map<DisplayArea<?>, Integer> expectedByMinLayer = mapValues(zSets, + v -> v.stream().min(Integer::compareTo).get()); + Map<DisplayArea<?>, Integer> expectedByMaxLayer = mapValues(zSets, + v -> v.stream().max(Integer::compareTo).get()); + + assertThat(expectedByMinLayer, is(equalTo(expectedByMaxLayer))); + assertThat(actualOrder, is(equalTo(expectedByMaxLayer))); + } + + private <K, V, R> Map<K, R> mapValues(Map<K, V> zSets, Function<V, R> f) { + return zSets.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + e -> f.apply(e.getValue()))); + } + + private List<DisplayArea<?>> collectLeafAreas(DisplayArea<?> root) { + ArrayList<DisplayArea<?>> leafs = new ArrayList<>(); + traverseLeafAreas(root, leafs::add); + return leafs; + } + + private Map<DisplayArea<?>, Set<Integer>> calculateZSets( + DisplayAreaPolicyBuilder.Result policy, DisplayArea.Root root, + DisplayArea<WindowContainer> ime, + DisplayArea<ActivityStack> tasks) { + Map<DisplayArea<?>, Set<Integer>> zSets = new HashMap<>(); + int[] types = {TYPE_STATUS_BAR, TYPE_NAVIGATION_BAR, TYPE_PRESENTATION, + TYPE_APPLICATION_OVERLAY}; + for (int type : types) { + WindowToken token = tokenOfType(type); + recordLayer(policy.findAreaForToken(token), token.getWindowLayerFromType(), zSets); + } + recordLayer(tasks, APPLICATION_LAYER, zSets); + recordLayer(ime, mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD), zSets); + return zSets; + } + + private void recordLayer(DisplayArea<?> area, int layer, + Map<DisplayArea<?>, Set<Integer>> zSets) { + zSets.computeIfAbsent(area, k -> new HashSet<>()).add(layer); + } + + private Matcher<WindowContainer> decendantOfOneOf(List<? extends WindowContainer> expected) { + return new CustomTypeSafeMatcher<WindowContainer>("descendant of one of " + expected) { + @Override + protected boolean matchesSafely(WindowContainer actual) { + for (WindowContainer expected : expected) { + WindowContainer candidate = actual; + while (candidate != null && candidate.getParent() != candidate) { + if (candidate.getParent() == expected) { + return true; + } + candidate = candidate.getParent(); + } + } + return false; + } + + @Override + protected void describeMismatchSafely(WindowContainer item, + Description description) { + description.appendText("was ").appendValue(item); + while (item != null && item.getParent() != item) { + item = item.getParent(); + description.appendText(", child of ").appendValue(item); + } + } + }; + } + + private WindowToken tokenOfType(int type) { + WindowToken m = mock(WindowToken.class); + when(m.getWindowLayerFromType()).thenReturn(mPolicy.getWindowLayerFromTypeLw(type)); + return m; + } + + private static void traverseLeafAreas(DisplayArea<?> root, Consumer<DisplayArea<?>> consumer) { + boolean leaf = true; + for (int i = 0; i < root.getChildCount(); i++) { + WindowContainer child = root.getChildAt(i); + if (child instanceof DisplayArea<?>) { + traverseLeafAreas((DisplayArea<?>) child, consumer); + leaf = false; + } + } + if (leaf) { + consumer.accept(root); + } + } + + private static class SurfacelessDisplayAreaRoot extends DisplayArea.Root { + + SurfacelessDisplayAreaRoot(WindowManagerService wms) { + super(wms); + } + + @Override + SurfaceControl.Builder makeChildSurface(WindowContainer child) { + return new MockSurfaceControlBuilder(); + } + } + +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java index c1a1d5ecd3c8..31206315618e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java @@ -34,13 +34,13 @@ public class DisplayAreaProviderTest { @Test public void testFromResources_emptyProvider() { Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider("")), - Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class)); + Matchers.instanceOf(DisplayAreaPolicy.DefaultProvider.class)); } @Test public void testFromResources_nullProvider() { Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(null)), - Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class)); + Matchers.instanceOf(DisplayAreaPolicy.DefaultProvider.class)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index f517881d835b..8ad75053060f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -56,6 +56,7 @@ import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.StrictMode; import android.os.UserHandle; +import android.util.Log; import android.view.InputChannel; import android.view.Surface; import android.view.SurfaceControl; @@ -120,11 +121,22 @@ public class SystemServicesTestRule implements TestRule { return new Statement() { @Override public void evaluate() throws Throwable { + Throwable throwable = null; try { runWithDexmakerShareClassLoader(SystemServicesTestRule.this::setUp); base.evaluate(); + } catch (Throwable t) { + throwable = t; } finally { - tearDown(); + try { + tearDown(); + } catch (Throwable t) { + if (throwable != null) { + Log.e("SystemServicesTestRule", "Suppressed: ", throwable); + t.addSuppressed(throwable); + } + throw t; + } } } }; diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 420695dc51e4..df5b311bbab1 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -500,6 +500,19 @@ public class UsageStatsService extends SystemService implements == PackageManager.PERMISSION_GRANTED); } + /** + * Obfuscate both {@link UsageEvents.Event#NOTIFICATION_SEEN} and + * {@link UsageEvents.Event#NOTIFICATION_INTERRUPTION} events if the provided calling uid does + * not hold the {@link android.Manifest.permission.MANAGE_NOTIFICATIONS} permission. + */ + private boolean shouldObfuscateNotificationEvents(int callingPid, int callingUid) { + if (callingUid == Process.SYSTEM_UID) { + return false; + } + return !(getContext().checkPermission(android.Manifest.permission.MANAGE_NOTIFICATIONS, + callingPid, callingUid) == PackageManager.PERMISSION_GRANTED); + } + private static void deleteRecursively(File f) { File[] files = f.listFiles(); if (files != null) { @@ -1038,9 +1051,7 @@ public class UsageStatsService extends SystemService implements /** * Called by the Binder stub. */ - UsageEvents queryEvents(int userId, long beginTime, long endTime, - boolean shouldObfuscateInstantApps, boolean shouldHideShortcutInvocationEvents, - boolean shouldHideLocusIdEvents) { + UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) { synchronized (mLock) { if (!mUserUnlockedStates.get(userId)) { Slog.w(TAG, "Failed to query events for locked user " + userId); @@ -1051,8 +1062,7 @@ public class UsageStatsService extends SystemService implements if (service == null) { return null; // user was stopped or removed } - return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps, - shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents); + return service.queryEvents(beginTime, endTime, flags); } } @@ -1475,10 +1485,15 @@ public class UsageStatsService extends SystemService implements try { final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents( userId, callingPackage, callingPid, callingUid); - boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid); - return UsageStatsService.this.queryEvents(userId, beginTime, endTime, - obfuscateInstantApps, hideShortcutInvocationEvents, - shouldHideLocusIdEvents); + final boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid); + final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents( + callingPid, callingUid); + int flags = UsageEvents.SHOW_ALL_EVENT_DATA; + if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS; + if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS; + if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS; + if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS; + return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags); } finally { Binder.restoreCallingIdentity(token); } @@ -1525,10 +1540,15 @@ public class UsageStatsService extends SystemService implements try { final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents( userId, callingPackage, callingPid, callingUid); - boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid); - return UsageStatsService.this.queryEvents(userId, beginTime, endTime, - obfuscateInstantApps, hideShortcutInvocationEvents, - shouldHideLocusIdEvents); + final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents( + callingPid, callingUid); + boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid); + int flags = UsageEvents.SHOW_ALL_EVENT_DATA; + if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS; + if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS; + if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS; + if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS; + return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags); } finally { Binder.restoreCallingIdentity(token); } @@ -2144,12 +2164,8 @@ public class UsageStatsService extends SystemService implements } @Override - public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime, - boolean obfuscateInstantApps, boolean shouldHideShortcutInvocationEvents, - boolean shouldHideLocusIdEvents) { - return UsageStatsService.this.queryEvents( - userId, beginTime, endTime, obfuscateInstantApps, - shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents); + public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime, int flags) { + return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags); } @Override diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index d9317ace6e24..db26d88dbfbb 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -18,6 +18,10 @@ package com.android.server.usage; import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN; import static android.app.usage.UsageEvents.Event.DEVICE_STARTUP; +import static android.app.usage.UsageEvents.HIDE_LOCUS_EVENTS; +import static android.app.usage.UsageEvents.HIDE_SHORTCUT_EVENTS; +import static android.app.usage.UsageEvents.OBFUSCATE_INSTANT_APPS; +import static android.app.usage.UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS; import static android.app.usage.UsageStatsManager.INTERVAL_BEST; import static android.app.usage.UsageStatsManager.INTERVAL_COUNT; import static android.app.usage.UsageStatsManager.INTERVAL_DAILY; @@ -481,8 +485,7 @@ class UserUsageStatsService { return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner); } - UsageEvents queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps, - boolean hideShortcutInvocationEvents, boolean hideLocusIdEvents) { + UsageEvents queryEvents(final long beginTime, final long endTime, int flags) { if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { return null; } @@ -500,15 +503,22 @@ class UserUsageStatsService { } Event event = stats.events.get(i); - if (hideShortcutInvocationEvents - && event.mEventType == Event.SHORTCUT_INVOCATION) { + final int eventType = event.mEventType; + if (eventType == Event.SHORTCUT_INVOCATION + && (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) { continue; } - if (hideLocusIdEvents - && event.mEventType == Event.LOCUS_ID_SET) { + if (eventType == Event.LOCUS_ID_SET + && (flags & HIDE_LOCUS_EVENTS) == HIDE_LOCUS_EVENTS) { continue; } - if (obfuscateInstantApps) { + if ((eventType == Event.NOTIFICATION_SEEN + || eventType == Event.NOTIFICATION_INTERRUPTION) + && (flags & OBFUSCATE_NOTIFICATION_EVENTS) + == OBFUSCATE_NOTIFICATION_EVENTS) { + event = event.getObfuscatedNotificationEvent(); + } + if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) { event = event.getObfuscatedIfInstantApp(); } if (event.mPackage != null) { diff --git a/startop/iorap/functional_tests/AndroidTest.xml b/startop/iorap/functional_tests/AndroidTest.xml index 41109b43ab82..ef56fc827420 100644 --- a/startop/iorap/functional_tests/AndroidTest.xml +++ b/startop/iorap/functional_tests/AndroidTest.xml @@ -34,6 +34,11 @@ <option name="run-command" value="rm -r /data/misc/iorapd/*" /> <option name="run-command" value="sleep 1" /> + <!-- Set system properties to enable perfetto tracing, readahead and detailed logging. --> + <option name="run-command" value="setprop iorapd.perfetto.enable true" /> + <option name="run-command" value="setprop iorapd.readahead.enable true" /> + <option name="run-command" value="setprop iorapd.log.verbose true" /> + <option name="run-command" value="start iorapd" /> <!-- give it some time to restart the service; otherwise the first unit test might fail --> @@ -45,9 +50,5 @@ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> </test> - <!-- using DeviceSetup again does not work. we simply leave the device in a semi-bad - state. there is no way to clean this up as far as I know. - --> - </configuration> diff --git a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java index bd8a45c2ca00..40023878af19 100644 --- a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java +++ b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java @@ -67,7 +67,7 @@ public class IorapWorkFlowTest { private static final String TEST_ACTIVITY_NAME = "com.android.settings.Settings"; private static final String DB_PATH = "/data/misc/iorapd/sqlite.db"; - private static final Duration TIMEOUT = Duration.ofSeconds(20L); + private static final Duration TIMEOUT = Duration.ofSeconds(300L); private static final String READAHEAD_INDICATOR = "Description = /data/misc/iorapd/com.android.settings/none/com.android.settings.Settings/compiled_traces/compiled_trace.pb"; @@ -88,7 +88,7 @@ public class IorapWorkFlowTest { mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), TIMEOUT.getSeconds()); } - @Test + @Test (timeout = 300000) public void testApp() throws Exception { assertThat(mDevice, notNullValue()); @@ -247,7 +247,7 @@ public class IorapWorkFlowTest { if (supplier.getAsBoolean()) { return true; } - TimeUnit.SECONDS.sleep(totalSleepTimeSeconds); + TimeUnit.SECONDS.sleep(sleepIntervalSeconds); totalSleepTimeSeconds += sleepIntervalSeconds; if (totalSleepTimeSeconds > timeout.getSeconds()) { return false; @@ -367,7 +367,7 @@ public class IorapWorkFlowTest { * * <p> This should be run as root.</p> */ - private String executeShellCommand(String cmd) throws Exception { + private static String executeShellCommand(String cmd) throws Exception { Log.i(TAG, "Execute: " + cmd); return UiDevice.getInstance( InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd); diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index ec99f36f6e70..52213d8c4fae 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 @@ public final class Call { /** 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 @@ public final class Call { * * @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 @@ public final class Call { 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 @@ public final class Call { 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 @@ public final class Call { } /** + * 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 56acdff530eb..f019a9d33005 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 abstract class Conference extends Conferenceable { 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 @@ public abstract class Conference extends Conferenceable { * 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 @@ public abstract class Conference extends Conferenceable { * * @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 @@ public abstract class Conference extends Conferenceable { * * @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 @@ public abstract class Conference extends Conferenceable { * 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 @@ public abstract class Conference extends Conferenceable { */ @SystemApi @TestApi + @RequiresPermission(MODIFY_PHONE_STATE) public void setConferenceState(boolean isConference) { for (Listener l : mListeners) { l.onConferenceStateChanged(this, isConference); @@ -1007,6 +1037,7 @@ public abstract class Conference extends Conferenceable { */ @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 @@ public abstract class Conference extends Conferenceable { } /** - * 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 8049459cf3f4..3b0ba2548660 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 @@ public abstract class Connection extends Conferenceable { /** 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 @@ public abstract class Connection extends Conferenceable { * * @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 @@ public abstract class Connection extends Conferenceable { 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 @@ public abstract class Connection extends Conferenceable { */ @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 @@ public abstract class Connection extends Conferenceable { */ @SystemApi @TestApi - public final long getConnectElapsedTimeMillis() { + public final @ElapsedRealtimeLong long getConnectionStartElapsedRealtimeMillis() { return mConnectElapsedTimeMillis; } @@ -2550,6 +2568,9 @@ public abstract class Connection extends Conferenceable { * 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 @@ public abstract class Connection extends Conferenceable { */ @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 @@ public abstract class Connection extends Conferenceable { * 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 abstract class Connection extends Conferenceable { 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 00c2918837ac..2aea723cf418 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -18,7 +18,6 @@ package android.telecom; 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 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { } @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 @@ public abstract class ConnectionService extends Service { } 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 @@ public abstract class ConnectionService extends Service { null : conference.getVideoProvider().getInterface(), conference.getVideoState(), conference.getConnectTimeMillis(), - conference.getConnectionStartElapsedRealTime(), + conference.getConnectionStartElapsedRealtimeMillis(), conference.getStatusHints(), conference.getExtras(), conference.getAddress(), @@ -1884,7 +1913,7 @@ public abstract class ConnectionService extends Service { connection.isRingbackRequested(), connection.getAudioModeIsVoip(), connection.getConnectTimeMillis(), - connection.getConnectElapsedTimeMillis(), + connection.getConnectionStartElapsedRealtimeMillis(), connection.getStatusHints(), connection.getDisconnectCause(), createIdList(connection.getConferenceables()), @@ -2152,6 +2181,17 @@ public abstract class ConnectionService extends Service { } } + 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 @@ public abstract class ConnectionService extends Service { null : conference.getVideoProvider().getInterface(), conference.getVideoState(), conference.getConnectTimeMillis(), - conference.getConnectionStartElapsedRealTime(), + conference.getConnectionStartElapsedRealtimeMillis(), conference.getStatusHints(), conference.getExtras(), conference.getAddress(), @@ -2465,7 +2505,7 @@ public abstract class ConnectionService extends Service { 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 594c1eb392b3..9d29174059ad 100644 --- a/telecomm/java/android/telecom/InCallAdapter.java +++ b/telecomm/java/android/telecom/InCallAdapter.java @@ -283,6 +283,20 @@ public final class InCallAdapter { } /** + * 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 ebfa3a15639a..982e5f30e28c 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; 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 @@ import java.util.List; * 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 @@ import java.util.List; * <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 @@ import java.util.List; * } * } * } + * } * </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 f00432b5fad3..4e6e1a53113e 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 @@ public final class PhoneAccount implements Parcelable { * 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 @@ public final class PhoneAccount implements Parcelable { */ @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 a28cc4f69155..5d7d6490ba3e 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -819,8 +819,8 @@ public class TelecomManager { * 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 @@ public class TelecomManager { /** * 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 @@ public class TelecomManager { @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 4249dff151c7..a397d77db2f6 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -104,6 +104,9 @@ oneway interface IConnectionService { 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 eb2d714fe3f4..9beff22ce52e 100644 --- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl @@ -67,6 +67,8 @@ oneway interface IInCallAdapter { 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 d2a5905f7a99..a27c4802c306 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -661,4 +661,16 @@ public class Annotation { }) @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 ebb53c50ca98..c51a8520ad3b 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1090,6 +1090,14 @@ public class CarrierConfigManager { "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}. */ @@ -2369,7 +2377,7 @@ public class CarrierConfigManager { * {@link CellSignalStrengthLte#USE_RSRQ}, {@link CellSignalStrengthLte#USE_RSSNR}. * * For example, if both RSRP and RSRQ are used, the value of key is 3 (1 << 0 | 1 << 1). - * If the key is invalid or not configured, a default value (RSRP | RSSNR = 1 << 0 | 1 << 2) + * If the key is invalid or not configured, a default value (RSRP = 1 << 0) * will apply. * * @hide @@ -4004,6 +4012,7 @@ public class CarrierConfigManager { 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); @@ -4325,7 +4334,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false); sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 2000); sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT, - CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSSNR); + CellSignalStrengthLte.USE_RSRP); // Default wifi configurations. sDefaults.putAll(Wifi.getDefaults()); sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false); diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java index ec86c144c08e..bfa209bd31f5 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 @@ public abstract class CellInfo implements Parcelable { /** * 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/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index 1cd45e93a52a..2529387b19b3 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -181,7 +181,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P mCqi = CellInfo.UNAVAILABLE; mTimingAdvance = CellInfo.UNAVAILABLE; mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - mParametersUseForLevel = USE_RSRP | USE_RSSNR; + mParametersUseForLevel = USE_RSRP; } /** {@inheritDoc} */ @@ -236,7 +236,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P int[] rsrpThresholds, rsrqThresholds, rssnrThresholds; boolean rsrpOnly; if (cc == null) { - mParametersUseForLevel = USE_RSRP | USE_RSSNR; + mParametersUseForLevel = USE_RSRP; rsrpThresholds = sRsrpThresholds; rsrqThresholds = sRsrqThresholds; rssnrThresholds = sRssnrThresholds; diff --git a/telephony/java/android/telephony/DisplayInfo.aidl b/telephony/java/android/telephony/DisplayInfo.aidl new file mode 100644 index 000000000000..861b0fe04848 --- /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 000000000000..d54bcf931c33 --- /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 54980a29c0a6..250d9e8b212e 100644 --- a/telephony/java/android/telephony/PreciseDisconnectCause.java +++ b/telephony/java/android/telephony/PreciseDisconnectCause.java @@ -256,337 +256,6 @@ public final class PreciseDisconnectCause { /** 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 5a4dac5220a0..ad58c54054a3 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 @@ public class SignalStrength implements Parcelable { /* 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 @@ public class SignalStrength implements Parcelable { mTdscdma = tdscdma; mLte = lte; mNr = nr; - mTimestamp = SystemClock.elapsedRealtimeNanos(); + mTimestampMillis = SystemClock.elapsedRealtime(); } /** @@ -274,7 +275,6 @@ public class SignalStrength implements Parcelable { mTdscdma.updateLevel(cc, ss); mLte.updateLevel(cc, ss); mNr.updateLevel(cc, ss); - mTimestamp = SystemClock.elapsedRealtimeNanos(); } /** @@ -300,7 +300,7 @@ public class SignalStrength implements Parcelable { 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 @@ public class SignalStrength implements Parcelable { 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 @@ public class SignalStrength implements Parcelable { 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/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index c40573b25068..6fdc13e6a31b 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -492,6 +492,7 @@ public interface RILConstants { int RIL_REQUEST_ENABLE_UICC_APPLICATIONS = 208; int RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT = 209; int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210; + int RIL_REQUEST_GET_BARRING_INFO = 211; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java index 20d0e9690e8e..ae20cae36839 100644 --- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java +++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java @@ -430,10 +430,9 @@ public class ApkVerityTest extends BaseHostJUnit4Test { 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/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index 95b8f6700c76..da45d9a258bd 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -296,6 +296,8 @@ public class AppLaunch extends InstrumentationTestCase { AppLaunchResult launchResults = null; if (hasFailureOnFirstLaunch(launch)) { // skip if the app has failures while launched first + Log.w(TAG, "Has failures on first launch: " + launch.getApp()); + forceStopApp(launch.getApp()); continue; } AtraceLogger atraceLogger = null; diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 155c61f3f8c7..eb78529e8715 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 @@ public class VpnTest { @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 @@ public class VpnTest { * 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/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 957216e17925..37842de92e79 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -45,6 +45,7 @@ public final class FrameworksTestsFilter extends SelectTest { "android.view.InsetsSourceTest", "android.view.InsetsSourceConsumerTest", "android.view.InsetsStateTest", + "android.view.WindowMetricsTest" }; public FrameworksTestsFilter(Bundle testArgs) { diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index b6f4490a1872..f693315c6cff 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -28,6 +28,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.ActivityManager; @@ -1621,11 +1622,13 @@ public class WifiManager { * @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() {} } diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java index 99901808ec3e..9c01d3643c19 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java @@ -738,7 +738,16 @@ public final class Credential implements Parcelable { @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("IMSI: ").append(mImsi).append("\n"); + String imsi; + if (mImsi != null) { + if (mImsi.length() > 6 && mImsi.charAt(6) != '*') { + // Truncate the full IMSI from the log + imsi = mImsi.substring(0, 6) + "****"; + } else { + imsi = mImsi; + } + builder.append("IMSI: ").append(imsi).append("\n"); + } builder.append("EAPType: ").append(mEapType).append("\n"); return builder.toString(); } diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiNl80211Manager.java index 61f18e0b7191..89f642fdbb66 100644 --- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java +++ b/wifi/java/android/net/wifi/wificond/WifiNl80211Manager.java @@ -49,15 +49,16 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; /** - * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework. The + * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework - used + * to encapsulate the Wi-Fi 80211nl management interface. The * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions. * * @hide */ @SystemApi -@SystemService(Context.WIFI_COND_SERVICE) -public class WifiCondManager { - private static final String TAG = "WifiCondManager"; +@SystemService(Context.WIFI_NL80211_SERVICE) +public class WifiNl80211Manager { + private static final String TAG = "WifiNl80211Manager"; private boolean mVerboseLoggingEnabled = false; /** @@ -316,14 +317,14 @@ public class WifiCondManager { public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5; /** @hide */ - public WifiCondManager(Context context) { + public WifiNl80211Manager(Context context) { mAlarmManager = context.getSystemService(AlarmManager.class); mEventHandler = new Handler(context.getMainLooper()); } /** @hide */ @VisibleForTesting - public WifiCondManager(Context context, IWificond wificond) { + public WifiNl80211Manager(Context context, IWificond wificond) { this(context); mWificond = wificond; } @@ -485,7 +486,7 @@ public class WifiCondManager { } /** - * Enable or disable verbose logging of the WifiCondManager module. + * Enable or disable verbose logging of the WifiNl80211Manager module. * @param enable True to enable verbose logging. False to disable verbose logging. */ public void enableVerboseLogging(boolean enable) { @@ -493,7 +494,7 @@ public class WifiCondManager { } /** - * Register a death notification for the WifiCondManager which acts as a proxy for the + * Register a death notification for the WifiNl80211Manager which acts as a proxy for the * wificond daemon (i.e. the death listener will be called when and if the wificond daemon * dies). * @@ -518,7 +519,7 @@ public class WifiCondManager { // We already have a wificond handle. return true; } - IBinder binder = ServiceManager.getService(Context.WIFI_COND_SERVICE); + IBinder binder = ServiceManager.getService(Context.WIFI_NL80211_SERVICE); mWificond = IWificond.Stub.asInterface(binder); if (mWificond == null) { Log.e(TAG, "Failed to get reference to wificond"); diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiNl80211ManagerTest.java index b745a341b459..a8184068ff5a 100644 --- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java +++ b/wifi/tests/src/android/net/wifi/wificond/WifiNl80211ManagerTest.java @@ -71,10 +71,10 @@ import java.util.List; import java.util.Set; /** - * Unit tests for {@link android.net.wifi.WifiCondManager}. + * Unit tests for {@link android.net.wifi.wificond.WifiNl80211Manager}. */ @SmallTest -public class WifiCondManagerTest { +public class WifiNl80211ManagerTest { @Mock private IWificond mWificond; @Mock @@ -86,21 +86,21 @@ public class WifiCondManagerTest { @Mock private IApInterface mApInterface; @Mock - private WifiCondManager.SoftApCallback mSoftApListener; + private WifiNl80211Manager.SoftApCallback mSoftApListener; @Mock - private WifiCondManager.SendMgmtFrameCallback mSendMgmtFrameCallback; + private WifiNl80211Manager.SendMgmtFrameCallback mSendMgmtFrameCallback; @Mock - private WifiCondManager.ScanEventCallback mNormalScanCallback; + private WifiNl80211Manager.ScanEventCallback mNormalScanCallback; @Mock - private WifiCondManager.ScanEventCallback mPnoScanCallback; + private WifiNl80211Manager.ScanEventCallback mPnoScanCallback; @Mock - private WifiCondManager.PnoScanRequestCallback mPnoScanRequestCallback; + private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback; @Mock private Context mContext; private TestLooper mLooper; private TestAlarmManager mTestAlarmManager; private AlarmManager mAlarmManager; - private WifiCondManager mWificondControl; + private WifiNl80211Manager mWificondControl; private static final String TEST_INTERFACE_NAME = "test_wlan_if"; private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1"; private static final String TEST_INVALID_INTERFACE_NAME = "asdf"; @@ -180,7 +180,7 @@ public class WifiCondManagerTest { when(mWificond.tearDownApInterface(any())).thenReturn(true); when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl); when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME); - mWificondControl = new WifiCondManager(mContext, mWificond); + mWificondControl = new WifiNl80211Manager(mContext, mWificond); assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run, mNormalScanCallback, mPnoScanCallback)); @@ -492,7 +492,7 @@ public class WifiCondManagerTest { // getScanResults should fail. assertEquals(0, mWificondControl.getScanResults(TEST_INTERFACE_NAME, - WifiCondManager.SCAN_TYPE_SINGLE_SCAN).size()); + WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN).size()); } /** @@ -786,10 +786,10 @@ public class WifiCondManagerTest { */ @Test public void testSendMgmtFrameCalledTwiceBeforeFinished() throws Exception { - WifiCondManager.SendMgmtFrameCallback cb1 = mock( - WifiCondManager.SendMgmtFrameCallback.class); - WifiCondManager.SendMgmtFrameCallback cb2 = mock( - WifiCondManager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb1 = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb2 = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE, Runnable::run, cb1); @@ -800,7 +800,7 @@ public class WifiCondManagerTest { mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE, Runnable::run, cb2); - verify(cb2).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED); + verify(cb2).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED); // verify SendMgmtFrame() still was only called once i.e. not called again verify(mClientInterface, times(1)) .SendMgmtFrame(any(), any(), anyInt()); @@ -811,8 +811,8 @@ public class WifiCondManagerTest { */ @Test public void testSendMgmtFrameThrowsException() throws Exception { - WifiCondManager.SendMgmtFrameCallback cb = mock( - WifiCondManager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor = ArgumentCaptor.forClass(ISendMgmtFrameEvent.class); @@ -834,7 +834,7 @@ public class WifiCondManagerTest { verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue())); sendMgmtFrameEventCaptor.getValue().OnFailure( - WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN); + WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); mLooper.dispatchAll(); handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm()); @@ -848,8 +848,8 @@ public class WifiCondManagerTest { */ @Test public void testSendMgmtFrameSuccess() throws Exception { - WifiCondManager.SendMgmtFrameCallback cb = mock( - WifiCondManager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor = ArgumentCaptor.forClass(ISendMgmtFrameEvent.class); @@ -882,8 +882,8 @@ public class WifiCondManagerTest { */ @Test public void testSendMgmtFrameFailure() throws Exception { - WifiCondManager.SendMgmtFrameCallback cb = mock( - WifiCondManager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor = ArgumentCaptor.forClass(ISendMgmtFrameEvent.class); @@ -898,10 +898,10 @@ public class WifiCondManagerTest { Runnable::run, cb); sendMgmtFrameEventCaptor.getValue().OnFailure( - WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN); + WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); mLooper.dispatchAll(); verify(cb, never()).onAck(anyInt()); - verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN); + verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue())); // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not @@ -917,8 +917,8 @@ public class WifiCondManagerTest { */ @Test public void testSendMgmtFrameTimeout() throws Exception { - WifiCondManager.SendMgmtFrameCallback cb = mock( - WifiCondManager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor = ArgumentCaptor.forClass(ISendMgmtFrameEvent.class); @@ -935,7 +935,7 @@ public class WifiCondManagerTest { handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm()); mLooper.dispatchAll(); verify(cb, never()).onAck(anyInt()); - verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT); + verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT); // verify that even if onAck() callback is triggered after timeout, // SendMgmtFrameCallback is not triggered again @@ -1006,7 +1006,8 @@ public class WifiCondManagerTest { sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS); mLooper.dispatchAll(); verify(mSendMgmtFrameCallback, never()).onAck(anyInt()); - verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT); + verify(mSendMgmtFrameCallback).onFailure( + WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT); } /** @@ -1032,9 +1033,10 @@ public class WifiCondManagerTest { handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm()); // OnFailure posts to the handler sendMgmtFrameEventCaptor.getValue().OnFailure( - WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN); + WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); mLooper.dispatchAll(); - verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT); + verify(mSendMgmtFrameCallback).onFailure( + WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT); } /** |