diff options
128 files changed, 5045 insertions, 3121 deletions
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java index de9d609ea17b..5b43ac387453 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java @@ -412,11 +412,6 @@ public final class AppSearchImpl implements Closeable { String prefix = createPrefix(packageName, databaseName); GetSchemaResponse.Builder responseBuilder = new GetSchemaResponse.Builder(); - if (!fullSchema.getTypesList().isEmpty()) { - // TODO(b/183050495) find a place to store the version for the database, rather - // than read from a schema. - responseBuilder.setVersion(fullSchema.getTypes(0).getVersion()); - } for (int i = 0; i < fullSchema.getTypesCount(); i++) { String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType()); if (!prefix.equals(typePrefix)) { @@ -444,6 +439,10 @@ public final class AppSearchImpl implements Closeable { AppSearchSchema schema = SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder); + + // TODO(b/183050495) find a place to store the version for the database, rather + // than read from a schema. + responseBuilder.setVersion(fullSchema.getTypes(i).getVersion()); responseBuilder.addSchema(schema); } return responseBuilder.build(); diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt index 58f430b5bd61..29bd54134a37 100644 --- a/apex/appsearch/synced_jetpack_changeid.txt +++ b/apex/appsearch/synced_jetpack_changeid.txt @@ -1 +1 @@ -Ie11a0555775a0ab2a39f6ce6d0d8a7b735c416ce +Ibbd3a92ad091f6911de652e2ba7e44f555a70a72 diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index cb1fccf1e96e..d459c0502189 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -183,6 +183,10 @@ public class PowerExemptionManager { public static final int REASON_APPOP = 66; /** @hide */ public static final int REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD = 67; + /** @hide */ + public static final int REASON_OP_ACTIVATE_VPN = 68; + /** @hide */ + public static final int REASON_OP_ACTIVATE_PLATFORM_VPN = 69; /* BG-FGS-launch is allowed by temp-allow-list or system-allow-list. Reason code for temp and system allow list starts here. @@ -344,6 +348,8 @@ public class PowerExemptionManager { REASON_ALLOWLISTED_PACKAGE, REASON_APPOP, REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD, + REASON_OP_ACTIVATE_VPN, + REASON_OP_ACTIVATE_PLATFORM_VPN, // temp and system allow list reasons. REASON_GEOFENCING, REASON_PUSH_MESSAGING, @@ -603,6 +609,10 @@ public class PowerExemptionManager { return "APPOP"; case REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD: return "ACTIVITY_VISIBILITY_GRACE_PERIOD"; + case REASON_OP_ACTIVATE_VPN: + return "OP_ACTIVATE_VPN"; + case REASON_OP_ACTIVATE_PLATFORM_VPN: + return "OP_ACTIVATE_PLATFORM_VPN"; case REASON_GEOFENCING: return "GEOFENCING"; case REASON_PUSH_MESSAGING: diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 2551b77b5353..bfc9144faa5c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -8766,6 +8766,8 @@ package android.permission { public final class PermissionControllerManager { method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); + method @Nullable public String getGroupOfPlatformPermission(@NonNull String); + method @NonNull public java.util.Set<java.lang.String> getPlatformPermissionsForGroup(@NonNull String); method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>); method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback); method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle); @@ -8787,7 +8789,9 @@ package android.permission { method @NonNull public final android.os.IBinder onBind(android.content.Intent); method @BinderThread public abstract void onCountPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull java.util.function.IntConsumer); method @BinderThread public abstract void onGetAppPermissions(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionPresentationInfo>>); + method @BinderThread public void onGetGroupOfPlatformPermission(@NonNull String, @NonNull java.util.function.Consumer<java.lang.String>); method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>); + method @BinderThread public void onGetPlatformPermissionsForGroup(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>); method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable); method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable); method @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2fbea28f34d5..4e140a8d3f45 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3744,10 +3744,8 @@ public class Notification implements Parcelable private int mTextColorsAreForBackground = COLOR_INVALID; private int mPrimaryTextColor = COLOR_INVALID; private int mSecondaryTextColor = COLOR_INVALID; - private boolean mRebuildStyledRemoteViews; private boolean mTintActionButtons; - private boolean mTintWithThemeAccent; private boolean mInNightMode; /** @@ -3783,7 +3781,6 @@ public class Notification implements Parcelable mContext = context; Resources res = mContext.getResources(); mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); - mTintWithThemeAccent = res.getBoolean(R.bool.config_tintNotificationsWithTheme); if (res.getBoolean(R.bool.config_enableNightMode)) { Configuration currentConfig = res.getConfiguration(); @@ -5208,15 +5205,21 @@ public class Notification implements Parcelable || mSecondaryTextColor == COLOR_INVALID || mTextColorsAreForBackground != backgroundColor) { mTextColorsAreForBackground = backgroundColor; - mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext, + int defaultPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext, backgroundColor, mInNightMode); - mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext, + int defaultSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext, backgroundColor, mInNightMode); - if (backgroundColor != COLOR_DEFAULT && isColorized(p)) { + boolean colorized = backgroundColor != COLOR_DEFAULT; + if (colorized) { mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( - mPrimaryTextColor, backgroundColor, 4.5); + defaultPrimaryTextColor, backgroundColor, 4.5); mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( - mSecondaryTextColor, backgroundColor, 4.5); + defaultSecondaryTextColor, backgroundColor, 4.5); + } else { + mPrimaryTextColor = obtainThemeColor(R.attr.textColorPrimary, + defaultPrimaryTextColor); + mSecondaryTextColor = obtainThemeColor(R.attr.textColorSecondary, + defaultSecondaryTextColor); } } } @@ -5243,11 +5246,9 @@ public class Notification implements Parcelable contentView.setProgressBar(R.id.progress, max, progress, ind); contentView.setProgressBackgroundTintList(R.id.progress, mContext.getColorStateList(R.color.notification_progress_background_color)); - if (mTintWithThemeAccent || getRawColor(p) != COLOR_DEFAULT) { - ColorStateList progressTint = ColorStateList.valueOf(getAccentColor(p)); - contentView.setProgressTintList(R.id.progress, progressTint); - contentView.setProgressIndeterminateTintList(R.id.progress, progressTint); - } + ColorStateList progressTint = ColorStateList.valueOf(getAccentColor(p)); + contentView.setProgressTintList(R.id.progress, progressTint); + contentView.setProgressIndeterminateTintList(R.id.progress, progressTint); return true; } else { contentView.setViewVisibility(R.id.progress, View.GONE); @@ -5810,8 +5811,7 @@ public class Notification implements Parcelable } private boolean useExistingRemoteView() { - return mStyle == null || (!mStyle.displayCustomViewInline() - && !mRebuildStyledRemoteViews); + return mStyle == null || !mStyle.displayCustomViewInline(); } /** @@ -6083,8 +6083,7 @@ public class Notification implements Parcelable background = outResultColor[0].getDefaultColor(); textColor = ContrastColorUtil.resolvePrimaryColor(mContext, background, mInNightMode); - } else if (mTintActionButtons && !mInNightMode - && getRawColor(p) != COLOR_DEFAULT && !isColorized(p)) { + } else if (mTintActionButtons && !mInNightMode && !isColorized(p)) { textColor = getAccentColor(p); } else { textColor = getPrimaryTextColor(p); @@ -6262,7 +6261,7 @@ public class Notification implements Parcelable * is the primary text color, otherwise it's the contrast-adjusted app-provided color. */ private @ColorInt int getSmallIconColor(StandardTemplateParams p) { - return isColorized(p) ? getPrimaryTextColor(p) : getContrastColor(p); + return getContrastColor(p); } /** @@ -6274,11 +6273,9 @@ public class Notification implements Parcelable if (isColorized(p)) { return getPrimaryTextColor(p); } - if (mTintWithThemeAccent) { - int color = obtainThemeColor(R.attr.colorAccent, COLOR_INVALID); - if (color != COLOR_INVALID) { - return color; - } + int color = obtainThemeColor(R.attr.colorAccent, COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; } return getContrastColor(p); } @@ -6288,7 +6285,7 @@ public class Notification implements Parcelable * color when colorized, or when not using theme color tints. */ private @ColorInt int getProtectionColor(StandardTemplateParams p) { - if (mTintWithThemeAccent && !isColorized(p)) { + if (!isColorized(p)) { int color = obtainThemeColor(R.attr.colorBackgroundFloating, COLOR_INVALID); if (color != COLOR_INVALID) { return color; @@ -6307,12 +6304,10 @@ public class Notification implements Parcelable if (isColorized(p)) { return getPrimaryTextColor(p); } - if (mTintWithThemeAccent) { - int color = obtainThemeColor(com.android.internal.R.attr.colorAccentTertiary, - COLOR_INVALID); - if (color != COLOR_INVALID) { - return color; - } + int color = obtainThemeColor(com.android.internal.R.attr.colorAccentTertiary, + COLOR_INVALID); + if (color != COLOR_INVALID) { + return color; } return getContrastColor(p); } @@ -6342,6 +6337,9 @@ public class Notification implements Parcelable * Gets the contrast-adjusted version of the color provided by the app. */ private @ColorInt int getContrastColor(StandardTemplateParams p) { + if (isColorized(p)) { + return getPrimaryTextColor(p); + } int rawColor = getRawColor(p); if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) { return mCachedContrastColor; @@ -6352,9 +6350,10 @@ public class Notification implements Parcelable int background = getDefaultBackgroundColor(); if (rawColor == COLOR_DEFAULT) { ensureColors(p); - color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode); - if (mTintWithThemeAccent) { - color = obtainThemeColor(R.attr.colorAccent, color); + color = obtainThemeColor(R.attr.colorAccent, COLOR_INVALID); + if (color == COLOR_INVALID) { + color = ContrastColorUtil.resolveDefaultColor(mContext, background, + mInNightMode); } } else { color = ContrastColorUtil.resolveContrastColor(mContext, rawColor, @@ -6375,11 +6374,6 @@ public class Notification implements Parcelable * @param p the template params to inflate this with */ private @ColorInt int getRawColor(StandardTemplateParams p) { - // When notifications are theme-tinted, the raw color is only used for the icon, so go - // ahead and keep that color instead of changing the color for minimized notifs. - if (p.mReduceHighlights && !mTintWithThemeAccent) { - return COLOR_DEFAULT; - } return mN.color; } @@ -6486,6 +6480,7 @@ public class Notification implements Parcelable + " notification: " + mN.mShortcutId + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); } + validateColorizedHasColor(); // first, add any extras from the calling code if (mUserExtras != null) { @@ -6539,6 +6534,21 @@ public class Notification implements Parcelable return mN; } + // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, + // a use case that is not supported by the Compat Framework library. + @SuppressWarnings("AndroidFrameworkCompatChange") + private void validateColorizedHasColor() { + if (mN.color == COLOR_DEFAULT && mN.extras.getBoolean(EXTRA_COLORIZED)) { + if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { + throw new IllegalArgumentException( + "Colorized notifications must set a color (other than COLOR_DEFAULT)."); + } else { + Log.w(TAG, "Colorized notifications must set a color (other than " + + "COLOR_DEFAULT). This is required for apps targeting S."); + } + } + } + /** * Returns the color for the given Theme.DeviceDefault.DayNight attribute, or * defValue if that could not be completed @@ -6551,13 +6561,9 @@ public class Notification implements Parcelable } theme = new ContextThemeWrapper(mContext, R.style.Theme_DeviceDefault_DayNight) .getTheme(); - TypedArray ta = theme.obtainStyledAttributes(new int[]{attrRes}); - if (ta == null) { - return defaultColor; + try (TypedArray ta = theme.obtainStyledAttributes(new int[]{attrRes})) { + return ta.getColor(0, defaultColor); } - int background = ta.getColor(0, defaultColor); - ta.recycle(); - return background; } /** @@ -6671,11 +6677,7 @@ public class Notification implements Parcelable * which must be resolved by the caller before being used. */ private @ColorInt int getUnresolvedBackgroundColor(StandardTemplateParams p) { - if (isColorized(p)) { - return getRawColor(p); - } else { - return COLOR_DEFAULT; - } + return isColorized(p) ? getRawColor(p) : COLOR_DEFAULT; } /** @@ -6700,18 +6702,6 @@ public class Notification implements Parcelable } /** - * Forces all styled remoteViews to be built from scratch and not use any cached - * RemoteViews. - * This is needed for legacy apps that are baking in their remoteviews into the - * notification. - * - * @hide - */ - public void setRebuildStyledRemoteViews(boolean rebuild) { - mRebuildStyledRemoteViews = rebuild; - } - - /** * Get the text that should be displayed in the statusBar when heads upped. This is * usually just the app name, but may be different depending on the style. * @@ -6878,7 +6868,7 @@ public class Notification implements Parcelable * @hide */ public boolean isColorized() { - return extras.getBoolean(EXTRA_COLORIZED) + return color != COLOR_DEFAULT && extras.getBoolean(EXTRA_COLORIZED) && (hasColorizedPermission() || isForegroundService()); } @@ -8389,27 +8379,6 @@ public class Notification implements Parcelable return true; } - private CharSequence createConversationTitleFromMessages() { - ArraySet<CharSequence> names = new ArraySet<>(); - for (int i = 0; i < mMessages.size(); i++) { - Message m = mMessages.get(i); - Person sender = m.getSenderPerson(); - if (sender != null) { - names.add(sender.getName()); - } - } - SpannableStringBuilder title = new SpannableStringBuilder(); - int size = names.size(); - for (int i = 0; i < size; i++) { - CharSequence name = names.valueAt(i); - if (!TextUtils.isEmpty(title)) { - title.append(", "); - } - title.append(BidiFormatter.getInstance().unicodeWrap(name)); - } - return title; - } - /** * @hide */ @@ -8423,11 +8392,6 @@ public class Notification implements Parcelable return remoteViews; } - private static TextAppearanceSpan makeFontColorSpan(int color) { - return new TextAppearanceSpan(null, 0, 0, - ColorStateList.valueOf(color), null); - } - public static final class Message { /** @hide */ public static final String KEY_TEXT = "text"; @@ -9889,23 +9853,6 @@ public class Notification implements Parcelable // Comparison done for all custom RemoteViews, independent of style return false; } - - private RemoteViews buildIntoRemoteView(RemoteViews template, RemoteViews customContent, - boolean headerless) { - if (customContent != null) { - // Need to clone customContent before adding, because otherwise it can no longer be - // parceled independently of remoteViews. - customContent = customContent.clone(); - customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams)); - if (headerless) { - template.removeFromParent(R.id.notification_top_line); - } - template.removeAllViews(R.id.notification_main_column); - template.addView(R.id.notification_main_column, customContent); - template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); - } - return template; - } } /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e4120913c9f7..843aa2ecb492 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1774,19 +1774,6 @@ public class DevicePolicyManager { = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED"; /** - * Broadcast action to notify ManagedProvisioning that - * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has changed. - * @hide - * @deprecated No longer needed as ManagedProvisioning no longer handles - * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction changing. - */ - // TODO(b/177221010): Remove when Managed Provisioning no longer depend on it. - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @Deprecated - public static final String ACTION_DATA_SHARING_RESTRICTION_CHANGED = - "android.app.action.DATA_SHARING_RESTRICTION_CHANGED"; - - /** * Broadcast action from ManagedProvisioning to notify that the latest change to * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has been successfully * applied (cross profile intent filters updated). Only usesd for CTS tests. @@ -2678,8 +2665,8 @@ public class DevicePolicyManager { * A boolean extra which determines whether to skip the ownership disclaimer screen during the * provisioning flow. The default value is {@code false}. * - * If the value is {@code true}, it is the responsibility of the provisioning initiator to - * show the relevant disclaimer. + * If the value is {@code true}, the provisioning initiator must display a device ownership + * disclaimer screen similar to that provided in AOSP. * * <p>This extra is only respected when provided alongside the {@link * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent action. diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index a0721c32fc2a..bcd00b21e97d 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -74,7 +74,7 @@ public final class BatteryUsageStats implements Parcelable { for (int i = 0; i < systemBatteryConsumerCount; i++) { final SystemBatteryConsumer consumer = builder.mSystemBatteryConsumerBuilders.valueAt(i).build(); - totalPower += consumer.getConsumedPower(); + totalPower += consumer.getConsumedPower() - consumer.getPowerConsumedByApps(); mSystemBatteryConsumers.add(consumer); } diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index b12dad038ce3..40c658f01e28 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -46,8 +46,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; -import android.content.res.AssetFileDescriptor; -import android.net.Uri; import android.provider.DocumentsContract.Document; import android.provider.MediaStore; import android.system.ErrnoException; @@ -83,7 +81,6 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; -import java.util.Locale; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -1446,46 +1443,22 @@ public final class FileUtils { } } - // TODO(b/170488060): Consider better approach /** {@hide} */ @VisibleForTesting - public static FileDescriptor convertToModernFd(FileDescriptor fd) { + public static ParcelFileDescriptor convertToModernFd(FileDescriptor fd) { try { Context context = AppGlobals.getInitialApplication(); - // /mnt/user paths are not accessible directly so convert to a /storage path - String filePath = Os.readlink("/proc/self/fd/" + fd.getInt$()).replace( - "/mnt/user/" + UserHandle.myUserId(), "/storage"); - File realFile = new File(filePath); - String fileName = realFile.getName(); - boolean isCameraVideo = !fileName.startsWith(".") && fileName.endsWith(".mp4") - && contains(CAMERA_DIR_LOWER_CASE, filePath.toLowerCase(Locale.ROOT)); - - if (!SystemProperties.getBoolean("sys.fuse.transcode_enabled", false) - || UserHandle.getAppId(Process.myUid()) == getMediaProviderAppId(context) - || !isCameraVideo) { - // 1. If transcode is enabled we optimize by default, unless explicitly disabled. - // 2. Never convert modern fd for MediaProvider, because this requires + if (UserHandle.getAppId(Process.myUid()) == getMediaProviderAppId(context)) { + // Never convert modern fd for MediaProvider, because this requires // MediaStore#scanFile and can cause infinite loops when MediaProvider scans - // 3. Only convert published mp4 videos in the DCIM/Camera dir return null; } - Log.i(TAG, "Changing to modern format dataSource for: " + realFile); - ContentResolver resolver = context.getContentResolver(); - - Uri uri = MediaStore.scanFile(resolver, realFile); - if (uri != null) { - Bundle opts = new Bundle(); - opts.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, true); - AssetFileDescriptor afd = resolver.openTypedAssetFileDescriptor(uri, "*/*", opts); - Log.i(TAG, "Changed to modern format dataSource for: " + realFile); - return afd.getFileDescriptor(); - } else { - Log.i(TAG, "Failed to change to modern format dataSource for: " + realFile); - } + return MediaStore.getOriginalMediaFormatFileDescriptor(context, + ParcelFileDescriptor.dup(fd)); } catch (Exception e) { - Log.w(TAG, "Failed to change to modern format dataSource", e); + Log.w(TAG, "Failed to convert to modern format file descriptor", e); + return null; } - return null; } private static int getMediaProviderAppId(Context context) { diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 6d677f35b563..66e1c5a93f16 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -48,4 +48,10 @@ oneway interface IPermissionController { void getPrivilegesDescriptionStringForProfile( in String deviceProfileName, in AndroidFuture<String> callback); + void getPlatformPermissionsForGroup( + in String permissionGroupName, + in AndroidFuture<List<String>> callback); + void getGroupOfPlatformPermission( + in String permissionName, + in AndroidFuture<String> callback); } diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 913b827332bf..05eb23ad705d 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -45,6 +45,7 @@ import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -66,6 +67,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -720,4 +722,46 @@ public final class PermissionControllerManager { mRemoteService.run( service -> service.notifyOneTimePermissionSessionTimeout(packageName)); } + + /** + * Get the platform permissions which belong to a particular permission group + * + * @param permissionGroupName The permission group whose permissions are desired + * @return A list of the platform permissions in the group, or empty if the group is not a valid + * platform group. + */ + public @NonNull Set<String> getPlatformPermissionsForGroup( + @NonNull String permissionGroupName) { + try { + return new ArraySet<>(mRemoteService.postAsync(service -> { + AndroidFuture<List<String>> future = new AndroidFuture<>(); + service.getPlatformPermissionsForGroup(permissionGroupName, future); + return future; + }).get(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); + } catch (Exception e) { + Log.e(TAG, "Failed to get permissions of " + permissionGroupName, e); + return null; + } + } + + /** + * Get the platform group of a particular permission, if the permission is a platform permission + * + * @param permissionName The permission name whose group is desired + * @return The name of the permission group this permission belongs to, or null if it has no + * group, or is not a platform permission + */ + public @Nullable String getGroupOfPlatformPermission( + @NonNull String permissionName) { + try { + return mRemoteService.postAsync(service -> { + AndroidFuture<String> future = new AndroidFuture<>(); + service.getGroupOfPlatformPermission(permissionName, future); + return future; + }).get(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (Exception e) { + Log.e(TAG, "Failed to get group of " + permissionName, e); + return null; + } + } } diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index ad9e8b3d6dd4..0b99b85fdf24 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -301,6 +301,29 @@ public abstract class PermissionControllerService extends Service { } /** + * Get the platform permissions which belong to a particular permission group + * + * @param permissionGroupName The permission group whose permissions are desired + * @param callback A callback the permission names will be passed to + */ + @BinderThread + public void onGetPlatformPermissionsForGroup(@NonNull String permissionGroupName, + @NonNull Consumer<List<String>> callback) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + + /** + * Get the platform group of a particular permission, if the permission is a platform permission + * + * @param permissionName The permission name whose group is desired + * @param callback A callback the group name will be passed to + */ + @BinderThread + public void onGetGroupOfPlatformPermission(@NonNull String permissionName, + @NonNull Consumer<String> callback) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + /** * Get a user-readable sentence, describing the set of privileges that are to be granted to a * companion app managing a device of the given profile. * @@ -563,6 +586,36 @@ public abstract class PermissionControllerService extends Service { callback.completeExceptionally(t); } } + + @Override + public void getPlatformPermissionsForGroup( + @NonNull String permissionName, + @NonNull AndroidFuture<List<String>> callback) { + try { + Objects.requireNonNull(permissionName); + Objects.requireNonNull(callback); + PermissionControllerService.this.onGetPlatformPermissionsForGroup( + permissionName, callback::complete); + } catch (Throwable t) { + callback.completeExceptionally(t); + } + } + + @Override + public void getGroupOfPlatformPermission( + @NonNull String permissionGroupName, + @NonNull AndroidFuture<String> callback) { + try { + Objects.requireNonNull(permissionGroupName); + Objects.requireNonNull(callback); + PermissionControllerService + .this + .onGetGroupOfPlatformPermission( + permissionGroupName, callback::complete); + } catch (Throwable t) { + callback.completeExceptionally(t); + } + } }; } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 22d74ca33a83..ecb1700bf7d5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6745,9 +6745,8 @@ public final class Settings { /** * The current location time zone detection enabled state for the user. * - * See {@link - * android.app.timezonedetector.TimeZoneDetector#getCapabilities} for access. See {@link - * android.app.timezonedetector.TimeZoneDetector#updateConfiguration} to update. + * See {@link android.app.time.TimeManager#getTimeZoneCapabilitiesAndConfig} for access. + * See {@link android.app.time.TimeManager#updateTimeZoneConfiguration} to update. * @hide */ public static final String LOCATION_TIME_ZONE_DETECTION_ENABLED = diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 7aa5ee51b606..9a855f30a373 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -214,6 +214,17 @@ public class SpeechRecognizer { * command to the created {@code SpeechRecognizer}, otherwise no notifications will be * received. * + * <p>For apps targeting Android 11 (API level 30) interaction with a speech recognition + * service requires <queries> element to be added to the manifest file: + * <pre>{@code + * <queries> + * <intent> + * <action + * android:name="android.speech.RecognitionService" /> + * </intent> + * </queries> + * }</pre> + * * @param context in which to create {@code SpeechRecognizer} * @return a new {@code SpeechRecognizer} */ @@ -231,7 +242,18 @@ public class SpeechRecognizer { * {@link SpeechRecognizer} to. Normally you would not use this; use * {@link #createSpeechRecognizer(Context)} instead to use the system default recognition * service. - * + * + * <p>For apps targeting Android 11 (API level 30) interaction with a speech recognition + * service requires <queries> element to be added to the manifest file: + * <pre>{@code + * <queries> + * <intent> + * <action + * android:name="android.speech.RecognitionService" /> + * </intent> + * </queries> + * }</pre> + * * @param context in which to create {@code SpeechRecognizer} * @param serviceComponent the {@link ComponentName} of a specific service to direct this * {@code SpeechRecognizer} to diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index c62e93463048..9f63500fc853 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -129,6 +129,12 @@ public class HapticFeedbackConstants { public static final int SAFE_MODE_ENABLED = 10001; /** + * Invocation of the voice assistant via hardware button. + * @hide + */ + public static final int ASSISTANT_BUTTON = 10002; + + /** * Flag for {@link View#performHapticFeedback(int, int) * View.performHapticFeedback(int, int)}: Ignore the setting in the * view for whether to perform haptic feedback, do it always. diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index 4a1d68547f70..cdf1e46ac5db 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -59,6 +59,10 @@ per-file ViewRootImpl.java = file:/services/core/java/com/android/server/input/O per-file ViewRootImpl.java = file:/services/core/java/com/android/server/wm/OWNERS per-file ViewRootImpl.java = file:/core/java/android/view/inputmethod/OWNERS per-file AccessibilityInteractionController.java = file:/services/accessibility/OWNERS +per-file OnReceiveContentListener.java = file:/core/java/android/service/autofill/OWNERS +per-file OnReceiveContentListener.java = file:/core/java/android/widget/OWNERS +per-file ContentInfo.java = file:/core/java/android/service/autofill/OWNERS +per-file ContentInfo.java = file:/core/java/android/widget/OWNERS # WindowManager per-file DisplayCutout.aidl = file:/services/core/java/com/android/server/wm/OWNERS diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index fc264f313d70..3ebb10e37b9f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -660,7 +660,6 @@ <protected-broadcast android:name="android.app.action.PROFILE_OWNER_CHANGED" /> <protected-broadcast android:name="android.app.action.TRANSFER_OWNERSHIP_COMPLETE" /> <protected-broadcast android:name="android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE" /> - <protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" /> <protected-broadcast android:name="android.app.action.STATSD_STARTED" /> <protected-broadcast android:name="com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET" /> <protected-broadcast android:name="com.android.server.biometrics.face.ACTION_LOCKOUT_RESET" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index bf790fa5ff95..32732170e92b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3939,10 +3939,6 @@ color supplied by the Notification.Builder if present. --> <bool name="config_tintNotificationActionButtons">true</bool> - <!-- Flag indicating that tinted items (actions, expander, etc) are to be tinted using the - theme color, rather than the notification color. --> - <bool name="config_tintNotificationsWithTheme">true</bool> - <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app --> <bool name="config_showAreaUpdateInfoSettings">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1ab3c849cae7..e607b11e5297 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1889,7 +1889,6 @@ <java-symbol type="bool" name="config_notificationHeaderClickableForExpand" /> <java-symbol type="bool" name="config_enableNightMode" /> <java-symbol type="bool" name="config_tintNotificationActionButtons" /> - <java-symbol type="bool" name="config_tintNotificationsWithTheme" /> <java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" /> <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" /> <java-symbol type="bool" name="config_enableFusedLocationOverlay" /> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 0ea63643d24e..0f8c9e2de826 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.LocusId; import android.graphics.BitmapFactory; +import android.graphics.Color; import android.graphics.drawable.Icon; import android.media.session.MediaSession; import android.os.Build; @@ -60,7 +61,7 @@ public class NotificationTest { public void testColorizedByPermission() { Notification n = new Notification.Builder(mContext, "test") .setFlag(Notification.FLAG_CAN_COLORIZE, true) - .setColorized(true) + .setColorized(true).setColor(Color.WHITE) .build(); assertTrue(n.isColorized()); @@ -71,7 +72,7 @@ public class NotificationTest { n = new Notification.Builder(mContext, "test") .setFlag(Notification.FLAG_CAN_COLORIZE, false) - .setColorized(true) + .setColorized(true).setColor(Color.WHITE) .build(); assertFalse(n.isColorized()); } @@ -80,7 +81,7 @@ public class NotificationTest { public void testColorizedByForeground() { Notification n = new Notification.Builder(mContext, "test") .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) - .setColorized(true) + .setColorized(true).setColor(Color.WHITE) .build(); assertTrue(n.isColorized()); @@ -91,7 +92,7 @@ public class NotificationTest { n = new Notification.Builder(mContext, "test") .setFlag(Notification.FLAG_FOREGROUND_SERVICE, false) - .setColorized(true) + .setColorized(true).setColor(Color.WHITE) .build(); assertFalse(n.isColorized()); } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java index a18a88cb2ca8..60df9688b5a4 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java @@ -103,7 +103,10 @@ public class BatteryUsageStatsTest { } public void validateBatteryUsageStats(BatteryUsageStats batteryUsageStats) { - assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(21500); + // Camera: (10100 + 10200) - 20000 (consumed by apps) = 300 + // App: 300 + 400 + 500 = 1200 + // Total: 1500 + assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(1500); assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(20); assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(1000); assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(2000); diff --git a/drm/java/Android.bp b/drm/java/Android.bp index 54e1a8c0dff8..21fc018f7f4a 100644 --- a/drm/java/Android.bp +++ b/drm/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-drm-sources", srcs: ["**/*.java"], diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp index dcfd5d72a7cb..63d1f6d6f2d6 100644 --- a/graphics/java/Android.bp +++ b/graphics/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-graphics-nonupdatable-sources", srcs: [ diff --git a/identity/Android.bp b/identity/Android.bp new file mode 100644 index 000000000000..826d6f806573 --- /dev/null +++ b/identity/Android.bp @@ -0,0 +1,31 @@ +// +// Copyright (C) 2021 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 { + default_applicable_licenses: ["frameworks_base_identity_license"], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "frameworks_base_identity_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "NOTICE", + ], +} diff --git a/identity/java/Android.bp b/identity/java/Android.bp index 16aef5d2d871..a193d9764c7a 100644 --- a/identity/java/Android.bp +++ b/identity/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_identity_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_identity_license"], +} + filegroup { name: "framework-identity-sources", srcs: ["**/*.java"], diff --git a/keystore/java/Android.bp b/keystore/java/Android.bp index 6860f71a8516..21edff1e1c96 100644 --- a/keystore/java/Android.bp +++ b/keystore/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_keystore_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_keystore_license"], +} + filegroup { name: "framework-keystore-sources", srcs: [ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 9eec48c02306..64bd245cb2ee 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -268,7 +268,7 @@ public class BubbleStackView extends FrameLayout private boolean mIsDraggingStack = false; /** Whether the expanded view has been hidden, because we are dragging out a bubble. */ - private boolean mExpandedViewHidden = false; + private boolean mExpandedViewTemporarilyHidden = false; /** Animator for animating the expanded view's alpha (including the TaskView inside it). */ private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f); @@ -968,7 +968,13 @@ public class BubbleStackView extends FrameLayout @Override public void onAnimationEnd(Animator animation) { - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + if (mExpandedBubble != null + && mExpandedBubble.getExpandedView() != null + // The surface needs to be Z ordered on top for alpha values to work on the + // TaskView, and if we're temporarily hidden, we are still on the screen + // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha + // = 0f remains in effect. + && !mExpandedViewTemporarilyHidden) { mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); mExpandedBubble.getExpandedView().setAlphaAnimating(false); } @@ -983,7 +989,7 @@ public class BubbleStackView extends FrameLayout mAnimatingOutSurfaceAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION); mAnimatingOutSurfaceAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); mAnimatingOutSurfaceAlphaAnimator.addUpdateListener(valueAnimator -> { - if (!mExpandedViewHidden) { + if (!mExpandedViewTemporarilyHidden) { mAnimatingOutSurfaceView.setAlpha((float) valueAnimator.getAnimatedValue()); } }); @@ -1596,7 +1602,7 @@ public class BubbleStackView extends FrameLayout // If we're expanded, screenshot the currently expanded bubble (before expanding the newly // selected bubble) so we can animate it out. if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null - && !mExpandedViewHidden) { + && !mExpandedViewTemporarilyHidden) { if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { // Before screenshotting, have the real ActivityView show on top of other surfaces // so that the screenshot doesn't flicker on top of it. @@ -1722,13 +1728,13 @@ public class BubbleStackView extends FrameLayout /** Animate the expanded view hidden. This is done while we're dragging out a bubble. */ private void hideExpandedViewIfNeeded() { - if (mExpandedViewHidden + if (mExpandedViewTemporarilyHidden || mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { return; } - mExpandedViewHidden = true; + mExpandedViewTemporarilyHidden = true; // Scale down. PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) @@ -1752,11 +1758,11 @@ public class BubbleStackView extends FrameLayout * Animate the expanded view visible again. This is done when we're done dragging out a bubble. */ private void showExpandedViewIfNeeded() { - if (!mExpandedViewHidden) { + if (!mExpandedViewTemporarilyHidden) { return; } - mExpandedViewHidden = false; + mExpandedViewTemporarilyHidden = false; PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) .spring(AnimatableScaleMatrix.SCALE_X, @@ -2085,7 +2091,7 @@ public class BubbleStackView extends FrameLayout mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); }) .withEndActions(() -> { - mExpandedViewHidden = false; + mExpandedViewTemporarilyHidden = false; mIsBubbleSwitchAnimating = false; }) .start(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index a52db24aa184..af4ccadae538 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -540,9 +540,10 @@ public class PipAnimationController { // WindowContainerTransaction in task organizer final Rect destBounds = getDestinationBounds(); getSurfaceTransactionHelper().resetScale(tx, leash, destBounds); - if (transitionDirection == TRANSITION_DIRECTION_LEAVE_PIP) { - // Leaving to fullscreen, reset crop to null. - tx.setPosition(leash, destBounds.left, destBounds.top); + if (isOutPipDirection(transitionDirection)) { + // Exit pip, clear scale, position and crop. + tx.setMatrix(leash, 1, 0, 0, 1); + tx.setPosition(leash, 0, 0); tx.setWindowCrop(leash, 0, 0); } else { getSurfaceTransactionHelper().crop(tx, leash, destBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index ffa821daf7f8..99ec10049340 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -1016,10 +1016,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); wct.scheduleFinishEnterPip(mToken, destinationBounds); } else if (isOutPipDirection(direction)) { - // If we are animating to fullscreen, then we need to reset the override bounds - // on the task to ensure that the task "matches" the parent's bounds. - taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP) - ? null : destinationBounds; + // If we are animating to fullscreen or split screen, then we need to reset the + // override bounds on the task to ensure that the task "matches" the parent's bounds. + taskBounds = null; applyWindowingModeChangeOnExit(wct, direction); } else { // Just a resize in PIP diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index 99a663523d61..c26b686f91fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -16,6 +16,8 @@ package com.android.wm.shell.pip.phone; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + import android.content.Context; import android.content.res.Resources; import android.graphics.PixelFormat; @@ -242,6 +244,7 @@ public class PipDismissTargetHandler { lp.setTitle("pip-dismiss-overlay"); lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; lp.setFitInsetsTypes(0 /* types */); return lp; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 287f68f397ca..d474b6638e4a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -782,7 +782,6 @@ public class PipTouchHandler { private final Point mStartPosition = new Point(); private final PointF mDelta = new PointF(); private boolean mShouldHideMenuAfterFling; - private float mDownSavedFraction = -1f; @Override public void onDown(PipTouchState touchState) { @@ -796,7 +795,6 @@ public class PipTouchHandler { mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mPipBoundsState.getMovementBounds().bottom; mMotionHelper.setSpringingToTouch(false); - mDownSavedFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()); // If the menu is still visible then just poke the menu // so that it will timeout after the user stops touching it @@ -867,9 +865,12 @@ public class PipTouchHandler { if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) { mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */); } else { - mPipUiEventLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); - mPipBoundsState.setStashed(STASH_TYPE_NONE); + if (mPipBoundsState.isStashed()) { + // Reset stashed state if previously stashed + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); + mPipBoundsState.setStashed(STASH_TYPE_NONE); + } mMotionHelper.flingToSnapTarget(vel.x, vel.y, this::flingEndAction /* endAction */); } @@ -896,19 +897,19 @@ public class PipTouchHandler { mMotionHelper.expandLeavePip(); } } else if (mMenuState != MENU_STATE_FULL) { - if (!mTouchState.isWaitingForDoubleTap()) { - if (mPipBoundsState.isStashed()) { - animateToUnStashedState(); - mPipUiEventLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); - mPipBoundsState.setStashed(STASH_TYPE_NONE); - } else { - // User has stalled long enough for this not to be a drag or a double tap, - // just expand the menu - mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(), - true /* allowMenuTimeout */, willResizeMenu(), - shouldShowResizeHandle()); - } + if (mPipBoundsState.isStashed()) { + // Unstash immediately if stashed, and don't wait for the double tap timeout + animateToUnStashedState(); + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); + mPipBoundsState.setStashed(STASH_TYPE_NONE); + mTouchState.removeDoubleTapTimeoutCallback(); + } else if (!mTouchState.isWaitingForDoubleTap()) { + // User has stalled long enough for this not to be a drag or a double tap, + // just expand the menu + mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(), + true /* allowMenuTimeout */, willResizeMenu(), + shouldShowResizeHandle()); } else { // Next touch event _may_ be the second tap for the double-tap, schedule a // fallback runnable to trigger the menu if no touch event occurs before the @@ -916,7 +917,6 @@ public class PipTouchHandler { mTouchState.scheduleDoubleTapTimeoutCallback(); } } - mDownSavedFraction = -1f; return true; } @@ -932,6 +932,7 @@ public class PipTouchHandler { PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT); mPipBoundsState.setStashed(STASH_TYPE_RIGHT); } + mMenuController.hideMenu(); } private void flingEndAction() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipShelfHeightTest.kt index 5713822bba99..84f66fc14969 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipShelfHeightTest.kt @@ -18,13 +18,13 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.helpers.FixedAppHelper import com.google.common.truth.Truth import org.junit.FixMethodOrder import org.junit.Test @@ -33,15 +33,16 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test Pip launch. - * To run this test: `atest WMShellFlickerTests:PipMovesInAllApps` + * Test Pip movement with Launcher shelf height change. + * To run this test: `atest WMShellFlickerTests:PipShelfHeightTest` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class PipMovesInAllApps(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +class PipShelfHeightTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val taplInstrumentation = LauncherInstrumentation() + private val testApp = FixedAppHelper(instrumentation) override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = false) { @@ -49,10 +50,12 @@ class PipMovesInAllApps(testSpec: FlickerTestParameter) : PipTransition(testSpec eachRun { taplInstrumentation.pressHome() } + test { + testApp.exit(wmHelper) + } } transitions { - taplInstrumentation.pressHome().switchToAllApps() - wmHelper.waitForAppTransitionIdle() + testApp.launchViaIntent(wmHelper) } } @@ -68,7 +71,7 @@ class PipMovesInAllApps(testSpec: FlickerTestParameter) : PipTransition(testSpec } } - @FlakyTest(bugId = 184050344) + @Presubmit @Test fun pipWindowMovesUp() = testSpec.assertWmEnd { val initialState = this.trace?.first()?.wmState diff --git a/location/java/Android.bp b/location/java/Android.bp index 996a7ea37adf..543f2b1ab4a8 100644 --- a/location/java/Android.bp +++ b/location/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-location-sources", srcs: [ diff --git a/lowpan/java/Android.bp b/lowpan/java/Android.bp index b95b0daf428e..58513d70042c 100644 --- a/lowpan/java/Android.bp +++ b/lowpan/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-lowpan-sources", srcs: [ diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 21f8623b1953..d746c850a018 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -32,6 +32,7 @@ import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.FileUtils; +import android.os.ParcelFileDescriptor; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -1531,16 +1532,24 @@ public class ExifInterface { if (fileDescriptor == null) { throw new NullPointerException("fileDescriptor cannot be null"); } - FileDescriptor modernFd = FileUtils.convertToModernFd(fileDescriptor); + // If a file descriptor has a modern file descriptor, this means that the file can be + // transcoded and not using the modern file descriptor will trigger the transcoding + // operation. Thus, to avoid unnecessary transcoding, need to convert to modern file + // descriptor if it exists. As of Android S, transcoding is not supported for image files, + // so this is for protecting against non-image files sent to ExifInterface, but support may + // be added in the future. + ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(fileDescriptor); if (modernFd != null) { - fileDescriptor = modernFd; + fileDescriptor = modernFd.getFileDescriptor(); } mAssetInputStream = null; mFilename = null; boolean isFdDuped = false; - if (isSeekableFD(fileDescriptor)) { + // Can't save attributes to files with transcoding because apps get a different copy of + // that file when they're not using it through framework libraries like ExifInterface. + if (isSeekableFD(fileDescriptor) && modernFd == null) { mSeekableFileDescriptor = fileDescriptor; // Keep the original file descriptor in order to save attributes when it's seekable. // Otherwise, just close the given file descriptor after reading it because the save @@ -2545,27 +2554,22 @@ public class ExifInterface { private void initForFilename(String filename) throws IOException { FileInputStream in = null; - FileInputStream legacyInputStream = null; mAssetInputStream = null; mFilename = filename; mIsInputStream = false; try { in = new FileInputStream(filename); - FileDescriptor modernFd = FileUtils.convertToModernFd(in.getFD()); + ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(in.getFD()); if (modernFd != null) { - legacyInputStream = in; - in = new FileInputStream(modernFd); - } - - if (isSeekableFD(in.getFD())) { - mSeekableFileDescriptor = in.getFD(); - } else { + closeQuietly(in); + in = new FileInputStream(modernFd.getFileDescriptor()); mSeekableFileDescriptor = null; + } else if (isSeekableFD(in.getFD())) { + mSeekableFileDescriptor = in.getFD(); } loadAttributes(in); } finally { closeQuietly(in); - closeQuietly(legacyInputStream); } } diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index c13f610a908c..2943eee5b1da 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -33,6 +33,7 @@ import android.os.Build; import android.os.Bundle; import android.os.FileUtils; import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.os.SystemProperties; import android.text.TextUtils; @@ -300,11 +301,11 @@ public class MediaMetadataRetriever implements AutoCloseable { */ public void setDataSource(FileDescriptor fd, long offset, long length) throws IllegalArgumentException { - FileDescriptor modernFd = FileUtils.convertToModernFd(fd); + ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(fd); if (modernFd == null) { _setDataSource(fd, offset, length); } else { - _setDataSource(modernFd, offset, length); + _setDataSource(modernFd.getFileDescriptor(), offset, length); } } diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 644afb79814f..2d8babdc9f94 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -46,6 +46,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Parcel; +import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.PowerManager; @@ -97,7 +98,6 @@ import java.util.UUID; import java.util.Vector; import java.util.concurrent.Executor; - /** * MediaPlayer class can be used to control playback of audio/video files and streams. * @@ -1268,11 +1268,11 @@ public class MediaPlayer extends PlayerBase */ public void setDataSource(FileDescriptor fd, long offset, long length) throws IOException, IllegalArgumentException, IllegalStateException { - FileDescriptor modernFd = FileUtils.convertToModernFd(fd); + ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(fd); if (modernFd == null) { _setDataSource(fd, offset, length); } else { - _setDataSource(modernFd, offset, length); + _setDataSource(modernFd.getFileDescriptor(), offset, length); } } diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java index 5d0f0aa8a921..86ed50bacb63 100644 --- a/media/java/android/media/PlayerBase.java +++ b/media/java/android/media/PlayerBase.java @@ -19,12 +19,10 @@ package android.media; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; -import android.app.AppOpsManager; import android.content.Context; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.text.TextUtils; @@ -51,10 +49,6 @@ public abstract class PlayerBase { private static final boolean DEBUG = DEBUG_APP_OPS || false; private static IAudioService sService; //lazy initialization, use getService() - /** if true, only use OP_PLAY_AUDIO monitoring for logging, and rely on muting to happen - * in AudioFlinger */ - private static final boolean USE_AUDIOFLINGER_MUTING_FOR_OP = true; - // parameters of the player that affect AppOps protected AudioAttributes mAttributes; @@ -112,21 +106,6 @@ public abstract class PlayerBase { * Call from derived class when instantiation / initialization is successful */ protected void baseRegisterPlayer(int sessionId) { - if (!USE_AUDIOFLINGER_MUTING_FOR_OP) { - IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); - mAppOps = IAppOpsService.Stub.asInterface(b); - // initialize mHasAppOpsPlayAudio - updateAppOpsPlayAudio(); - // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed - mAppOpsCallback = new IAppOpsCallbackWrapper(this); - try { - mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO, - ActivityThread.currentPackageName(), mAppOpsCallback); - } catch (RemoteException e) { - Log.e(TAG, "Error registering appOps callback", e); - mHasAppOpsPlayAudio = false; - } - } try { mPlayerIId = getService().trackPlayer( new PlayerIdCard(mImplType, mAttributes, new IPlayerWrapper(this), @@ -150,9 +129,7 @@ public abstract class PlayerBase { Log.e(TAG, "Error talking to audio service, audio attributes will not be updated", e); } synchronized (mLock) { - boolean attributesChanged = (mAttributes != attr); mAttributes = attr; - updateAppOpsPlayAudio_sync(attributesChanged); } } @@ -209,11 +186,6 @@ public abstract class PlayerBase { Log.v(TAG, "baseStart() piid=" + mPlayerIId + " deviceId=" + deviceId); } updateState(AudioPlaybackConfiguration.PLAYER_STATE_STARTED, deviceId); - synchronized (mLock) { - if (isRestricted_sync()) { - playerSetVolume(true/*muting*/,0, 0); - } - } } void baseSetStartDelayMs(int delayMs) { @@ -254,13 +226,11 @@ public abstract class PlayerBase { private void updatePlayerVolume() { final float finalLeftVol, finalRightVol; - final boolean isRestricted; synchronized (mLock) { finalLeftVol = mVolMultiplier * mLeftVolume * mPanMultiplierL; finalRightVol = mVolMultiplier * mRightVolume * mPanMultiplierR; - isRestricted = isRestricted_sync(); } - playerSetVolume(isRestricted /*muting*/, finalLeftVol, finalRightVol); + playerSetVolume(false /*muting*/, finalLeftVol, finalRightVol); } void setVolumeMultiplier(float vol) { @@ -281,9 +251,6 @@ public abstract class PlayerBase { int baseSetAuxEffectSendLevel(float level) { synchronized (mLock) { mAuxEffectSendLevel = level; - if (isRestricted_sync()) { - return AudioSystem.SUCCESS; - } } return playerSetAuxEffectSendLevel(false/*muting*/, level); } @@ -317,98 +284,6 @@ public abstract class PlayerBase { } } - private void updateAppOpsPlayAudio() { - synchronized (mLock) { - updateAppOpsPlayAudio_sync(false); - } - } - - /** - * To be called whenever a condition that might affect audibility of this player is updated. - * Must be called synchronized on mLock. - */ - void updateAppOpsPlayAudio_sync(boolean attributesChanged) { - if (USE_AUDIOFLINGER_MUTING_FOR_OP) { - return; - } - boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio; - try { - int mode = AppOpsManager.MODE_IGNORED; - if (mAppOps != null) { - mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO, - mAttributes.getUsage(), - Process.myUid(), ActivityThread.currentPackageName()); - } - mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED); - } catch (RemoteException e) { - mHasAppOpsPlayAudio = false; - } - - // AppsOps alters a player's volume; when the restriction changes, reflect it on the actual - // volume used by the player - try { - if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio || - attributesChanged) { - getService().playerHasOpPlayAudio(mPlayerIId, mHasAppOpsPlayAudio); - if (!isRestricted_sync()) { - if (DEBUG_APP_OPS) { - Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume - + "/" + mRightVolume); - } - playerSetVolume(false/*muting*/, - mLeftVolume * mPanMultiplierL, mRightVolume * mPanMultiplierR); - playerSetAuxEffectSendLevel(false/*muting*/, mAuxEffectSendLevel); - } else { - if (DEBUG_APP_OPS) { - Log.v(TAG, "updateAppOpsPlayAudio: muting player"); - } - playerSetVolume(true/*muting*/, 0.0f, 0.0f); - playerSetAuxEffectSendLevel(true/*muting*/, 0.0f); - } - } - } catch (Exception e) { - // failing silently, player might not be in right state - } - } - - /** - * To be called by the subclass whenever an operation is potentially restricted. - * As the media player-common behavior are incorporated into this class, the subclass's need - * to call this method should be removed, and this method could become private. - * FIXME can this method be private so subclasses don't have to worry about when to check - * the restrictions. - * @return - */ - boolean isRestricted_sync() { - if (USE_AUDIOFLINGER_MUTING_FOR_OP) { - return false; - } - // check app ops - if (mHasAppOpsPlayAudio) { - return false; - } - // check bypass flag - if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) { - return false; - } - // check force audibility flag and camera restriction - if (((mAttributes.getAllFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED) != 0) - && (mAttributes.getUsage() == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)) { - boolean cameraSoundForced = false; - try { - cameraSoundForced = getService().isCameraSoundForced(); - } catch (RemoteException e) { - Log.e(TAG, "Cannot access AudioService in isRestricted_sync()"); - } catch (NullPointerException e) { - Log.e(TAG, "Null AudioService in isRestricted_sync()"); - } - if (cameraSoundForced) { - return false; - } - } - return true; - } - private static IAudioService getService() { if (sService != null) { @@ -478,26 +353,6 @@ public abstract class PlayerBase { abstract void playerStop(); //===================================================================== - private static class IAppOpsCallbackWrapper extends IAppOpsCallback.Stub { - private final WeakReference<PlayerBase> mWeakPB; - - public IAppOpsCallbackWrapper(PlayerBase pb) { - mWeakPB = new WeakReference<PlayerBase>(pb); - } - - @Override - public void opChanged(int op, int uid, String packageName) { - if (op == AppOpsManager.OP_PLAY_AUDIO) { - if (DEBUG_APP_OPS) { Log.v(TAG, "opChanged: op=PLAY_AUDIO pack=" + packageName); } - final PlayerBase pb = mWeakPB.get(); - if (pb != null) { - pb.updateAppOpsPlayAudio(); - } - } - } - } - - //===================================================================== /** * Wrapper around an implementation of IPlayer for all subclasses of PlayerBase * that doesn't keep a strong reference on PlayerBase diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index 6141b7fc7463..860d88afe4a2 100755 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -805,8 +805,10 @@ public class MtpDatabase implements AutoCloseable { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteStream); - if (byteStream.size() > MAX_THUMB_SIZE) + if (byteStream.size() > MAX_THUMB_SIZE) { + Log.w(TAG, "getThumbnailProcess: size=" + byteStream.size()); return null; + } byte[] byteArray = byteStream.toByteArray(); @@ -836,7 +838,15 @@ public class MtpDatabase implements AutoCloseable { outLongs[0] = thumbOffsetAndSize != null ? thumbOffsetAndSize[1] : 0; outLongs[1] = exif.getAttributeInt(ExifInterface.TAG_PIXEL_X_DIMENSION, 0); outLongs[2] = exif.getAttributeInt(ExifInterface.TAG_PIXEL_Y_DIMENSION, 0); - return true; + if (exif.getThumbnailRange() != null) { + if ((outLongs[0] == 0) || (outLongs[1] == 0) || (outLongs[2] == 0)) { + Log.d(TAG, "getThumbnailInfo: check thumb info:" + + thumbOffsetAndSize[0] + "," + thumbOffsetAndSize[1] + + "," + outLongs[1] + "," + outLongs[2]); + } + + return true; + } } catch (IOException e) { // ignore and fall through } @@ -869,7 +879,9 @@ public class MtpDatabase implements AutoCloseable { case MtpConstants.FORMAT_JFIF: try { ExifInterface exif = new ExifInterface(path); - return exif.getThumbnail(); + + if (exif.getThumbnailRange() != null) + return exif.getThumbnail(); } catch (IOException e) { // ignore and fall through } diff --git a/media/mca/effect/java/Android.bp b/media/mca/effect/java/Android.bp index 708167c94607..70d999f76483 100644 --- a/media/mca/effect/java/Android.bp +++ b/media/mca/effect/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-mca-effect-sources", srcs: ["**/*.java"], diff --git a/media/mca/filterfw/java/Android.bp b/media/mca/filterfw/java/Android.bp index 51be85b46602..77afcff27f56 100644 --- a/media/mca/filterfw/java/Android.bp +++ b/media/mca/filterfw/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-mca-filterfw-sources", srcs: ["**/*.java"], diff --git a/media/mca/filterpacks/java/Android.bp b/media/mca/filterpacks/java/Android.bp index d9271b95f6d2..f370b21589c5 100644 --- a/media/mca/filterpacks/java/Android.bp +++ b/media/mca/filterpacks/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-mca-filterpacks-sources", srcs: ["**/*.java"], diff --git a/media/tests/MtpTests/res/raw/test_bad_thumb.jpg b/media/tests/MtpTests/res/raw/test_bad_thumb.jpg Binary files differindex e69de29bb2d1..78ac703850a1 100644 --- a/media/tests/MtpTests/res/raw/test_bad_thumb.jpg +++ b/media/tests/MtpTests/res/raw/test_bad_thumb.jpg diff --git a/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java index dfbafe496de5..48be6fea845f 100644 --- a/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java +++ b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java @@ -271,9 +271,10 @@ public class MtpDatabaseTest { Log.d(TAG, "testMtpDatabaseThumbnail: Test bad JPG"); - testThumbnail(handleJpgBadThumb, jpgfileBadThumb, false); +// Now we support to generate thumbnail if embedded thumbnail is corrupted or not existed + testThumbnail(handleJpgBadThumb, jpgfileBadThumb, true); - testThumbnail(handleJpgNoThumb, jpgFileNoThumb, false); + testThumbnail(handleJpgNoThumb, jpgFileNoThumb, true); testThumbnail(handleJpgBad, jpgfileBad, false); diff --git a/mime/java/Android.bp b/mime/java/Android.bp index 7e562639d4cc..07cada8e1372 100644 --- a/mime/java/Android.bp +++ b/mime/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-mime-sources", srcs: ["**/*.java"], diff --git a/mms/java/Android.bp b/mms/java/Android.bp index 367d8c35fdce..4d51439392fa 100644 --- a/mms/java/Android.bp +++ b/mms/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-mms-sources", srcs: [ diff --git a/opengl/java/Android.bp b/opengl/java/Android.bp index 8ed4161cb9de..6dbae421e059 100644 --- a/opengl/java/Android.bp +++ b/opengl/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-opengl-sources", srcs: ["**/*.java"], diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index 2cae99fbb326..5613ca1562f7 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -232,6 +232,7 @@ package android.net { method @NonNull public android.net.Network register(); method public final void sendLinkProperties(@NonNull android.net.LinkProperties); method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); + method public final void sendNetworkScore(@NonNull android.net.NetworkScore); method public final void sendNetworkScore(@IntRange(from=0, to=99) int); method public final void sendQosCallbackError(int, int); method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes); diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java index c57da53f289d..3622c1c669db 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java +++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java @@ -956,7 +956,6 @@ public abstract class NetworkAgent { * Must be called by the agent to update the score of this network. * * @param score the new score. - * @hide TODO : unhide when impl is complete */ public final void sendNetworkScore(@NonNull NetworkScore score) { Objects.requireNonNull(score); diff --git a/packages/Connectivity/framework/src/android/net/NetworkScore.java b/packages/Connectivity/framework/src/android/net/NetworkScore.java index 1c235f470157..0dee225a0e89 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkScore.java +++ b/packages/Connectivity/framework/src/android/net/NetworkScore.java @@ -170,7 +170,7 @@ public final class NetworkScore implements Parcelable { @Override public String toString() { - return "Score(" + mLegacyInt + ")"; + return "Score(" + mLegacyInt + " ; Policies : " + mPolicies + ")"; } @Override diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java index 1ccf4175bac5..781bfcdbc75e 100644 --- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java +++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java @@ -51,7 +51,6 @@ public class AppSwitchPreference extends SwitchPreference { @Override public void onBindViewHolder(PreferenceViewHolder holder) { - setSingleLineTitle(true); super.onBindViewHolder(holder); final View switchView = holder.findViewById(android.R.id.switch_widget); if (switchView != null) { diff --git a/packages/SystemUI/res/drawable/global_actions_lite_background.xml b/packages/SystemUI/res/drawable/global_actions_lite_background.xml new file mode 100644 index 000000000000..3fdce761b135 --- /dev/null +++ b/packages/SystemUI/res/drawable/global_actions_lite_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +* Copyright 2021, The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/global_actions_lite_background"/> + <corners android:radius="@dimen/global_actions_corner_radius" /> +</shape> diff --git a/packages/SystemUI/res/drawable/global_actions_lite_button.xml b/packages/SystemUI/res/drawable/global_actions_lite_button.xml new file mode 100644 index 000000000000..b0184e9bc207 --- /dev/null +++ b/packages/SystemUI/res/drawable/global_actions_lite_button.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +* Copyright 2021, The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid android:color="@color/global_actions_lite_button_background"/> +</shape> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_lite.xml b/packages/SystemUI/res/layout/global_actions_grid_item_lite.xml new file mode 100644 index 000000000000..9ab1ac80f048 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_item_lite.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<com.android.systemui.globalactions.GlobalActionsItem + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:gravity="center" + android:layout_gravity="center" + android:orientation="vertical" + android:stateListAnimator="@anim/control_state_list_animator"> + <ImageView + android:id="@*android:id/icon" + android:layout_width="@dimen/global_actions_button_size" + android:layout_height="@dimen/global_actions_button_size" + android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin" + android:padding="@dimen/global_actions_button_padding" + android:scaleType="centerInside" + android:tint="@color/global_actions_lite_text" + android:background="@drawable/global_actions_lite_button"/> + <TextView + android:id="@*android:id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:marqueeRepeatLimit="marquee_forever" + android:maxLines="2" + android:textSize="12sp" + android:gravity="center" + android:textColor="@color/global_actions_lite_text" + android:breakStrategy="high_quality" + android:hyphenationFrequency="full" + android:textAppearance="?android:attr/textAppearanceSmall" /> +</com.android.systemui.globalactions.GlobalActionsItem> diff --git a/packages/SystemUI/res/layout/global_actions_grid_lite.xml b/packages/SystemUI/res/layout/global_actions_grid_lite.xml new file mode 100644 index 000000000000..0df980054c0c --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_lite.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/global_actions_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center" + android:layout_gravity="center"> + <com.android.systemui.globalactions.GlobalActionsLayoutLite + android:id="@id/global_actions_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:clipChildren="false" + android:clipToPadding="false" + android:background="@drawable/global_actions_lite_background" + android:padding="@dimen/global_actions_lite_padding"> + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@android:id/list" + android:gravity="center" + android:translationZ="@dimen/global_actions_translate" + android:orientation="horizontal" + android:layoutDirection="ltr"> + <androidx.constraintlayout.helper.widget.Flow + android:id="@+id/list_flow" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:flow_wrapMode="chain" + app:flow_maxElementsWrap="2" + app:flow_horizontalGap="@dimen/global_actions_lite_padding" + app:flow_verticalGap="@dimen/global_actions_lite_padding" + app:flow_horizontalStyle="packed"/> + </androidx.constraintlayout.widget.ConstraintLayout> + </com.android.systemui.globalactions.GlobalActionsLayoutLite> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml index 6d44138c5354..8b2d4e0a44b9 100644 --- a/packages/SystemUI/res/layout/long_screenshot.xml +++ b/packages/SystemUI/res/layout/long_screenshot.xml @@ -42,6 +42,8 @@ android:padding="6dp" android:src="@drawable/ic_screenshot_share" android:layout_marginRight="8dp" + android:contentDescription="@*android:string/share" + android:tooltipText="@*android:string/share" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@id/preview" /> @@ -115,4 +117,3 @@ /> </androidx.constraintlayout.widget.ConstraintLayout> - diff --git a/packages/SystemUI/res/layout/people_space_tile_view.xml b/packages/SystemUI/res/layout/people_space_tile_view.xml index 8f64ddf7011d..3e901808e66a 100644 --- a/packages/SystemUI/res/layout/people_space_tile_view.xml +++ b/packages/SystemUI/res/layout/people_space_tile_view.xml @@ -35,15 +35,8 @@ <ImageView android:id="@+id/tile_view_person_icon" - android:layout_width="48dp" - android:layout_height="48dp" /> - - <ImageView - android:id="@+id/tile_view_package_icon" - android:layout_width="16dp" - android:layout_marginStart="-8dp" - android:layout_marginTop="32dp" - android:layout_height="16dp" /> + android:layout_width="52dp" + android:layout_height="52dp" /> <LinearLayout android:orientation="horizontal" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index e3bf12495a82..e77a8a4fbdf0 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -54,6 +54,13 @@ <color name="global_actions_emergency_background">@color/GM2_red_400</color> <color name="global_actions_emergency_text">@color/GM2_grey_100</color> + <!-- Colors for Power Menu Lite --> + <color name="global_actions_lite_background">#191C18</color> + <color name="global_actions_lite_button_background">#303030</color> + <color name="global_actions_lite_text">#F0F0F0</color> + <color name="global_actions_lite_emergency_background">#F85D4D</color> + <color name="global_actions_lite_emergency_icon">@color/GM2_grey_900</color> + <color name="global_actions_shutdown_ui_text">@color/control_primary_text</color> <!-- Tint color for the content on the notification overflow card. --> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 53a42aeb2b87..1d82518da619 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -570,6 +570,10 @@ <!-- Max number of columns for power menu --> <integer name="power_menu_max_columns">3</integer> + <!-- Max number of columns for power menu lite --> + <integer name="power_menu_lite_max_columns">2</integer> + <!-- Max number of rows for power menu lite --> + <integer name="power_menu_lite_max_rows">4</integer> <!-- If the dp width of the available space is <= this value, potentially adjust the number of columns--> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 1bd12a3deca3..91e0466c82fe 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1065,6 +1065,12 @@ <dimen name="global_actions_power_dialog_item_width">255dp</dimen> <dimen name="global_actions_power_dialog_item_bottom_margin">45dp</dimen> + <!-- Power Menu Lite --> + <dimen name="global_actions_button_size">96dp</dimen> + <dimen name="global_actions_button_padding">38dp</dimen> + <dimen name="global_actions_corner_radius">28dp</dimen> + <dimen name="global_actions_lite_padding">24dp</dimen> + <!-- The maximum offset in either direction that elements are moved horizontally to prevent burn-in on AOD. --> <dimen name="burn_in_prevention_offset_x">8dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index cb21245fa639..10c70a0914c8 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -395,6 +395,13 @@ <item name="android:windowIsFloating">true</item> </style> + <style name="Theme.SystemUI.Dialog.GlobalActionsLite" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen"> + <item name="android:windowIsFloating">true</item> + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:backgroundDimEnabled">true</item> + <item name="android:windowCloseOnTouchOutside">true</item> + </style> + <style name="Theme.SystemUI.Dialog.MediaOutput"> <item name="android:windowBackground">@drawable/media_output_dialog_background</item> </style> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index 02a8958ef657..31f1332b265c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -492,4 +492,11 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> mKeyguardSecurityContainerController.updateResources(); } } + + /** Update keyguard position based on a tapped X coordinate. */ + public void updateKeyguardPosition(float x) { + if (mKeyguardSecurityContainerController != null) { + mKeyguardSecurityContainerController.updateKeyguardPosition(x); + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 708b2d55b75a..7ed63375a334 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -267,6 +267,13 @@ public class KeyguardSecurityContainer extends FrameLayout { updateSecurityViewLocation(false); } + /** Update keyguard position based on a tapped X coordinate. */ + public void updateKeyguardPosition(float x) { + if (mOneHandedMode) { + moveBouncerForXCoordinate(x, /* animate= */false); + } + } + /** Return whether the one-handed keyguard should be enabled. */ private boolean canUseOneHandedBouncer() { // Is it enabled? @@ -488,9 +495,13 @@ public class KeyguardSecurityContainer extends FrameLayout { return; } + moveBouncerForXCoordinate(event.getX(), /* animate= */true); + } + + private void moveBouncerForXCoordinate(float x, boolean animate) { // Did the tap hit the "other" side of the bouncer? - if ((mIsSecurityViewLeftAligned && (event.getX() > getWidth() / 2f)) - || (!mIsSecurityViewLeftAligned && (event.getX() < getWidth() / 2f))) { + if ((mIsSecurityViewLeftAligned && (x > getWidth() / 2f)) + || (!mIsSecurityViewLeftAligned && (x < getWidth() / 2f))) { mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned; Settings.Global.putInt( @@ -499,7 +510,7 @@ public class KeyguardSecurityContainer extends FrameLayout { mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); - updateSecurityViewLocation(true); + updateSecurityViewLocation(animate); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 760eaecae247..4827cab3b5c0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -515,6 +515,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } } + /** Update keyguard position based on a tapped X coordinate. */ + public void updateKeyguardPosition(float x) { + mView.updateKeyguardPosition(x); + } + static class Factory { private final KeyguardSecurityContainer mView; diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt index 464bee18f030..f8a20023e47a 100644 --- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt +++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt @@ -23,11 +23,11 @@ import android.text.TextUtils import com.android.systemui.R -interface CameraIntents { +class CameraIntents { companion object { - const val DEFAULT_SECURE_CAMERA_INTENT_ACTION = + val DEFAULT_SECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE - const val DEFAULT_INSECURE_CAMERA_INTENT_ACTION = + val DEFAULT_INSECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA @JvmStatic @@ -59,13 +59,13 @@ interface CameraIntents { } @JvmStatic - fun isSecureCameraIntent(intent: Intent): Boolean { - return intent.getAction().equals(DEFAULT_SECURE_CAMERA_INTENT_ACTION) + fun isSecureCameraIntent(intent: Intent?): Boolean { + return intent?.getAction()?.equals(DEFAULT_SECURE_CAMERA_INTENT_ACTION) ?: false } @JvmStatic - fun isInsecureCameraIntent(intent: Intent): Boolean { - return intent.getAction().equals(DEFAULT_INSECURE_CAMERA_INTENT_ACTION) + fun isInsecureCameraIntent(intent: Intent?): Boolean { + return intent?.getAction()?.equals(DEFAULT_INSECURE_CAMERA_INTENT_ACTION) ?: false } } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 18627189f188..34d94d8e13bb 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -15,16 +15,8 @@ package com.android.systemui.globalactions; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; -import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; - -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; + import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; @@ -34,92 +26,47 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.Nullable; -import android.app.ActivityManager; -import android.app.Dialog; import android.app.IActivityManager; import android.app.PendingIntent; -import android.app.StatusBarManager; -import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; -import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.content.res.ColorStateList; -import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; -import android.graphics.Color; import android.graphics.drawable.Drawable; import android.media.AudioManager; -import android.net.ConnectivityManager; -import android.os.Binder; import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.RemoteException; -import android.os.SystemProperties; -import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; import android.service.dreams.IDreamManager; -import android.sysprop.TelephonyProperties; import android.telecom.TelecomManager; -import android.telephony.ServiceState; -import android.telephony.TelephonyCallback; -import android.telephony.TelephonyManager; import android.transition.AutoTransition; import android.transition.TransitionManager; import android.transition.TransitionSet; -import android.util.ArraySet; import android.util.Log; -import android.view.ContextThemeWrapper; import android.view.IWindowManager; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; -import android.view.accessibility.AccessibilityEvent; -import android.widget.BaseAdapter; import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.ImageView.ScaleType; -import android.widget.LinearLayout; -import android.widget.ListPopupWindow; import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.LifecycleRegistry; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.colorextraction.ColorExtractor.GradientColors; -import com.android.internal.colorextraction.drawable.ScrimDrawable; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.util.EmergencyAffordanceManager; -import com.android.internal.util.ScreenshotHelper; import com.android.internal.view.RotationPolicy; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Interpolators; -import com.android.systemui.MultiListLayout; -import com.android.systemui.MultiListLayout.MultiListAdapter; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.controls.ControlsServiceInfo; @@ -139,9 +86,10 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; -import com.android.systemui.util.EmergencyDialerConstants; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.leak.RotationUtils; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.settings.SecureSettings; import java.util.ArrayList; import java.util.Collections; @@ -157,144 +105,37 @@ import javax.inject.Provider; /** * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending * on whether the keyguard is showing, and whether the device is provisioned. + * This version includes wallet and controls. */ -public class GlobalActionsDialog implements DialogInterface.OnDismissListener, +public class GlobalActionsDialog extends GlobalActionsDialogLite + implements DialogInterface.OnDismissListener, DialogInterface.OnShowListener, ConfigurationController.ConfigurationListener, GlobalActionsPanelPlugin.Callbacks, LifecycleOwner { - public static final String SYSTEM_DIALOG_REASON_KEY = "reason"; - public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; - public static final String SYSTEM_DIALOG_REASON_DREAM = "dream"; - private static final String TAG = "GlobalActionsDialog"; - private static final boolean SHOW_SILENT_TOGGLE = true; - - /* Valid settings for global actions keys. - * see config.xml config_globalActionList */ - @VisibleForTesting - static final String GLOBAL_ACTION_KEY_POWER = "power"; - private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; - static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; - private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; - private static final String GLOBAL_ACTION_KEY_USERS = "users"; - private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; - static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; - private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; - private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; - static final String GLOBAL_ACTION_KEY_RESTART = "restart"; - private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout"; - static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency"; - static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; - public static final String PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"; public static final String PREFS_CONTROLS_FILE = "controls_prefs"; private static final int SEEDING_MAX = 2; - private final Context mContext; - private final GlobalActionsManager mWindowManagerFuncs; - private final AudioManager mAudioManager; - private final IDreamManager mDreamManager; - private final DevicePolicyManager mDevicePolicyManager; private final LockPatternUtils mLockPatternUtils; private final KeyguardStateController mKeyguardStateController; - private final BroadcastDispatcher mBroadcastDispatcher; - private final ContentResolver mContentResolver; - private final Resources mResources; - private final ConfigurationController mConfigurationController; - private final UserManager mUserManager; - private final TrustManager mTrustManager; - private final IActivityManager mIActivityManager; - private final TelecomManager mTelecomManager; - private final MetricsLogger mMetricsLogger; - private final UiEventLogger mUiEventLogger; private final NotificationShadeDepthController mDepthController; private final SysUiState mSysUiState; - - // Used for RingerModeTracker - private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); - - @VisibleForTesting - protected final ArrayList<Action> mItems = new ArrayList<>(); - @VisibleForTesting - protected final ArrayList<Action> mOverflowItems = new ArrayList<>(); - @VisibleForTesting - protected final ArrayList<Action> mPowerItems = new ArrayList<>(); - - @VisibleForTesting - protected ActionsDialog mDialog; - - private Action mSilentModeAction; - private ToggleAction mAirplaneModeOn; - - private MyAdapter mAdapter; - private MyOverflowAdapter mOverflowAdapter; - private MyPowerOptionsAdapter mPowerAdapter; - - private boolean mKeyguardShowing = false; - private boolean mDeviceProvisioned = false; - private ToggleState mAirplaneState = ToggleState.Off; - private boolean mIsWaitingForEcmExit = false; - private boolean mHasTelephony; - private boolean mHasVibrator; - private final boolean mShowSilentToggle; - private final EmergencyAffordanceManager mEmergencyAffordanceManager; - private final ScreenshotHelper mScreenshotHelper; private final ActivityStarter mActivityStarter; private final SysuiColorExtractor mSysuiColorExtractor; private final IStatusBarService mStatusBarService; private final NotificationShadeWindowController mNotificationShadeWindowController; private GlobalActionsPanelPlugin mWalletPlugin; private Optional<ControlsUiController> mControlsUiControllerOptional; - private final IWindowManager mIWindowManager; - private final Executor mBackgroundExecutor; private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>(); private ControlsComponent mControlsComponent; private Optional<ControlsController> mControlsControllerOptional; - private final RingerModeTracker mRingerModeTracker; - private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms - private Handler mMainHandler; private UserContextProvider mUserContextProvider; @VisibleForTesting boolean mShowLockScreenCardsAndControls = false; - private int mSmallestScreenWidthDp; - - @VisibleForTesting - public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { - @UiEvent(doc = "The global actions / power menu surface became visible on the screen.") - GA_POWER_MENU_OPEN(337), - - @UiEvent(doc = "The global actions / power menu surface was dismissed.") - GA_POWER_MENU_CLOSE(471), - - @UiEvent(doc = "The global actions bugreport button was pressed.") - GA_BUGREPORT_PRESS(344), - - @UiEvent(doc = "The global actions bugreport button was long pressed.") - GA_BUGREPORT_LONG_PRESS(345), - - @UiEvent(doc = "The global actions emergency button was pressed.") - GA_EMERGENCY_DIALER_PRESS(346), - - @UiEvent(doc = "The global actions screenshot button was pressed.") - GA_SCREENSHOT_PRESS(347), - - @UiEvent(doc = "The global actions screenshot button was long pressed.") - GA_SCREENSHOT_LONG_PRESS(348); - - private final int mId; - - GlobalActionsEvent(int id) { - mId = id; - } - - @Override - public int getId() { - return mId; - } - } /** * @param context everything needs a context :( @@ -304,9 +145,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, AudioManager audioManager, IDreamManager iDreamManager, DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, - ConnectivityManager connectivityManager, TelephonyListenerManager telephonyListenerManager, - ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources, + GlobalSettings globalSettings, SecureSettings secureSettings, + @Nullable Vibrator vibrator, @Main Resources resources, ConfigurationController configurationController, ActivityStarter activityStarter, KeyguardStateController keyguardStateController, UserManager userManager, TrustManager trustManager, IActivityManager iActivityManager, @@ -320,83 +161,52 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler, ControlsComponent controlsComponent, UserContextProvider userContextProvider) { - mContext = context; - mWindowManagerFuncs = windowManagerFuncs; - mAudioManager = audioManager; - mDreamManager = iDreamManager; - mDevicePolicyManager = devicePolicyManager; + + super(context, windowManagerFuncs, + audioManager, iDreamManager, + devicePolicyManager, lockPatternUtils, + broadcastDispatcher, telephonyListenerManager, + globalSettings, secureSettings, + vibrator, resources, + configurationController, + keyguardStateController, userManager, + trustManager, iActivityManager, + telecomManager, metricsLogger, + depthController, colorExtractor, + statusBarService, + notificationShadeWindowController, + iWindowManager, + backgroundExecutor, + uiEventLogger, + ringerModeTracker, sysUiState, handler); + mLockPatternUtils = lockPatternUtils; mKeyguardStateController = keyguardStateController; - mBroadcastDispatcher = broadcastDispatcher; - mContentResolver = contentResolver; - mResources = resources; - mConfigurationController = configurationController; - mUserManager = userManager; - mTrustManager = trustManager; - mIActivityManager = iActivityManager; - mTelecomManager = telecomManager; - mMetricsLogger = metricsLogger; - mUiEventLogger = uiEventLogger; mDepthController = depthController; mSysuiColorExtractor = colorExtractor; mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; mControlsComponent = controlsComponent; mControlsUiControllerOptional = controlsComponent.getControlsUiController(); - mIWindowManager = iWindowManager; - mBackgroundExecutor = backgroundExecutor; - mRingerModeTracker = ringerModeTracker; mControlsControllerOptional = controlsComponent.getControlsController(); mSysUiState = sysUiState; - mMainHandler = handler; mUserContextProvider = userContextProvider; - mSmallestScreenWidthDp = mContext.getResources().getConfiguration().smallestScreenWidthDp; - - // receive broadcasts - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); - mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter); - - mHasTelephony = - context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); - - // get notified of phone state changes - telephonyListenerManager.addServiceStateListener(mPhoneStateListener); - contentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, - mAirplaneModeObserver); - mHasVibrator = vibrator != null && vibrator.hasVibrator(); - - mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean( - R.bool.config_useFixedVolume); - if (mShowSilentToggle) { - mRingerModeTracker.getRingerMode().observe(this, ringer -> - mHandler.sendEmptyMessage(MESSAGE_REFRESH) - ); - } - - mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); - mScreenshotHelper = new ScreenshotHelper(context); - - mConfigurationController.addCallback(this); - mActivityStarter = activityStarter; keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onUnlockedChanged() { if (mDialog != null) { + ActionsDialog dialog = (ActionsDialog) mDialog; boolean unlocked = mKeyguardStateController.isUnlocked(); - if (mDialog.mWalletViewController != null) { - mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked); + if (dialog.mWalletViewController != null) { + dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked); } - if (!mDialog.isShowingControls() + if (!dialog.isShowingControls() && mControlsComponent.getVisibility() == AVAILABLE) { - mDialog.showControls(mControlsUiControllerOptional.get()); + dialog.showControls(mControlsUiControllerOptional.get()); } if (unlocked) { - mDialog.hideLockMessage(); + dialog.hideLockMessage(); } } } @@ -409,11 +219,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // This callback may occur after the dialog has been shown. If so, add // controls into the already visible space or show the lock msg if needed. if (mDialog != null) { - if (!mDialog.isShowingControls() + ActionsDialog dialog = (ActionsDialog) mDialog; + if (!dialog.isShowingControls() && mControlsComponent.getVisibility() == AVAILABLE) { - mDialog.showControls(mControlsUiControllerOptional.get()); - } else if (shouldShowLockMessage(mDialog)) { - mDialog.showLockMessage(); + dialog.showControls(mControlsUiControllerOptional.get()); + } else if (shouldShowLockMessage(dialog)) { + dialog.showLockMessage(); } } }); @@ -421,10 +232,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // Listen for changes to show controls on the power menu while locked onPowerMenuLockScreenSettingsChanged(); - mContext.getContentResolver().registerContentObserver( + mGlobalSettings.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), false /* notifyForDescendants */, - new ContentObserver(mMainHandler) { + new ContentObserver(handler) { @Override public void onChange(boolean selfChange) { onPowerMenuLockScreenSettingsChanged(); @@ -443,7 +254,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return; } - String[] preferredControlsPackages = mContext.getResources() + String[] preferredControlsPackages = getContext().getResources() .getStringArray(com.android.systemui.R.array.config_controlsPreferredPackages); SharedPreferences prefs = mUserContextProvider.getUserContext() @@ -498,67 +309,14 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, */ public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned, GlobalActionsPanelPlugin walletPlugin) { - mKeyguardShowing = keyguardShowing; - mDeviceProvisioned = isDeviceProvisioned; mWalletPlugin = walletPlugin; - if (mDialog != null && mDialog.isShowing()) { - // In order to force global actions to hide on the same affordance press, we must - // register a call to onGlobalActionsShown() first to prevent the default actions - // menu from showing. This will be followed by a subsequent call to - // onGlobalActionsHidden() on dismiss() - mWindowManagerFuncs.onGlobalActionsShown(); - mDialog.dismiss(); - mDialog = null; - } else { - handleShow(); - } + super.showOrHideDialog(keyguardShowing, isDeviceProvisioned); } - /** - * Dismiss the global actions dialog, if it's currently shown - */ - public void dismissDialog() { - mHandler.removeMessages(MESSAGE_DISMISS); - mHandler.sendEmptyMessage(MESSAGE_DISMISS); - } - - private void awakenIfNecessary() { - if (mDreamManager != null) { - try { - if (mDreamManager.isDreaming()) { - mDreamManager.awaken(); - } - } catch (RemoteException e) { - // we tried - } - } - } - - private void handleShow() { - awakenIfNecessary(); - mDialog = createDialog(); - prepareDialog(); + @Override + protected void handleShow() { seedFavorites(); - - WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); - attrs.setTitle("ActionsDialog"); - attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mDialog.getWindow().setAttributes(attrs); - // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports - mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM); - mDialog.show(); - mWindowManagerFuncs.onGlobalActionsShown(); - } - - @VisibleForTesting - protected boolean shouldShowAction(Action action) { - if (mKeyguardShowing && !action.showDuringKeyguard()) { - return false; - } - if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { - return false; - } - return action.shouldShow(); + super.handleShow(); } /** @@ -566,134 +324,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, * layout is being used. */ @VisibleForTesting + @Override protected int getMaxShownPowerItems() { - return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns); - } - - /** - * Add a power menu action item for to either the main or overflow items lists, depending on - * whether controls are enabled and whether the max number of shown items has been reached. - */ - private void addActionItem(Action action) { - if (mItems.size() < getMaxShownPowerItems()) { - mItems.add(action); - } else { - mOverflowItems.add(action); - } - } - - @VisibleForTesting - protected String[] getDefaultActions() { - return mResources.getStringArray(R.array.config_globalActionsList); - } - - private void addIfShouldShowAction(List<Action> actions, Action action) { - if (shouldShowAction(action)) { - actions.add(action); - } - } - - @VisibleForTesting - protected void createActionItems() { - // Simple toggle style if there's no vibrator, otherwise use a tri-state - if (!mHasVibrator) { - mSilentModeAction = new SilentModeToggleAction(); - } else { - mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler); - } - mAirplaneModeOn = new AirplaneModeAction(); - onAirplaneModeChanged(); - - mItems.clear(); - mOverflowItems.clear(); - mPowerItems.clear(); - String[] defaultActions = getDefaultActions(); - - ShutDownAction shutdownAction = new ShutDownAction(); - RestartAction restartAction = new RestartAction(); - ArraySet<String> addedKeys = new ArraySet<String>(); - List<Action> tempActions = new ArrayList<>(); - CurrentUserProvider currentUser = new CurrentUserProvider(); - - // make sure emergency affordance action is first, if needed - if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { - addIfShouldShowAction(tempActions, new EmergencyAffordanceAction()); - addedKeys.add(GLOBAL_ACTION_KEY_EMERGENCY); - } - - for (int i = 0; i < defaultActions.length; i++) { - String actionKey = defaultActions[i]; - if (addedKeys.contains(actionKey)) { - // If we already have added this, don't add it again. - continue; - } - if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { - addIfShouldShowAction(tempActions, shutdownAction); - } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { - addIfShouldShowAction(tempActions, mAirplaneModeOn); - } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { - if (shouldDisplayBugReport(currentUser.get())) { - addIfShouldShowAction(tempActions, new BugReportAction()); - } - } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) { - if (mShowSilentToggle) { - addIfShouldShowAction(tempActions, mSilentModeAction); - } - } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { - if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { - addUserActions(tempActions, currentUser.get()); - } - } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { - addIfShouldShowAction(tempActions, getSettingsAction()); - } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { - if (shouldDisplayLockdown(currentUser.get())) { - addIfShouldShowAction(tempActions, new LockDownAction()); - } - } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { - addIfShouldShowAction(tempActions, getVoiceAssistAction()); - } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { - addIfShouldShowAction(tempActions, getAssistAction()); - } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { - addIfShouldShowAction(tempActions, restartAction); - } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) { - addIfShouldShowAction(tempActions, new ScreenshotAction()); - } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) { - if (mDevicePolicyManager.isLogoutEnabled() - && currentUser.get() != null - && currentUser.get().id != UserHandle.USER_SYSTEM) { - addIfShouldShowAction(tempActions, new LogoutAction()); - } - } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) { - addIfShouldShowAction(tempActions, new EmergencyDialerAction()); - } else { - Log.e(TAG, "Invalid global action key " + actionKey); - } - // Add here so we don't add more than one. - addedKeys.add(actionKey); - } - - // replace power and restart with a single power options action, if needed - if (tempActions.contains(shutdownAction) && tempActions.contains(restartAction) - && tempActions.size() > getMaxShownPowerItems()) { - // transfer shutdown and restart to their own list of power actions - int powerOptionsIndex = Math.min(tempActions.indexOf(restartAction), - tempActions.indexOf(shutdownAction)); - tempActions.remove(shutdownAction); - tempActions.remove(restartAction); - mPowerItems.add(shutdownAction); - mPowerItems.add(restartAction); - - // add the PowerOptionsAction after Emergency, if present - tempActions.add(powerOptionsIndex, new PowerOptionsAction()); - } - for (Action action : tempActions) { - addActionItem(action); - } - } - - private void onRotate() { - // re-allocate actions between main and overflow lists - this.createActionItems(); + return getContext().getResources().getInteger( + com.android.systemui.R.integer.power_menu_max_columns); } /** @@ -701,23 +335,20 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, * * @return A new dialog. */ - private ActionsDialog createDialog() { - createActionItems(); - - mAdapter = new MyAdapter(); - mOverflowAdapter = new MyOverflowAdapter(); - mPowerAdapter = new MyPowerOptionsAdapter(); + @Override + protected ActionsDialogLite createDialog() { + initDialogItems(); mDepthController.setShowingHomeControls(true); ControlsUiController uiController = null; if (mControlsComponent.getVisibility() == AVAILABLE) { uiController = mControlsUiControllerOptional.get(); } - ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter, + ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter, this::getWalletViewController, mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, controlsAvailable(), uiController, - mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter); + mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter); if (shouldShowLockMessage(dialog)) { dialog.showLockMessage(); @@ -729,59 +360,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return dialog; } - @VisibleForTesting - boolean shouldDisplayLockdown(UserInfo user) { - if (user == null) { - return false; - } - - int userId = user.id; - - // No lockdown option if it's not turned on in Settings - if (Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, userId) == 0) { - return false; - } - - // Lockdown is meaningless without a place to go. - if (!mKeyguardStateController.isMethodSecure()) { - return false; - } - - // Only show the lockdown button if the device isn't locked down (for whatever reason). - int state = mLockPatternUtils.getStrongAuthForUser(userId); - return (state == STRONG_AUTH_NOT_REQUIRED - || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST); - } - - @VisibleForTesting - boolean shouldDisplayBugReport(UserInfo currentUser) { - return Settings.Global.getInt( - mContentResolver, Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 - && (currentUser == null || currentUser.isPrimary()); - } - - @Override - public void onUiModeChanged() { - mContext.getTheme().applyStyle(mContext.getThemeResId(), true); - if (mDialog != null && mDialog.isShowing()) { - mDialog.refreshDialog(); - } - } - - @Override - public void onConfigChanged(Configuration newConfig) { - if (mDialog != null && mDialog.isShowing() - && (newConfig.smallestScreenWidthDp != mSmallestScreenWidthDp)) { - mSmallestScreenWidthDp = newConfig.smallestScreenWidthDp; - mDialog.refreshDialog(); - } - } - - public void destroy() { - mConfigurationController.removeCallback(this); - } - @Nullable private GlobalActionsPanelPlugin.PanelViewController getWalletViewController() { if (mWalletPlugin == null) { @@ -792,15 +370,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, /** * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is - * called when the quick access wallet requests dismissal. - */ - @Override - public void dismissGlobalActionsMenu() { - dismissDialog(); - } - - /** - * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is * called when the quick access wallet requests that an intent be started (with lock screen * shown first if needed). */ @@ -809,1364 +378,39 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent); } - @VisibleForTesting - protected final class PowerOptionsAction extends SinglePressAction { - private PowerOptionsAction() { - super(com.android.systemui.R.drawable.ic_settings_power, - R.string.global_action_power_options); - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - - @Override - public void onPress() { - if (mDialog != null) { - mDialog.showPowerOptionsMenu(); - } - } - } - - @VisibleForTesting - final class ShutDownAction extends SinglePressAction implements LongPressAction { - private ShutDownAction() { - super(R.drawable.ic_lock_power_off, - R.string.global_action_power_off); - } - - @Override - public boolean onLongPress() { - if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { - mWindowManagerFuncs.reboot(true); - return true; - } - return false; - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - - @Override - public void onPress() { - // shutdown by making sure radio and power are handled accordingly. - mWindowManagerFuncs.shutdown(); - } - } - - @VisibleForTesting - protected abstract class EmergencyAction extends SinglePressAction { - EmergencyAction(int iconResId, int messageResId) { - super(iconResId, messageResId); - } - - @Override - public boolean shouldBeSeparated() { - return false; - } - - @Override - public View create( - Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { - View v = super.create(context, convertView, parent, inflater); - int textColor; - v.setBackgroundTintList(ColorStateList.valueOf(v.getResources().getColor( - com.android.systemui.R.color.global_actions_emergency_background))); - textColor = v.getResources().getColor( - com.android.systemui.R.color.global_actions_emergency_text); - TextView messageView = v.findViewById(R.id.message); - messageView.setTextColor(textColor); - messageView.setSelected(true); // necessary for marquee to work - ImageView icon = v.findViewById(R.id.icon); - icon.getDrawable().setTint(textColor); - return v; - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - } - - private class EmergencyAffordanceAction extends EmergencyAction { - EmergencyAffordanceAction() { - super(R.drawable.emergency_icon, - R.string.global_action_emergency); - } - - @Override - public void onPress() { - mEmergencyAffordanceManager.performEmergencyCall(); - } - } - - @VisibleForTesting - class EmergencyDialerAction extends EmergencyAction { - private EmergencyDialerAction() { - super(com.android.systemui.R.drawable.ic_emergency_star, - R.string.global_action_emergency); - } - - @Override - public void onPress() { - mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU); - mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS); - if (mTelecomManager != null) { - Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent( - null /* number */); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE, - EmergencyDialerConstants.ENTRY_TYPE_POWER_MENU); - mContext.startActivityAsUser(intent, UserHandle.CURRENT); - } - } - } - - @VisibleForTesting - EmergencyDialerAction makeEmergencyDialerActionForTesting() { - return new EmergencyDialerAction(); - } - - @VisibleForTesting - final class RestartAction extends SinglePressAction implements LongPressAction { - private RestartAction() { - super(R.drawable.ic_restart, R.string.global_action_restart); - } - - @Override - public boolean onLongPress() { - if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { - mWindowManagerFuncs.reboot(true); - return true; - } - return false; - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - - @Override - public void onPress() { - mWindowManagerFuncs.reboot(false); - } - } - - @VisibleForTesting - class ScreenshotAction extends SinglePressAction { - final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons"; - - public ScreenshotAction() { - super(R.drawable.ic_screenshot, R.string.global_action_screenshot); - } - - @Override - public void onPress() { - // Add a little delay before executing, to give the - // dialog a chance to go away before it takes a - // screenshot. - // TODO: instead, omit global action dialog layer - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true, - SCREENSHOT_GLOBAL_ACTIONS, mHandler, null); - mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); - mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS); - } - }, mDialogPressDelay); - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return false; - } - - @Override - public boolean shouldShow() { - // Include screenshot in power menu for legacy nav because it is not accessible - // through Recents in that mode - return is2ButtonNavigationEnabled(); - } - - boolean is2ButtonNavigationEnabled() { - return NAV_BAR_MODE_2BUTTON == mContext.getResources().getInteger( - com.android.internal.R.integer.config_navBarInteractionMode); - } - } - - @VisibleForTesting - ScreenshotAction makeScreenshotActionForTesting() { - return new ScreenshotAction(); - } - - @VisibleForTesting - class BugReportAction extends SinglePressAction implements LongPressAction { - - public BugReportAction() { - super(R.drawable.ic_lock_bugreport, R.string.bugreport_title); - } - - @Override - public void onPress() { - // don't actually trigger the bugreport if we are running stability - // tests via monkey - if (ActivityManager.isUserAMonkey()) { - return; - } - // Add a little delay before executing, to give the - // dialog a chance to go away before it takes a - // screenshot. - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - try { - // Take an "interactive" bugreport. - mMetricsLogger.action( - MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE); - mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_PRESS); - if (!mIActivityManager.launchBugReportHandlerApp()) { - Log.w(TAG, "Bugreport handler could not be launched"); - mIActivityManager.requestInteractiveBugReport(); - } - } catch (RemoteException e) { - } - } - }, mDialogPressDelay); - } - - @Override - public boolean onLongPress() { - // don't actually trigger the bugreport if we are running stability - // tests via monkey - if (ActivityManager.isUserAMonkey()) { - return false; - } - try { - // Take a "full" bugreport. - mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); - mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS); - mIActivityManager.requestFullBugReport(); - } catch (RemoteException e) { - } - return false; - } - - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return false; - } - } - - @VisibleForTesting - BugReportAction makeBugReportActionForTesting() { - return new BugReportAction(); - } - - private final class LogoutAction extends SinglePressAction { - private LogoutAction() { - super(R.drawable.ic_logout, R.string.global_action_logout); - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return false; - } - - @Override - public void onPress() { - // Add a little delay before executing, to give the dialog a chance to go away before - // switching user - mHandler.postDelayed(() -> { - try { - int currentUserId = getCurrentUser().id; - mIActivityManager.switchUser(UserHandle.USER_SYSTEM); - mIActivityManager.stopUser(currentUserId, true /*force*/, null); - } catch (RemoteException re) { - Log.e(TAG, "Couldn't logout user " + re); - } - }, mDialogPressDelay); - } - } - - private Action getSettingsAction() { - return new SinglePressAction(R.drawable.ic_settings, - R.string.global_action_settings) { - - @Override - public void onPress() { - Intent intent = new Intent(Settings.ACTION_SETTINGS); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - mContext.startActivity(intent); - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - }; - } - - private Action getAssistAction() { - return new SinglePressAction(R.drawable.ic_action_assist_focused, - R.string.global_action_assist) { - @Override - public void onPress() { - Intent intent = new Intent(Intent.ACTION_ASSIST); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - mContext.startActivity(intent); - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - }; - } - - private Action getVoiceAssistAction() { - return new SinglePressAction(R.drawable.ic_voice_search, - R.string.global_action_voice_assist) { - @Override - public void onPress() { - Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - mContext.startActivity(intent); - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - }; - } - - @VisibleForTesting - class LockDownAction extends SinglePressAction { - LockDownAction() { - super(R.drawable.ic_lock_lockdown, R.string.global_action_lockdown); - } - - @Override - public void onPress() { - mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, - UserHandle.USER_ALL); - try { - mIWindowManager.lockNow(null); - // Lock profiles (if any) on the background thread. - mBackgroundExecutor.execute(() -> lockProfiles()); - } catch (RemoteException e) { - Log.e(TAG, "Error while trying to lock device.", e); - } - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return false; - } - } - - private void lockProfiles() { - final int currentUserId = getCurrentUser().id; - final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId); - for (final int id : profileIds) { - if (id != currentUserId) { - mTrustManager.setDeviceLockedForUser(id, true); - } - } - } - - private UserInfo getCurrentUser() { - try { - return mIActivityManager.getCurrentUser(); - } catch (RemoteException re) { - return null; - } - } - - /** - * Non-thread-safe current user provider that caches the result - helpful when a method needs - * to fetch it an indeterminate number of times. - */ - private class CurrentUserProvider { - private UserInfo mUserInfo = null; - private boolean mFetched = false; - - @Nullable - UserInfo get() { - if (!mFetched) { - mFetched = true; - mUserInfo = getCurrentUser(); - } - return mUserInfo; - } - } - - private void addUserActions(List<Action> actions, UserInfo currentUser) { - if (mUserManager.isUserSwitcherEnabled()) { - List<UserInfo> users = mUserManager.getUsers(); - for (final UserInfo user : users) { - if (user.supportsSwitchToByUser()) { - boolean isCurrentUser = currentUser == null - ? user.id == 0 : (currentUser.id == user.id); - Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) - : null; - SinglePressAction switchToUser = new SinglePressAction( - R.drawable.ic_menu_cc, icon, - (user.name != null ? user.name : "Primary") - + (isCurrentUser ? " \u2714" : "")) { - public void onPress() { - try { - mIActivityManager.switchUser(user.id); - } catch (RemoteException re) { - Log.e(TAG, "Couldn't switch user " + re); - } - } - - public boolean showDuringKeyguard() { - return true; - } - - public boolean showBeforeProvisioning() { - return false; - } - }; - addIfShouldShowAction(actions, switchToUser); - } - } - } - } - - private void prepareDialog() { - refreshSilentMode(); - mAirplaneModeOn.updateState(mAirplaneState); - mAdapter.notifyDataSetChanged(); - mLifecycle.setCurrentState(Lifecycle.State.RESUMED); - } - - private void refreshSilentMode() { - if (!mHasVibrator) { - Integer value = mRingerModeTracker.getRingerMode().getValue(); - final boolean silentModeOn = value != null && value != AudioManager.RINGER_MODE_NORMAL; - ((ToggleAction) mSilentModeAction).updateState( - silentModeOn ? ToggleState.On : ToggleState.Off); - } - } - - /** - * {@inheritDoc} - */ - public void onDismiss(DialogInterface dialog) { - if (mDialog == dialog) { - mDialog = null; - } - mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_CLOSE); - mWindowManagerFuncs.onGlobalActionsHidden(); - mLifecycle.setCurrentState(Lifecycle.State.CREATED); - } - - /** - * {@inheritDoc} - */ - public void onShow(DialogInterface dialog) { - mMetricsLogger.visible(MetricsEvent.POWER_MENU); - mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN); - } - - /** - * The adapter used for power menu items shown in the global actions dialog. - */ - public class MyAdapter extends MultiListAdapter { - private int countItems(boolean separated) { - int count = 0; - for (int i = 0; i < mItems.size(); i++) { - final Action action = mItems.get(i); - - if (action.shouldBeSeparated() == separated) { - count++; - } - } - return count; - } - - @Override - public int countSeparatedItems() { - return countItems(true); - } - - @Override - public int countListItems() { - return countItems(false); - } - - @Override - public int getCount() { - return countSeparatedItems() + countListItems(); - } - - @Override - public boolean isEnabled(int position) { - return getItem(position).isEnabled(); - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public Action getItem(int position) { - int filteredPos = 0; - for (int i = 0; i < mItems.size(); i++) { - final Action action = mItems.get(i); - if (!shouldShowAction(action)) { - continue; - } - if (filteredPos == position) { - return action; - } - filteredPos++; - } - - throw new IllegalArgumentException("position " + position - + " out of range of showable actions" - + ", filtered count=" + getCount() - + ", keyguardshowing=" + mKeyguardShowing - + ", provisioned=" + mDeviceProvisioned); - } - - - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Action action = getItem(position); - View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); - view.setOnClickListener(v -> onClickItem(position)); - if (action instanceof LongPressAction) { - view.setOnLongClickListener(v -> onLongClickItem(position)); - } - return view; - } - - @Override - public boolean onLongClickItem(int position) { - final Action action = mAdapter.getItem(position); - if (action instanceof LongPressAction) { - if (mDialog != null) { - mDialog.dismiss(); - } else { - Log.w(TAG, "Action long-clicked while mDialog is null."); - } - return ((LongPressAction) action).onLongPress(); - } - return false; - } - - @Override - public void onClickItem(int position) { - Action item = mAdapter.getItem(position); - if (!(item instanceof SilentModeTriStateAction)) { - if (mDialog != null) { - // don't dismiss the dialog if we're opening the power options menu - if (!(item instanceof PowerOptionsAction)) { - mDialog.dismiss(); - } - } else { - Log.w(TAG, "Action clicked while mDialog is null."); - } - item.onPress(); - } - } - - @Override - public boolean shouldBeSeparated(int position) { - return getItem(position).shouldBeSeparated(); - } - } - - /** - * The adapter used for items in the overflow menu. - */ - public class MyPowerOptionsAdapter extends BaseAdapter { - @Override - public int getCount() { - return mPowerItems.size(); - } - - @Override - public Action getItem(int position) { - return mPowerItems.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Action action = getItem(position); - if (action == null) { - Log.w(TAG, "No power options action found at position: " + position); - return null; - } - int viewLayoutResource = com.android.systemui.R.layout.global_actions_power_item; - View view = convertView != null ? convertView - : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false); - view.setOnClickListener(v -> onClickItem(position)); - if (action instanceof LongPressAction) { - view.setOnLongClickListener(v -> onLongClickItem(position)); - } - ImageView icon = view.findViewById(R.id.icon); - TextView messageView = view.findViewById(R.id.message); - messageView.setSelected(true); // necessary for marquee to work - - icon.setImageDrawable(action.getIcon(mContext)); - icon.setScaleType(ScaleType.CENTER_CROP); - - if (action.getMessage() != null) { - messageView.setText(action.getMessage()); - } else { - messageView.setText(action.getMessageResId()); - } - return view; - } - - private boolean onLongClickItem(int position) { - final Action action = getItem(position); - if (action instanceof LongPressAction) { - if (mDialog != null) { - mDialog.dismiss(); - } else { - Log.w(TAG, "Action long-clicked while mDialog is null."); - } - return ((LongPressAction) action).onLongPress(); - } - return false; - } - - private void onClickItem(int position) { - Action item = getItem(position); - if (!(item instanceof SilentModeTriStateAction)) { - if (mDialog != null) { - mDialog.dismiss(); - } else { - Log.w(TAG, "Action clicked while mDialog is null."); - } - item.onPress(); - } - } - } - - /** - * The adapter used for items in the power options menu, triggered by the PowerOptionsAction. - */ - public class MyOverflowAdapter extends BaseAdapter { - @Override - public int getCount() { - return mOverflowItems.size(); - } - - @Override - public Action getItem(int position) { - return mOverflowItems.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Action action = getItem(position); - if (action == null) { - Log.w(TAG, "No overflow action found at position: " + position); - return null; - } - int viewLayoutResource = com.android.systemui.R.layout.controls_more_item; - View view = convertView != null ? convertView - : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false); - TextView textView = (TextView) view; - if (action.getMessageResId() != 0) { - textView.setText(action.getMessageResId()); - } else { - textView.setText(action.getMessage()); - } - return textView; - } - - private boolean onLongClickItem(int position) { - final Action action = getItem(position); - if (action instanceof LongPressAction) { - if (mDialog != null) { - mDialog.dismiss(); - } else { - Log.w(TAG, "Action long-clicked while mDialog is null."); - } - return ((LongPressAction) action).onLongPress(); - } - return false; - } - - private void onClickItem(int position) { - Action item = getItem(position); - if (!(item instanceof SilentModeTriStateAction)) { - if (mDialog != null) { - mDialog.dismiss(); - } else { - Log.w(TAG, "Action clicked while mDialog is null."); - } - item.onPress(); - } - } - } - - // note: the scheme below made more sense when we were planning on having - // 8 different things in the global actions dialog. seems overkill with - // only 3 items now, but may as well keep this flexible approach so it will - // be easy should someone decide at the last minute to include something - // else, such as 'enable wifi', or 'enable bluetooth' - - /** - * What each item in the global actions dialog must be able to support. - */ - public interface Action { - /** - * @return Text that will be announced when dialog is created. null for none. - */ - CharSequence getLabelForAccessibility(Context context); - - View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); - - void onPress(); - - /** - * @return whether this action should appear in the dialog when the keygaurd is showing. - */ - boolean showDuringKeyguard(); - - /** - * @return whether this action should appear in the dialog before the - * device is provisioned.f - */ - boolean showBeforeProvisioning(); - - boolean isEnabled(); - - default boolean shouldBeSeparated() { - return false; - } - - /** - * Return the id of the message associated with this action, or 0 if it doesn't have one. - * @return - */ - int getMessageResId(); - - /** - * Return the icon drawable for this action. - */ - Drawable getIcon(Context context); - - /** - * Return the message associated with this action, or null if it doesn't have one. - * @return - */ - CharSequence getMessage(); - - default boolean shouldShow() { - return true; - } - } - - /** - * An action that also supports long press. - */ - private interface LongPressAction extends Action { - boolean onLongPress(); - } - - /** - * A single press action maintains no state, just responds to a press and takes an action. - */ - - private abstract class SinglePressAction implements Action { - private final int mIconResId; - private final Drawable mIcon; - private final int mMessageResId; - private final CharSequence mMessage; - - protected SinglePressAction(int iconResId, int messageResId) { - mIconResId = iconResId; - mMessageResId = messageResId; - mMessage = null; - mIcon = null; - } - - protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) { - mIconResId = iconResId; - mMessageResId = 0; - mMessage = message; - mIcon = icon; - } - - public boolean isEnabled() { - return true; - } - - public String getStatus() { - return null; - } - - abstract public void onPress(); - - public CharSequence getLabelForAccessibility(Context context) { - if (mMessage != null) { - return mMessage; - } else { - return context.getString(mMessageResId); - } - } - - public int getMessageResId() { - return mMessageResId; - } - - public CharSequence getMessage() { - return mMessage; - } - - @Override - public Drawable getIcon(Context context) { - if (mIcon != null) { - return mIcon; - } else { - return context.getDrawable(mIconResId); - } - } - - public View create( - Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { - View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2, - parent, false /* attach */); - - ImageView icon = v.findViewById(R.id.icon); - TextView messageView = v.findViewById(R.id.message); - messageView.setSelected(true); // necessary for marquee to work - - icon.setImageDrawable(getIcon(context)); - icon.setScaleType(ScaleType.CENTER_CROP); - - if (mMessage != null) { - messageView.setText(mMessage); - } else { - messageView.setText(mMessageResId); - } - - return v; - } - } - - private enum ToggleState { - Off(false), - TurningOn(true), - TurningOff(true), - On(false); - - private final boolean mInTransition; - - ToggleState(boolean intermediate) { - mInTransition = intermediate; - } - - public boolean inTransition() { - return mInTransition; - } - } - - /** - * A toggle action knows whether it is on or off, and displays an icon and status message - * accordingly. - */ - private abstract class ToggleAction implements Action { - - protected ToggleState mState = ToggleState.Off; - - // prefs - protected int mEnabledIconResId; - protected int mDisabledIconResid; - protected int mMessageResId; - protected int mEnabledStatusMessageResId; - protected int mDisabledStatusMessageResId; - - /** - * @param enabledIconResId The icon for when this action is on. - * @param disabledIconResid The icon for when this action is off. - * @param message The general information message, e.g 'Silent Mode' - * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' - * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' - */ - public ToggleAction(int enabledIconResId, - int disabledIconResid, - int message, - int enabledStatusMessageResId, - int disabledStatusMessageResId) { - mEnabledIconResId = enabledIconResId; - mDisabledIconResid = disabledIconResid; - mMessageResId = message; - mEnabledStatusMessageResId = enabledStatusMessageResId; - mDisabledStatusMessageResId = disabledStatusMessageResId; - } - - /** - * Override to make changes to resource IDs just before creating the View. - */ - void willCreate() { - - } - - @Override - public CharSequence getLabelForAccessibility(Context context) { - return context.getString(mMessageResId); - } - - private boolean isOn() { - return mState == ToggleState.On || mState == ToggleState.TurningOn; - } - - @Override - public CharSequence getMessage() { - return null; - } - @Override - public int getMessageResId() { - return isOn() ? mEnabledStatusMessageResId : mDisabledStatusMessageResId; - } - - private int getIconResId() { - return isOn() ? mEnabledIconResId : mDisabledIconResid; - } - - @Override - public Drawable getIcon(Context context) { - return context.getDrawable(getIconResId()); - } - - public View create(Context context, View convertView, ViewGroup parent, - LayoutInflater inflater) { - willCreate(); - - View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2, - parent, false /* attach */); - - ImageView icon = (ImageView) v.findViewById(R.id.icon); - TextView messageView = (TextView) v.findViewById(R.id.message); - final boolean enabled = isEnabled(); - - if (messageView != null) { - messageView.setText(getMessageResId()); - messageView.setEnabled(enabled); - messageView.setSelected(true); // necessary for marquee to work - } - - if (icon != null) { - icon.setImageDrawable(context.getDrawable(getIconResId())); - icon.setEnabled(enabled); - } - - v.setEnabled(enabled); - - return v; - } - - public final void onPress() { - if (mState.inTransition()) { - Log.w(TAG, "shouldn't be able to toggle when in transition"); - return; - } - - final boolean nowOn = !(mState == ToggleState.On); - onToggle(nowOn); - changeStateFromPress(nowOn); - } - - public boolean isEnabled() { - return !mState.inTransition(); - } - - /** - * Implementations may override this if their state can be in on of the intermediate states - * until some notification is received (e.g airplane mode is 'turning off' until we know the - * wireless connections are back online - * - * @param buttonOn Whether the button was turned on or off - */ - protected void changeStateFromPress(boolean buttonOn) { - mState = buttonOn ? ToggleState.On : ToggleState.Off; - } - - abstract void onToggle(boolean on); - - public void updateState(ToggleState state) { - mState = state; - } - } - - private class AirplaneModeAction extends ToggleAction { - AirplaneModeAction() { - super( - R.drawable.ic_lock_airplane_mode, - R.drawable.ic_lock_airplane_mode_off, - R.string.global_actions_toggle_airplane_mode, - R.string.global_actions_airplane_mode_on_status, - R.string.global_actions_airplane_mode_off_status); - } - - void onToggle(boolean on) { - if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) { - mIsWaitingForEcmExit = true; - // Launch ECM exit dialog - Intent ecmDialogIntent = - new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); - ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(ecmDialogIntent); - } else { - changeAirplaneModeSystemSetting(on); - } - } - - @Override - protected void changeStateFromPress(boolean buttonOn) { - if (!mHasTelephony) return; - - // In ECM mode airplane state cannot be changed - if (!TelephonyProperties.in_ecm_mode().orElse(false)) { - mState = buttonOn ? ToggleState.TurningOn : ToggleState.TurningOff; - mAirplaneState = mState; - } - } - - public boolean showDuringKeyguard() { - return true; - } - - public boolean showBeforeProvisioning() { - return false; - } - } - - private class SilentModeToggleAction extends ToggleAction { - public SilentModeToggleAction() { - super(R.drawable.ic_audio_vol_mute, - R.drawable.ic_audio_vol, - R.string.global_action_toggle_silent_mode, - R.string.global_action_silent_mode_on_status, - R.string.global_action_silent_mode_off_status); - } - - void onToggle(boolean on) { - if (on) { - mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); - } else { - mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); - } - } - - public boolean showDuringKeyguard() { - return true; - } - - public boolean showBeforeProvisioning() { - return false; - } - } - - private static class SilentModeTriStateAction implements Action, View.OnClickListener { - - private final int[] ITEM_IDS = {R.id.option1, R.id.option2, R.id.option3}; - - private final AudioManager mAudioManager; - private final Handler mHandler; - - SilentModeTriStateAction(AudioManager audioManager, Handler handler) { - mAudioManager = audioManager; - mHandler = handler; - } - - private int ringerModeToIndex(int ringerMode) { - // They just happen to coincide - return ringerMode; - } - - private int indexToRingerMode(int index) { - // They just happen to coincide - return index; - } - - @Override - public CharSequence getLabelForAccessibility(Context context) { - return null; - } - - @Override - public int getMessageResId() { - return 0; - } - - @Override - public CharSequence getMessage() { - return null; - } - - @Override - public Drawable getIcon(Context context) { - return null; - } - - - public View create(Context context, View convertView, ViewGroup parent, - LayoutInflater inflater) { - View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); - - int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); - for (int i = 0; i < 3; i++) { - View itemView = v.findViewById(ITEM_IDS[i]); - itemView.setSelected(selectedIndex == i); - // Set up click handler - itemView.setTag(i); - itemView.setOnClickListener(this); - } - return v; - } - - public void onPress() { - } - - public boolean showDuringKeyguard() { - return true; - } - - public boolean showBeforeProvisioning() { - return false; - } - - public boolean isEnabled() { - return true; - } - - void willCreate() { - } - - public void onClick(View v) { - if (!(v.getTag() instanceof Integer)) return; - - int index = (Integer) v.getTag(); - mAudioManager.setRingerMode(indexToRingerMode(index)); - mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); - } - } - - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) - || Intent.ACTION_SCREEN_OFF.equals(action)) { - String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); - if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { - mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISMISS, reason)); - } - } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { - // Airplane mode can be changed after ECM exits if airplane toggle button - // is pressed during ECM mode - if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)) - && mIsWaitingForEcmExit) { - mIsWaitingForEcmExit = false; - changeAirplaneModeSystemSetting(true); - } - } - } - }; - - private final TelephonyCallback.ServiceStateListener mPhoneStateListener = - new TelephonyCallback.ServiceStateListener() { - @Override - public void onServiceStateChanged(ServiceState serviceState) { - if (!mHasTelephony) return; - final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; - mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off; - mAirplaneModeOn.updateState(mAirplaneState); - mAdapter.notifyDataSetChanged(); - mOverflowAdapter.notifyDataSetChanged(); - mPowerAdapter.notifyDataSetChanged(); - } - }; - - private ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) { - @Override - public void onChange(boolean selfChange) { - onAirplaneModeChanged(); - } - }; - - private static final int MESSAGE_DISMISS = 0; - private static final int MESSAGE_REFRESH = 1; - private static final int DIALOG_DISMISS_DELAY = 300; // ms - private static final int DIALOG_PRESS_DELAY = 850; // ms - - @VisibleForTesting void setZeroDialogPressDelayForTesting() { - mDialogPressDelay = 0; // ms + @Override + protected int getEmergencyTextColor(Context context) { + return context.getResources().getColor( + com.android.systemui.R.color.global_actions_emergency_text); } - private Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_DISMISS: - if (mDialog != null) { - if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { - mDialog.completeDismiss(); - } else { - mDialog.dismiss(); - } - mDialog = null; - } - break; - case MESSAGE_REFRESH: - refreshSilentMode(); - mAdapter.notifyDataSetChanged(); - break; - } - } - }; - - private void onAirplaneModeChanged() { - // Let the service state callbacks handle the state. - if (mHasTelephony) return; - - boolean airplaneModeOn = Settings.Global.getInt( - mContentResolver, - Settings.Global.AIRPLANE_MODE_ON, - 0) == 1; - mAirplaneState = airplaneModeOn ? ToggleState.On : ToggleState.Off; - mAirplaneModeOn.updateState(mAirplaneState); + @Override + protected int getEmergencyIconColor(Context context) { + return getContext().getResources().getColor( + com.android.systemui.R.color.global_actions_emergency_text); } - /** - * Change the airplane mode system setting - */ - private void changeAirplaneModeSystemSetting(boolean on) { - Settings.Global.putInt( - mContentResolver, - Settings.Global.AIRPLANE_MODE_ON, - on ? 1 : 0); - Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra("state", on); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); - if (!mHasTelephony) { - mAirplaneState = on ? ToggleState.On : ToggleState.Off; - } + @Override + protected int getEmergencyBackgroundColor(Context context) { + return getContext().getResources().getColor( + com.android.systemui.R.color.global_actions_emergency_background); } - @NonNull @Override - public Lifecycle getLifecycle() { - return mLifecycle; + protected int getGridItemLayoutResource() { + return com.android.systemui.R.layout.global_actions_grid_item_v2; } @VisibleForTesting - static final class ActionsDialog extends Dialog implements DialogInterface, - ColorExtractor.OnColorsChangedListener { - - private final Context mContext; - private final MyAdapter mAdapter; - private final MyOverflowAdapter mOverflowAdapter; - private final MyPowerOptionsAdapter mPowerOptionsAdapter; - private final IStatusBarService mStatusBarService; - private final IBinder mToken = new Binder(); - private MultiListLayout mGlobalActionsLayout; - private Drawable mBackgroundDrawable; - private final SysuiColorExtractor mColorExtractor; + static class ActionsDialog extends ActionsDialogLite { + private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory; @Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController; - private boolean mKeyguardShowing; - private boolean mShowing; - private float mScrimAlpha; private ResetOrientationData mResetOrientationData; - private final NotificationShadeWindowController mNotificationShadeWindowController; - private final NotificationShadeDepthController mDepthController; - private final SysUiState mSysUiState; - private ListPopupWindow mOverflowPopup; - private Dialog mPowerOptionsDialog; - private final Runnable mOnRotateCallback; private final boolean mControlsAvailable; private ControlsUiController mControlsUiController; private ViewGroup mControlsView; - private ViewGroup mContainer; @VisibleForTesting ViewGroup mLockMessageContainer; private TextView mLockMessage; @@ -2178,27 +422,16 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, boolean controlsAvailable, @Nullable ControlsUiController controlsUiController, SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter) { - super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); - mContext = context; - mAdapter = adapter; - mOverflowAdapter = overflowAdapter; - mPowerOptionsAdapter = powerAdapter; - mDepthController = depthController; - mColorExtractor = sysuiColorExtractor; - mStatusBarService = statusBarService; - mNotificationShadeWindowController = notificationShadeWindowController; + super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions, + adapter, overflowAdapter, depthController, sysuiColorExtractor, + statusBarService, notificationShadeWindowController, sysuiState, + onRotateCallback, keyguardShowing, powerAdapter); mControlsAvailable = controlsAvailable; mControlsUiController = controlsUiController; - mSysUiState = sysuiState; - mOnRotateCallback = onRotateCallback; - mKeyguardShowing = keyguardShowing; mWalletFactory = walletFactory; - // Window initialization + // Update window attributes Window window = getWindow(); - window.requestFeature(Window.FEATURE_NO_TITLE); - // Inflate the decor view, so the attributes below are not overwritten by the theme. - window.getDecorView(); window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; @@ -2211,10 +444,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); - window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - window.getAttributes().setFitInsetsTypes(0 /* types */); setTitle(R.string.global_actions); - initializeLayout(); } @@ -2233,6 +463,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private void initializeWalletView() { + if (mWalletFactory == null) { + return; + } mWalletViewController = mWalletFactory.get(); if (!isWalletViewAvailable()) { return; @@ -2313,132 +546,24 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - private ListPopupWindow createPowerOverflowPopup() { - GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu( - new ContextThemeWrapper( - mContext, - com.android.systemui.R.style.Control_ListPopupWindow - ), false /* isDropDownMode */); - popup.setOnItemClickListener( - (parent, view, position, id) -> mOverflowAdapter.onClickItem(position)); - popup.setOnItemLongClickListener( - (parent, view, position, id) -> mOverflowAdapter.onLongClickItem(position)); - View overflowButton = - findViewById(com.android.systemui.R.id.global_actions_overflow_button); - popup.setAnchorView(overflowButton); - popup.setAdapter(mOverflowAdapter); - return popup; - } - - public void showPowerOptionsMenu() { - mPowerOptionsDialog = GlobalActionsPowerDialog.create(mContext, mPowerOptionsAdapter); - mPowerOptionsDialog.show(); - } - - private void showPowerOverflowMenu() { - mOverflowPopup = createPowerOverflowPopup(); - mOverflowPopup.show(); + @Override + protected int getLayoutResource() { + return com.android.systemui.R.layout.global_actions_grid_v2; } - private void initializeLayout() { - setContentView(com.android.systemui.R.layout.global_actions_grid_v2); - fixNavBarClipping(); + @Override + protected void initializeLayout() { + super.initializeLayout(); mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls); - mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view); - mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() { - @Override - public boolean dispatchPopulateAccessibilityEvent( - View host, AccessibilityEvent event) { - // Populate the title here, just as Activity does - event.getText().add(mContext.getString(R.string.global_actions)); - return true; - } - }); - mGlobalActionsLayout.setRotationListener(this::onRotate); - mGlobalActionsLayout.setAdapter(mAdapter); - mContainer = findViewById(com.android.systemui.R.id.global_actions_container); mLockMessageContainer = requireViewById( com.android.systemui.R.id.global_actions_lock_message_container); mLockMessage = requireViewById(com.android.systemui.R.id.global_actions_lock_message); - - View overflowButton = findViewById( - com.android.systemui.R.id.global_actions_overflow_button); - if (overflowButton != null) { - if (mOverflowAdapter.getCount() > 0) { - overflowButton.setOnClickListener((view) -> showPowerOverflowMenu()); - LinearLayout.LayoutParams params = - (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams(); - params.setMarginEnd(0); - mGlobalActionsLayout.setLayoutParams(params); - } else { - overflowButton.setVisibility(View.GONE); - LinearLayout.LayoutParams params = - (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams(); - params.setMarginEnd(mContext.getResources().getDimensionPixelSize( - com.android.systemui.R.dimen.global_actions_side_margin)); - mGlobalActionsLayout.setLayoutParams(params); - } - } - initializeWalletView(); - if (mBackgroundDrawable == null) { - mBackgroundDrawable = new ScrimDrawable(); - mScrimAlpha = 1.0f; - } getWindow().setBackgroundDrawable(mBackgroundDrawable); } - private void fixNavBarClipping() { - ViewGroup content = findViewById(android.R.id.content); - content.setClipChildren(false); - content.setClipToPadding(false); - ViewGroup contentParent = (ViewGroup) content.getParent(); - contentParent.setClipChildren(false); - contentParent.setClipToPadding(false); - } - - @Override - protected void onStart() { - super.setCanceledOnTouchOutside(true); - super.onStart(); - mGlobalActionsLayout.updateList(); - - if (mBackgroundDrawable instanceof ScrimDrawable) { - mColorExtractor.addOnColorsChangedListener(this); - GradientColors colors = mColorExtractor.getNeutralColors(); - updateColors(colors, false /* animate */); - } - } - - /** - * Updates background and system bars according to current GradientColors. - * - * @param colors Colors and hints to use. - * @param animate Interpolates gradient if true, just sets otherwise. - */ - private void updateColors(GradientColors colors, boolean animate) { - if (!(mBackgroundDrawable instanceof ScrimDrawable)) { - return; - } - ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate); - View decorView = getWindow().getDecorView(); - if (colors.supportsDarkText()) { - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | - View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); - } else { - decorView.setSystemUiVisibility(0); - } - } - - @Override - protected void onStop() { - super.onStop(); - mColorExtractor.removeOnColorsChangedListener(this); - } - @Override - public void show() { - super.show(); + protected void showDialog() { mShowing = true; mNotificationShadeWindowController.setRequestTopUi(true, TAG); mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true) @@ -2482,42 +607,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } @Override - public void dismiss() { - dismissWithAnimation(() -> { - mContainer.setTranslationX(0); - ObjectAnimator alphaAnimator = - ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f); - alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); - alphaAnimator.setDuration(233); - alphaAnimator.addUpdateListener((animation) -> { - float animatedValue = 1f - animation.getAnimatedFraction(); - int alpha = (int) (animatedValue * mScrimAlpha * 255); - mBackgroundDrawable.setAlpha(alpha); - mDepthController.updateGlobalDialogVisibility(animatedValue, - mGlobalActionsLayout); - }); - - float xOffset = mGlobalActionsLayout.getAnimationOffsetX(); - ObjectAnimator xAnimator = - ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset); - xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); - xAnimator.setDuration(350); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(alphaAnimator, xAnimator); - animatorSet.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - completeDismiss(); - } - }); - - animatorSet.start(); - - // close first, as popup windows will not fade during the animation - dismissOverflow(false); - dismissPowerOptions(false); - if (mControlsUiController != null) mControlsUiController.closeDialogs(false); - }); + protected void dismissInternal() { + super.dismissInternal(); + if (mControlsUiController != null) mControlsUiController.closeDialogs(false); } private void dismissForControlsActivity() { @@ -2527,26 +619,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, }); } - void dismissWithAnimation(Runnable animation) { - if (!mShowing) { - return; - } - mShowing = false; - animation.run(); - } - - private void completeDismiss() { - mShowing = false; - resetOrientation(); + @Override + protected void completeDismiss() { dismissWallet(); - dismissOverflow(true); - dismissPowerOptions(true); if (mControlsUiController != null) mControlsUiController.hide(); - mNotificationShadeWindowController.setRequestTopUi(false, TAG); - mDepthController.updateGlobalDialogVisibility(0, null /* view */); - mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false) - .commitUpdate(mContext.getDisplayId()); - super.dismiss(); + resetOrientation(); + super.completeDismiss(); } private void dismissWallet() { @@ -2557,38 +635,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } - private void dismissOverflow(boolean immediate) { - if (mOverflowPopup != null) { - if (immediate) { - mOverflowPopup.dismissImmediate(); - } else { - mOverflowPopup.dismiss(); - } - } - } - - private void dismissPowerOptions(boolean immediate) { - if (mPowerOptionsDialog != null) { - if (immediate) { - mPowerOptionsDialog.dismiss(); - } else { - mPowerOptionsDialog.dismiss(); - } - } - } - - private void setRotationSuggestionsEnabled(boolean enabled) { - try { - final int userId = Binder.getCallingUserHandle().getIdentifier(); - final int what = enabled - ? StatusBarManager.DISABLE2_NONE - : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS; - mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - private void resetOrientation() { if (mResetOrientationData != null) { RotationPolicy.setRotationLockAtAngle(mContext, mResetOrientationData.locked, @@ -2598,49 +644,20 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } @Override - public void onColorsChanged(ColorExtractor extractor, int which) { - if (mKeyguardShowing) { - if ((WallpaperManager.FLAG_LOCK & which) != 0) { - updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK), - true /* animate */); - } - } else { - if ((WallpaperManager.FLAG_SYSTEM & which) != 0) { - updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM), - true /* animate */); - } - } - } - - public void setKeyguardShowing(boolean keyguardShowing) { - mKeyguardShowing = keyguardShowing; - } - public void refreshDialog() { // ensure dropdown menus are dismissed before re-initializing the dialog dismissWallet(); - dismissOverflow(true); - dismissPowerOptions(true); if (mControlsUiController != null) { mControlsUiController.hide(); } - // re-create dialog - initializeLayout(); - mGlobalActionsLayout.updateList(); + super.refreshDialog(); if (mControlsUiController != null) { mControlsUiController.show(mControlsView, this::dismissForControlsActivity, null /* activityContext */); } } - public void onRotate(int from, int to) { - if (mShowing) { - mOnRotateCallback.run(); - refreshDialog(); - } - } - void hideLockMessage() { if (mLockMessageContainer.getVisibility() == View.VISIBLE) { mLockMessageContainer.animate().alpha(0).setDuration(150).setListener( @@ -2683,7 +700,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private boolean controlsAvailable() { - return mDeviceProvisioned + return isDeviceProvisioned() && mControlsComponent.isEnabled() && !mControlsServiceInfos.isEmpty(); } @@ -2703,7 +720,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private void onPowerMenuLockScreenSettingsChanged() { - mShowLockScreenCardsAndControls = Settings.Secure.getInt(mContentResolver, + mShowLockScreenCardsAndControls = mSecureSettings.getInt( Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0; } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java new file mode 100644 index 000000000000..47ae145590b5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -0,0 +1,2391 @@ +/* + * Copyright 2021 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.globalactions; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; +import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; + +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.Dialog; +import android.app.IActivityManager; +import android.app.StatusBarManager; +import android.app.WallpaperManager; +import android.app.admin.DevicePolicyManager; +import android.app.trust.TrustManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.Vibrator; +import android.provider.Settings; +import android.service.dreams.IDreamManager; +import android.sysprop.TelephonyProperties; +import android.telecom.TelecomManager; +import android.telephony.ServiceState; +import android.telephony.TelephonyCallback; +import android.telephony.TelephonyManager; +import android.util.ArraySet; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.IWindowManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; +import android.widget.LinearLayout; +import android.widget.ListPopupWindow; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.colorextraction.ColorExtractor.GradientColors; +import com.android.internal.colorextraction.drawable.ScrimDrawable; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.util.EmergencyAffordanceManager; +import com.android.internal.util.ScreenshotHelper; +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.Interpolators; +import com.android.systemui.MultiListLayout; +import com.android.systemui.MultiListLayout.MultiListAdapter; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.model.SysUiState; +import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; +import com.android.systemui.plugins.GlobalActionsPanelPlugin; +import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.telephony.TelephonyListenerManager; +import com.android.systemui.util.EmergencyDialerConstants; +import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.settings.SecureSettings; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending + * on whether the keyguard is showing, and whether the device is provisioned. + */ +public class GlobalActionsDialogLite implements DialogInterface.OnDismissListener, + DialogInterface.OnShowListener, + ConfigurationController.ConfigurationListener, + GlobalActionsPanelPlugin.Callbacks, + LifecycleOwner { + + public static final String SYSTEM_DIALOG_REASON_KEY = "reason"; + public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; + public static final String SYSTEM_DIALOG_REASON_DREAM = "dream"; + + private static final String TAG = "GlobalActionsDialogLite"; + + private static final boolean SHOW_SILENT_TOGGLE = true; + + /* Valid settings for global actions keys. + * see config.xml config_globalActionList */ + @VisibleForTesting + static final String GLOBAL_ACTION_KEY_POWER = "power"; + private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; + static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; + private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; + private static final String GLOBAL_ACTION_KEY_USERS = "users"; + private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; + static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; + private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; + private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; + static final String GLOBAL_ACTION_KEY_RESTART = "restart"; + private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout"; + static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency"; + static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; + + private final Context mContext; + private final GlobalActionsManager mWindowManagerFuncs; + private final AudioManager mAudioManager; + private final IDreamManager mDreamManager; + private final DevicePolicyManager mDevicePolicyManager; + private final LockPatternUtils mLockPatternUtils; + private final KeyguardStateController mKeyguardStateController; + private final BroadcastDispatcher mBroadcastDispatcher; + protected final GlobalSettings mGlobalSettings; + protected final SecureSettings mSecureSettings; + protected final Resources mResources; + private final ConfigurationController mConfigurationController; + private final UserManager mUserManager; + private final TrustManager mTrustManager; + private final IActivityManager mIActivityManager; + private final TelecomManager mTelecomManager; + private final MetricsLogger mMetricsLogger; + private final UiEventLogger mUiEventLogger; + private final NotificationShadeDepthController mDepthController; + private final SysUiState mSysUiState; + + // Used for RingerModeTracker + private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); + + @VisibleForTesting + protected final ArrayList<Action> mItems = new ArrayList<>(); + @VisibleForTesting + protected final ArrayList<Action> mOverflowItems = new ArrayList<>(); + @VisibleForTesting + protected final ArrayList<Action> mPowerItems = new ArrayList<>(); + + @VisibleForTesting + protected ActionsDialogLite mDialog; + + private Action mSilentModeAction; + private ToggleAction mAirplaneModeOn; + + protected MyAdapter mAdapter; + protected MyOverflowAdapter mOverflowAdapter; + protected MyPowerOptionsAdapter mPowerAdapter; + + private boolean mKeyguardShowing = false; + private boolean mDeviceProvisioned = false; + private ToggleState mAirplaneState = ToggleState.Off; + private boolean mIsWaitingForEcmExit = false; + private boolean mHasTelephony; + private boolean mHasVibrator; + private final boolean mShowSilentToggle; + private final EmergencyAffordanceManager mEmergencyAffordanceManager; + private final ScreenshotHelper mScreenshotHelper; + private final SysuiColorExtractor mSysuiColorExtractor; + private final IStatusBarService mStatusBarService; + protected final NotificationShadeWindowController mNotificationShadeWindowController; + private final IWindowManager mIWindowManager; + private final Executor mBackgroundExecutor; + private final RingerModeTracker mRingerModeTracker; + private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms + protected Handler mMainHandler; + private int mSmallestScreenWidthDp; + + @VisibleForTesting + public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The global actions / power menu surface became visible on the screen.") + GA_POWER_MENU_OPEN(337), + + @UiEvent(doc = "The global actions / power menu surface was dismissed.") + GA_POWER_MENU_CLOSE(471), + + @UiEvent(doc = "The global actions bugreport button was pressed.") + GA_BUGREPORT_PRESS(344), + + @UiEvent(doc = "The global actions bugreport button was long pressed.") + GA_BUGREPORT_LONG_PRESS(345), + + @UiEvent(doc = "The global actions emergency button was pressed.") + GA_EMERGENCY_DIALER_PRESS(346), + + @UiEvent(doc = "The global actions screenshot button was pressed.") + GA_SCREENSHOT_PRESS(347), + + @UiEvent(doc = "The global actions screenshot button was long pressed.") + GA_SCREENSHOT_LONG_PRESS(348); + + private final int mId; + + GlobalActionsEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + + /** + * @param context everything needs a context :( + */ + @Inject + public GlobalActionsDialogLite(Context context, GlobalActionsManager windowManagerFuncs, + AudioManager audioManager, IDreamManager iDreamManager, + DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils, + BroadcastDispatcher broadcastDispatcher, + TelephonyListenerManager telephonyListenerManager, + GlobalSettings globalSettings, SecureSettings secureSettings, + @Nullable Vibrator vibrator, @Main Resources resources, + ConfigurationController configurationController, + KeyguardStateController keyguardStateController, UserManager userManager, + TrustManager trustManager, IActivityManager iActivityManager, + @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger, + NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor, + IStatusBarService statusBarService, + NotificationShadeWindowController notificationShadeWindowController, + IWindowManager iWindowManager, + @Background Executor backgroundExecutor, + UiEventLogger uiEventLogger, + RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) { + mContext = context; + mWindowManagerFuncs = windowManagerFuncs; + mAudioManager = audioManager; + mDreamManager = iDreamManager; + mDevicePolicyManager = devicePolicyManager; + mLockPatternUtils = lockPatternUtils; + mKeyguardStateController = keyguardStateController; + mBroadcastDispatcher = broadcastDispatcher; + mGlobalSettings = globalSettings; + mSecureSettings = secureSettings; + mResources = resources; + mConfigurationController = configurationController; + mUserManager = userManager; + mTrustManager = trustManager; + mIActivityManager = iActivityManager; + mTelecomManager = telecomManager; + mMetricsLogger = metricsLogger; + mUiEventLogger = uiEventLogger; + mDepthController = depthController; + mSysuiColorExtractor = colorExtractor; + mStatusBarService = statusBarService; + mNotificationShadeWindowController = notificationShadeWindowController; + mIWindowManager = iWindowManager; + mBackgroundExecutor = backgroundExecutor; + mRingerModeTracker = ringerModeTracker; + mSysUiState = sysUiState; + mMainHandler = handler; + mSmallestScreenWidthDp = mContext.getResources().getConfiguration().smallestScreenWidthDp; + + // receive broadcasts + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); + mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter); + + mHasTelephony = + context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + + // get notified of phone state changes + telephonyListenerManager.addServiceStateListener(mPhoneStateListener); + mGlobalSettings.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, + mAirplaneModeObserver); + mHasVibrator = vibrator != null && vibrator.hasVibrator(); + + mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean( + R.bool.config_useFixedVolume); + if (mShowSilentToggle) { + mRingerModeTracker.getRingerMode().observe(this, ringer -> + mHandler.sendEmptyMessage(MESSAGE_REFRESH) + ); + } + + mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); + mScreenshotHelper = new ScreenshotHelper(context); + + mConfigurationController.addCallback(this); + } + + protected Context getContext() { + return mContext; + } + + /** + * Show the global actions dialog (creating if necessary) + * + * @param keyguardShowing True if keyguard is showing + */ + public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { + mKeyguardShowing = keyguardShowing; + mDeviceProvisioned = isDeviceProvisioned; + if (mDialog != null && mDialog.isShowing()) { + // In order to force global actions to hide on the same affordance press, we must + // register a call to onGlobalActionsShown() first to prevent the default actions + // menu from showing. This will be followed by a subsequent call to + // onGlobalActionsHidden() on dismiss() + mWindowManagerFuncs.onGlobalActionsShown(); + mDialog.dismiss(); + mDialog = null; + } else { + handleShow(); + } + } + + protected boolean isKeyguardShowing() { + return mKeyguardShowing; + } + + protected boolean isDeviceProvisioned() { + return mDeviceProvisioned; + } + + /** + * Dismiss the global actions dialog, if it's currently shown + */ + public void dismissDialog() { + mHandler.removeMessages(MESSAGE_DISMISS); + mHandler.sendEmptyMessage(MESSAGE_DISMISS); + } + + protected void awakenIfNecessary() { + if (mDreamManager != null) { + try { + if (mDreamManager.isDreaming()) { + mDreamManager.awaken(); + } + } catch (RemoteException e) { + // we tried + } + } + } + + protected void handleShow() { + awakenIfNecessary(); + mDialog = createDialog(); + prepareDialog(); + + WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); + attrs.setTitle("ActionsDialog"); + attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mDialog.getWindow().setAttributes(attrs); + // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports + mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM); + mDialog.show(); + mWindowManagerFuncs.onGlobalActionsShown(); + } + + @VisibleForTesting + protected boolean shouldShowAction(Action action) { + if (mKeyguardShowing && !action.showDuringKeyguard()) { + return false; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + return false; + } + return action.shouldShow(); + } + + /** + * Returns the maximum number of power menu items to show based on which GlobalActions + * layout is being used. + */ + @VisibleForTesting + protected int getMaxShownPowerItems() { + return mResources.getInteger(com.android.systemui.R.integer.power_menu_lite_max_columns) + * mResources.getInteger(com.android.systemui.R.integer.power_menu_lite_max_rows); + } + + /** + * Add a power menu action item for to either the main or overflow items lists, depending on + * whether controls are enabled and whether the max number of shown items has been reached. + */ + private void addActionItem(Action action) { + if (mItems.size() < getMaxShownPowerItems()) { + mItems.add(action); + } else { + mOverflowItems.add(action); + } + } + + @VisibleForTesting + protected String[] getDefaultActions() { + return mResources.getStringArray(R.array.config_globalActionsList); + } + + private void addIfShouldShowAction(List<Action> actions, Action action) { + if (shouldShowAction(action)) { + actions.add(action); + } + } + + @VisibleForTesting + protected void createActionItems() { + // Simple toggle style if there's no vibrator, otherwise use a tri-state + if (!mHasVibrator) { + mSilentModeAction = new SilentModeToggleAction(); + } else { + mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler); + } + mAirplaneModeOn = new AirplaneModeAction(); + onAirplaneModeChanged(); + + mItems.clear(); + mOverflowItems.clear(); + mPowerItems.clear(); + String[] defaultActions = getDefaultActions(); + + ShutDownAction shutdownAction = new ShutDownAction(); + RestartAction restartAction = new RestartAction(); + ArraySet<String> addedKeys = new ArraySet<>(); + List<Action> tempActions = new ArrayList<>(); + CurrentUserProvider currentUser = new CurrentUserProvider(); + + // make sure emergency affordance action is first, if needed + if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { + addIfShouldShowAction(tempActions, new EmergencyAffordanceAction()); + addedKeys.add(GLOBAL_ACTION_KEY_EMERGENCY); + } + + for (int i = 0; i < defaultActions.length; i++) { + String actionKey = defaultActions[i]; + if (addedKeys.contains(actionKey)) { + // If we already have added this, don't add it again. + continue; + } + if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { + addIfShouldShowAction(tempActions, shutdownAction); + } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { + addIfShouldShowAction(tempActions, mAirplaneModeOn); + } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { + if (shouldDisplayBugReport(currentUser.get())) { + addIfShouldShowAction(tempActions, new BugReportAction()); + } + } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) { + if (mShowSilentToggle) { + addIfShouldShowAction(tempActions, mSilentModeAction); + } + } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { + if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { + addUserActions(tempActions, currentUser.get()); + } + } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { + addIfShouldShowAction(tempActions, getSettingsAction()); + } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { + if (shouldDisplayLockdown(currentUser.get())) { + addIfShouldShowAction(tempActions, new LockDownAction()); + } + } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { + addIfShouldShowAction(tempActions, getVoiceAssistAction()); + } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { + addIfShouldShowAction(tempActions, getAssistAction()); + } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { + addIfShouldShowAction(tempActions, restartAction); + } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) { + addIfShouldShowAction(tempActions, new ScreenshotAction()); + } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) { + if (mDevicePolicyManager.isLogoutEnabled() + && currentUser.get() != null + && currentUser.get().id != UserHandle.USER_SYSTEM) { + addIfShouldShowAction(tempActions, new LogoutAction()); + } + } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) { + addIfShouldShowAction(tempActions, new EmergencyDialerAction()); + } else { + Log.e(TAG, "Invalid global action key " + actionKey); + } + // Add here so we don't add more than one. + addedKeys.add(actionKey); + } + + // replace power and restart with a single power options action, if needed + if (tempActions.contains(shutdownAction) && tempActions.contains(restartAction) + && tempActions.size() > getMaxShownPowerItems()) { + // transfer shutdown and restart to their own list of power actions + int powerOptionsIndex = Math.min(tempActions.indexOf(restartAction), + tempActions.indexOf(shutdownAction)); + tempActions.remove(shutdownAction); + tempActions.remove(restartAction); + mPowerItems.add(shutdownAction); + mPowerItems.add(restartAction); + + // add the PowerOptionsAction after Emergency, if present + tempActions.add(powerOptionsIndex, new PowerOptionsAction()); + } + for (Action action : tempActions) { + addActionItem(action); + } + } + + protected void onRotate() { + // re-allocate actions between main and overflow lists + this.createActionItems(); + } + + protected void initDialogItems() { + createActionItems(); + mAdapter = new MyAdapter(); + mOverflowAdapter = new MyOverflowAdapter(); + mPowerAdapter = new MyPowerOptionsAdapter(); + } + + /** + * Create the global actions dialog. + * + * @return A new dialog. + */ + protected ActionsDialogLite createDialog() { + initDialogItems(); + + mDepthController.setShowingHomeControls(false); + ActionsDialogLite dialog = new ActionsDialogLite(mContext, + com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite, + mAdapter, mOverflowAdapter, + mDepthController, mSysuiColorExtractor, + mStatusBarService, mNotificationShadeWindowController, + mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter); + + dialog.setCanceledOnTouchOutside(true); + dialog.setOnDismissListener(this); + dialog.setOnShowListener(this); + + return dialog; + } + + @VisibleForTesting + boolean shouldDisplayLockdown(UserInfo user) { + if (user == null) { + return false; + } + + int userId = user.id; + + // No lockdown option if it's not turned on in Settings + if (mSecureSettings.getIntForUser(Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, userId) == 0) { + return false; + } + + // Lockdown is meaningless without a place to go. + if (!mKeyguardStateController.isMethodSecure()) { + return false; + } + + // Only show the lockdown button if the device isn't locked down (for whatever reason). + int state = mLockPatternUtils.getStrongAuthForUser(userId); + return (state == STRONG_AUTH_NOT_REQUIRED + || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST); + } + + @VisibleForTesting + boolean shouldDisplayBugReport(UserInfo currentUser) { + return mGlobalSettings.getInt(Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 + && (currentUser == null || currentUser.isPrimary()); + } + + @Override + public void onUiModeChanged() { + mContext.getTheme().applyStyle(mContext.getThemeResId(), true); + if (mDialog != null && mDialog.isShowing()) { + mDialog.refreshDialog(); + } + } + + @Override + public void onConfigChanged(Configuration newConfig) { + if (mDialog != null && mDialog.isShowing() + && (newConfig.smallestScreenWidthDp != mSmallestScreenWidthDp)) { + mSmallestScreenWidthDp = newConfig.smallestScreenWidthDp; + mDialog.refreshDialog(); + } + } + + /** + * Clean up callbacks + */ + public void destroy() { + mConfigurationController.removeCallback(this); + } + + /** + * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is + * called when the quick access wallet requests dismissal. + */ + @Override + public void dismissGlobalActionsMenu() { + dismissDialog(); + } + + @VisibleForTesting + protected final class PowerOptionsAction extends SinglePressAction { + private PowerOptionsAction() { + super(com.android.systemui.R.drawable.ic_settings_power, + R.string.global_action_power_options); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + + @Override + public void onPress() { + if (mDialog != null) { + mDialog.showPowerOptionsMenu(); + } + } + } + + @VisibleForTesting + final class ShutDownAction extends SinglePressAction implements LongPressAction { + private ShutDownAction() { + super(R.drawable.ic_lock_power_off, + R.string.global_action_power_off); + } + + @Override + public boolean onLongPress() { + if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { + mWindowManagerFuncs.reboot(true); + return true; + } + return false; + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + + @Override + public void onPress() { + // shutdown by making sure radio and power are handled accordingly. + mWindowManagerFuncs.shutdown(); + } + } + + @VisibleForTesting + protected abstract class EmergencyAction extends SinglePressAction { + EmergencyAction(int iconResId, int messageResId) { + super(iconResId, messageResId); + } + + @Override + public boolean shouldBeSeparated() { + return false; + } + + @Override + public View create( + Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { + View v = super.create(context, convertView, parent, inflater); + int textColor = getEmergencyTextColor(context); + int iconColor = getEmergencyIconColor(context); + int backgroundColor = getEmergencyBackgroundColor(context); + TextView messageView = v.findViewById(R.id.message); + messageView.setTextColor(textColor); + messageView.setSelected(true); // necessary for marquee to work + ImageView icon = v.findViewById(R.id.icon); + icon.getDrawable().setTint(iconColor); + icon.setBackgroundTintList(ColorStateList.valueOf(backgroundColor)); + v.setBackgroundTintList(ColorStateList.valueOf(backgroundColor)); + return v; + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + } + + protected int getEmergencyTextColor(Context context) { + return context.getResources().getColor( + com.android.systemui.R.color.global_actions_lite_text); + } + + protected int getEmergencyIconColor(Context context) { + return context.getResources().getColor( + com.android.systemui.R.color.global_actions_lite_emergency_icon); + } + + protected int getEmergencyBackgroundColor(Context context) { + return context.getResources().getColor( + com.android.systemui.R.color.global_actions_lite_emergency_background); + } + + private class EmergencyAffordanceAction extends EmergencyAction { + EmergencyAffordanceAction() { + super(R.drawable.emergency_icon, + R.string.global_action_emergency); + } + + @Override + public void onPress() { + mEmergencyAffordanceManager.performEmergencyCall(); + } + } + + @VisibleForTesting + class EmergencyDialerAction extends EmergencyAction { + private EmergencyDialerAction() { + super(com.android.systemui.R.drawable.ic_emergency_star, + R.string.global_action_emergency); + } + + @Override + public void onPress() { + mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU); + mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS); + if (mTelecomManager != null) { + Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent( + null /* number */); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE, + EmergencyDialerConstants.ENTRY_TYPE_POWER_MENU); + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } + } + } + + @VisibleForTesting + EmergencyDialerAction makeEmergencyDialerActionForTesting() { + return new EmergencyDialerAction(); + } + + @VisibleForTesting + final class RestartAction extends SinglePressAction implements LongPressAction { + private RestartAction() { + super(R.drawable.ic_restart, R.string.global_action_restart); + } + + @Override + public boolean onLongPress() { + if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { + mWindowManagerFuncs.reboot(true); + return true; + } + return false; + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + + @Override + public void onPress() { + mWindowManagerFuncs.reboot(false); + } + } + + @VisibleForTesting + class ScreenshotAction extends SinglePressAction { + ScreenshotAction() { + super(R.drawable.ic_screenshot, R.string.global_action_screenshot); + } + + @Override + public void onPress() { + // Add a little delay before executing, to give the + // dialog a chance to go away before it takes a + // screenshot. + // TODO: instead, omit global action dialog layer + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true, + SCREENSHOT_GLOBAL_ACTIONS, mHandler, null); + mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); + mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS); + } + }, mDialogPressDelay); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + + @Override + public boolean shouldShow() { + // Include screenshot in power menu for legacy nav because it is not accessible + // through Recents in that mode + return is2ButtonNavigationEnabled(); + } + + boolean is2ButtonNavigationEnabled() { + return NAV_BAR_MODE_2BUTTON == mContext.getResources().getInteger( + com.android.internal.R.integer.config_navBarInteractionMode); + } + } + + @VisibleForTesting + ScreenshotAction makeScreenshotActionForTesting() { + return new ScreenshotAction(); + } + + @VisibleForTesting + class BugReportAction extends SinglePressAction implements LongPressAction { + + BugReportAction() { + super(R.drawable.ic_lock_bugreport, R.string.bugreport_title); + } + + @Override + public void onPress() { + // don't actually trigger the bugreport if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return; + } + // Add a little delay before executing, to give the + // dialog a chance to go away before it takes a + // screenshot. + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + try { + // Take an "interactive" bugreport. + mMetricsLogger.action( + MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE); + mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_PRESS); + if (!mIActivityManager.launchBugReportHandlerApp()) { + Log.w(TAG, "Bugreport handler could not be launched"); + mIActivityManager.requestInteractiveBugReport(); + } + } catch (RemoteException e) { + } + } + }, mDialogPressDelay); + } + + @Override + public boolean onLongPress() { + // don't actually trigger the bugreport if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return false; + } + try { + // Take a "full" bugreport. + mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); + mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS); + mIActivityManager.requestFullBugReport(); + } catch (RemoteException e) { + } + return false; + } + + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + } + + @VisibleForTesting + BugReportAction makeBugReportActionForTesting() { + return new BugReportAction(); + } + + private final class LogoutAction extends SinglePressAction { + private LogoutAction() { + super(R.drawable.ic_logout, R.string.global_action_logout); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + + @Override + public void onPress() { + // Add a little delay before executing, to give the dialog a chance to go away before + // switching user + mHandler.postDelayed(() -> { + try { + int currentUserId = getCurrentUser().id; + mIActivityManager.switchUser(UserHandle.USER_SYSTEM); + mIActivityManager.stopUser(currentUserId, true /*force*/, null); + } catch (RemoteException re) { + Log.e(TAG, "Couldn't logout user " + re); + } + }, mDialogPressDelay); + } + } + + private Action getSettingsAction() { + return new SinglePressAction(R.drawable.ic_settings, + R.string.global_action_settings) { + + @Override + public void onPress() { + Intent intent = new Intent(Settings.ACTION_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getAssistAction() { + return new SinglePressAction(R.drawable.ic_action_assist_focused, + R.string.global_action_assist) { + @Override + public void onPress() { + Intent intent = new Intent(Intent.ACTION_ASSIST); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getVoiceAssistAction() { + return new SinglePressAction(R.drawable.ic_voice_search, + R.string.global_action_voice_assist) { + @Override + public void onPress() { + Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + @VisibleForTesting + class LockDownAction extends SinglePressAction { + LockDownAction() { + super(R.drawable.ic_lock_lockdown, R.string.global_action_lockdown); + } + + @Override + public void onPress() { + mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, + UserHandle.USER_ALL); + try { + mIWindowManager.lockNow(null); + // Lock profiles (if any) on the background thread. + mBackgroundExecutor.execute(() -> lockProfiles()); + } catch (RemoteException e) { + Log.e(TAG, "Error while trying to lock device.", e); + } + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + } + + private void lockProfiles() { + final int currentUserId = getCurrentUser().id; + final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId); + for (final int id : profileIds) { + if (id != currentUserId) { + mTrustManager.setDeviceLockedForUser(id, true); + } + } + } + + protected UserInfo getCurrentUser() { + try { + return mIActivityManager.getCurrentUser(); + } catch (RemoteException re) { + return null; + } + } + + /** + * Non-thread-safe current user provider that caches the result - helpful when a method needs + * to fetch it an indeterminate number of times. + */ + private class CurrentUserProvider { + private UserInfo mUserInfo = null; + private boolean mFetched = false; + + @Nullable + UserInfo get() { + if (!mFetched) { + mFetched = true; + mUserInfo = getCurrentUser(); + } + return mUserInfo; + } + } + + private void addUserActions(List<Action> actions, UserInfo currentUser) { + if (mUserManager.isUserSwitcherEnabled()) { + List<UserInfo> users = mUserManager.getUsers(); + for (final UserInfo user : users) { + if (user.supportsSwitchToByUser()) { + boolean isCurrentUser = currentUser == null + ? user.id == 0 : (currentUser.id == user.id); + Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) + : null; + SinglePressAction switchToUser = new SinglePressAction( + R.drawable.ic_menu_cc, icon, + (user.name != null ? user.name : "Primary") + + (isCurrentUser ? " \u2714" : "")) { + public void onPress() { + try { + mIActivityManager.switchUser(user.id); + } catch (RemoteException re) { + Log.e(TAG, "Couldn't switch user " + re); + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + addIfShouldShowAction(actions, switchToUser); + } + } + } + } + + protected void prepareDialog() { + refreshSilentMode(); + mAirplaneModeOn.updateState(mAirplaneState); + mAdapter.notifyDataSetChanged(); + mLifecycle.setCurrentState(Lifecycle.State.RESUMED); + } + + private void refreshSilentMode() { + if (!mHasVibrator) { + Integer value = mRingerModeTracker.getRingerMode().getValue(); + final boolean silentModeOn = value != null && value != AudioManager.RINGER_MODE_NORMAL; + ((ToggleAction) mSilentModeAction).updateState( + silentModeOn ? ToggleState.On : ToggleState.Off); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onDismiss(DialogInterface dialog) { + if (mDialog == dialog) { + mDialog = null; + } + mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_CLOSE); + mWindowManagerFuncs.onGlobalActionsHidden(); + mLifecycle.setCurrentState(Lifecycle.State.CREATED); + } + + /** + * {@inheritDoc} + */ + @Override + public void onShow(DialogInterface dialog) { + mMetricsLogger.visible(MetricsEvent.POWER_MENU); + mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN); + } + + /** + * The adapter used for power menu items shown in the global actions dialog. + */ + public class MyAdapter extends MultiListAdapter { + private int countItems(boolean separated) { + int count = 0; + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + + if (action.shouldBeSeparated() == separated) { + count++; + } + } + return count; + } + + @Override + public int countSeparatedItems() { + return countItems(true); + } + + @Override + public int countListItems() { + return countItems(false); + } + + @Override + public int getCount() { + return countSeparatedItems() + countListItems(); + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public Action getItem(int position) { + int filteredPos = 0; + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + if (!shouldShowAction(action)) { + continue; + } + if (filteredPos == position) { + return action; + } + filteredPos++; + } + + throw new IllegalArgumentException("position " + position + + " out of range of showable actions" + + ", filtered count=" + getCount() + + ", keyguardshowing=" + mKeyguardShowing + + ", provisioned=" + mDeviceProvisioned); + } + + /** + * Get the row ID for an item + * @param position The position of the item within the adapter's data set + * @return + */ + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + Action action = getItem(position); + View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); + view.setOnClickListener(v -> onClickItem(position)); + if (action instanceof LongPressAction) { + view.setOnLongClickListener(v -> onLongClickItem(position)); + } + return view; + } + + @Override + public boolean onLongClickItem(int position) { + final Action action = mAdapter.getItem(position); + if (action instanceof LongPressAction) { + if (mDialog != null) { + mDialog.dismiss(); + } else { + Log.w(TAG, "Action long-clicked while mDialog is null."); + } + return ((LongPressAction) action).onLongPress(); + } + return false; + } + + @Override + public void onClickItem(int position) { + Action item = mAdapter.getItem(position); + if (!(item instanceof SilentModeTriStateAction)) { + if (mDialog != null) { + // don't dismiss the dialog if we're opening the power options menu + if (!(item instanceof PowerOptionsAction)) { + mDialog.dismiss(); + } + } else { + Log.w(TAG, "Action clicked while mDialog is null."); + } + item.onPress(); + } + } + + @Override + public boolean shouldBeSeparated(int position) { + return getItem(position).shouldBeSeparated(); + } + } + + /** + * The adapter used for items in the overflow menu. + */ + public class MyPowerOptionsAdapter extends BaseAdapter { + @Override + public int getCount() { + return mPowerItems.size(); + } + + @Override + public Action getItem(int position) { + return mPowerItems.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + Action action = getItem(position); + if (action == null) { + Log.w(TAG, "No power options action found at position: " + position); + return null; + } + int viewLayoutResource = com.android.systemui.R.layout.global_actions_power_item; + View view = convertView != null ? convertView + : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false); + view.setOnClickListener(v -> onClickItem(position)); + if (action instanceof LongPressAction) { + view.setOnLongClickListener(v -> onLongClickItem(position)); + } + ImageView icon = view.findViewById(R.id.icon); + TextView messageView = view.findViewById(R.id.message); + messageView.setSelected(true); // necessary for marquee to work + + icon.setImageDrawable(action.getIcon(mContext)); + icon.setScaleType(ScaleType.CENTER_CROP); + + if (action.getMessage() != null) { + messageView.setText(action.getMessage()); + } else { + messageView.setText(action.getMessageResId()); + } + return view; + } + + private boolean onLongClickItem(int position) { + final Action action = getItem(position); + if (action instanceof LongPressAction) { + if (mDialog != null) { + mDialog.dismiss(); + } else { + Log.w(TAG, "Action long-clicked while mDialog is null."); + } + return ((LongPressAction) action).onLongPress(); + } + return false; + } + + private void onClickItem(int position) { + Action item = getItem(position); + if (!(item instanceof SilentModeTriStateAction)) { + if (mDialog != null) { + mDialog.dismiss(); + } else { + Log.w(TAG, "Action clicked while mDialog is null."); + } + item.onPress(); + } + } + } + + /** + * The adapter used for items in the power options menu, triggered by the PowerOptionsAction. + */ + public class MyOverflowAdapter extends BaseAdapter { + @Override + public int getCount() { + return mOverflowItems.size(); + } + + @Override + public Action getItem(int position) { + return mOverflowItems.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + Action action = getItem(position); + if (action == null) { + Log.w(TAG, "No overflow action found at position: " + position); + return null; + } + int viewLayoutResource = com.android.systemui.R.layout.controls_more_item; + View view = convertView != null ? convertView + : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false); + TextView textView = (TextView) view; + if (action.getMessageResId() != 0) { + textView.setText(action.getMessageResId()); + } else { + textView.setText(action.getMessage()); + } + return textView; + } + + protected boolean onLongClickItem(int position) { + final Action action = getItem(position); + if (action instanceof LongPressAction) { + if (mDialog != null) { + mDialog.dismiss(); + } else { + Log.w(TAG, "Action long-clicked while mDialog is null."); + } + return ((LongPressAction) action).onLongPress(); + } + return false; + } + + protected void onClickItem(int position) { + Action item = getItem(position); + if (!(item instanceof SilentModeTriStateAction)) { + if (mDialog != null) { + mDialog.dismiss(); + } else { + Log.w(TAG, "Action clicked while mDialog is null."); + } + item.onPress(); + } + } + } + + // note: the scheme below made more sense when we were planning on having + // 8 different things in the global actions dialog. seems overkill with + // only 3 items now, but may as well keep this flexible approach so it will + // be easy should someone decide at the last minute to include something + // else, such as 'enable wifi', or 'enable bluetooth' + + /** + * What each item in the global actions dialog must be able to support. + */ + public interface Action { + /** + * @return Text that will be announced when dialog is created. null for none. + */ + CharSequence getLabelForAccessibility(Context context); + + /** + * Create the item's view + * @param context + * @param convertView + * @param parent + * @param inflater + * @return + */ + View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); + + /** + * Handle a regular press + */ + void onPress(); + + /** + * @return whether this action should appear in the dialog when the keygaurd is showing. + */ + boolean showDuringKeyguard(); + + /** + * @return whether this action should appear in the dialog before the + * device is provisioned.f + */ + boolean showBeforeProvisioning(); + + /** + * @return whether this action is enabled + */ + boolean isEnabled(); + + /** + * @return whether this action should be in a separate section + */ + default boolean shouldBeSeparated() { + return false; + } + + /** + * Return the id of the message associated with this action, or 0 if it doesn't have one. + * @return + */ + int getMessageResId(); + + /** + * Return the icon drawable for this action. + */ + Drawable getIcon(Context context); + + /** + * Return the message associated with this action, or null if it doesn't have one. + * @return + */ + CharSequence getMessage(); + + /** + * @return whether the action should be visible + */ + default boolean shouldShow() { + return true; + } + } + + /** + * An action that also supports long press. + */ + private interface LongPressAction extends Action { + boolean onLongPress(); + } + + /** + * A single press action maintains no state, just responds to a press and takes an action. + */ + + private abstract class SinglePressAction implements Action { + private final int mIconResId; + private final Drawable mIcon; + private final int mMessageResId; + private final CharSequence mMessage; + + protected SinglePressAction(int iconResId, int messageResId) { + mIconResId = iconResId; + mMessageResId = messageResId; + mMessage = null; + mIcon = null; + } + + protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) { + mIconResId = iconResId; + mMessageResId = 0; + mMessage = message; + mIcon = icon; + } + + public boolean isEnabled() { + return true; + } + + public String getStatus() { + return null; + } + + public abstract void onPress(); + + public CharSequence getLabelForAccessibility(Context context) { + if (mMessage != null) { + return mMessage; + } else { + return context.getString(mMessageResId); + } + } + + public int getMessageResId() { + return mMessageResId; + } + + public CharSequence getMessage() { + return mMessage; + } + + @Override + public Drawable getIcon(Context context) { + if (mIcon != null) { + return mIcon; + } else { + return context.getDrawable(mIconResId); + } + } + + public View create( + Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { + View v = inflater.inflate(getGridItemLayoutResource(), parent, false /* attach */); + // ConstraintLayout flow needs an ID to reference + v.setId(View.generateViewId()); + + ImageView icon = v.findViewById(R.id.icon); + TextView messageView = v.findViewById(R.id.message); + messageView.setSelected(true); // necessary for marquee to work + + icon.setImageDrawable(getIcon(context)); + icon.setScaleType(ScaleType.CENTER_CROP); + + if (mMessage != null) { + messageView.setText(mMessage); + } else { + messageView.setText(mMessageResId); + } + + return v; + } + } + + protected int getGridItemLayoutResource() { + return com.android.systemui.R.layout.global_actions_grid_item_lite; + } + + private enum ToggleState { + Off(false), + TurningOn(true), + TurningOff(true), + On(false); + + private final boolean mInTransition; + + ToggleState(boolean intermediate) { + mInTransition = intermediate; + } + + public boolean inTransition() { + return mInTransition; + } + } + + /** + * A toggle action knows whether it is on or off, and displays an icon and status message + * accordingly. + */ + private abstract class ToggleAction implements Action { + + protected ToggleState mState = ToggleState.Off; + + // prefs + protected int mEnabledIconResId; + protected int mDisabledIconResid; + protected int mMessageResId; + protected int mEnabledStatusMessageResId; + protected int mDisabledStatusMessageResId; + + /** + * @param enabledIconResId The icon for when this action is on. + * @param disabledIconResid The icon for when this action is off. + * @param message The general information message, e.g 'Silent Mode' + * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' + * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' + */ + ToggleAction(int enabledIconResId, + int disabledIconResid, + int message, + int enabledStatusMessageResId, + int disabledStatusMessageResId) { + mEnabledIconResId = enabledIconResId; + mDisabledIconResid = disabledIconResid; + mMessageResId = message; + mEnabledStatusMessageResId = enabledStatusMessageResId; + mDisabledStatusMessageResId = disabledStatusMessageResId; + } + + /** + * Override to make changes to resource IDs just before creating the View. + */ + void willCreate() { + + } + + @Override + public CharSequence getLabelForAccessibility(Context context) { + return context.getString(mMessageResId); + } + + private boolean isOn() { + return mState == ToggleState.On || mState == ToggleState.TurningOn; + } + + @Override + public CharSequence getMessage() { + return null; + } + @Override + public int getMessageResId() { + return isOn() ? mEnabledStatusMessageResId : mDisabledStatusMessageResId; + } + + private int getIconResId() { + return isOn() ? mEnabledIconResId : mDisabledIconResid; + } + + @Override + public Drawable getIcon(Context context) { + return context.getDrawable(getIconResId()); + } + + public View create(Context context, View convertView, ViewGroup parent, + LayoutInflater inflater) { + willCreate(); + + View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2, + parent, false /* attach */); + ViewGroup.LayoutParams p = v.getLayoutParams(); + p.width = WRAP_CONTENT; + v.setLayoutParams(p); + + ImageView icon = (ImageView) v.findViewById(R.id.icon); + TextView messageView = (TextView) v.findViewById(R.id.message); + final boolean enabled = isEnabled(); + + if (messageView != null) { + messageView.setText(getMessageResId()); + messageView.setEnabled(enabled); + messageView.setSelected(true); // necessary for marquee to work + } + + if (icon != null) { + icon.setImageDrawable(context.getDrawable(getIconResId())); + icon.setEnabled(enabled); + } + + v.setEnabled(enabled); + + return v; + } + + public final void onPress() { + if (mState.inTransition()) { + Log.w(TAG, "shouldn't be able to toggle when in transition"); + return; + } + + final boolean nowOn = !(mState == ToggleState.On); + onToggle(nowOn); + changeStateFromPress(nowOn); + } + + public boolean isEnabled() { + return !mState.inTransition(); + } + + /** + * Implementations may override this if their state can be in on of the intermediate states + * until some notification is received (e.g airplane mode is 'turning off' until we know the + * wireless connections are back online + * + * @param buttonOn Whether the button was turned on or off + */ + protected void changeStateFromPress(boolean buttonOn) { + mState = buttonOn ? ToggleState.On : ToggleState.Off; + } + + abstract void onToggle(boolean on); + + public void updateState(ToggleState state) { + mState = state; + } + } + + private class AirplaneModeAction extends ToggleAction { + AirplaneModeAction() { + super( + R.drawable.ic_lock_airplane_mode, + R.drawable.ic_lock_airplane_mode_off, + R.string.global_actions_toggle_airplane_mode, + R.string.global_actions_airplane_mode_on_status, + R.string.global_actions_airplane_mode_off_status); + } + + void onToggle(boolean on) { + if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) { + mIsWaitingForEcmExit = true; + // Launch ECM exit dialog + Intent ecmDialogIntent = + new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); + ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(ecmDialogIntent); + } else { + changeAirplaneModeSystemSetting(on); + } + } + + @Override + protected void changeStateFromPress(boolean buttonOn) { + if (!mHasTelephony) return; + + // In ECM mode airplane state cannot be changed + if (!TelephonyProperties.in_ecm_mode().orElse(false)) { + mState = buttonOn ? ToggleState.TurningOn : ToggleState.TurningOff; + mAirplaneState = mState; + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + } + + private class SilentModeToggleAction extends ToggleAction { + SilentModeToggleAction() { + super(R.drawable.ic_audio_vol_mute, + R.drawable.ic_audio_vol, + R.string.global_action_toggle_silent_mode, + R.string.global_action_silent_mode_on_status, + R.string.global_action_silent_mode_off_status); + } + + void onToggle(boolean on) { + if (on) { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); + } else { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + } + + private static class SilentModeTriStateAction implements Action, View.OnClickListener { + + private static final int[] ITEM_IDS = {R.id.option1, R.id.option2, R.id.option3}; + + private final AudioManager mAudioManager; + private final Handler mHandler; + + SilentModeTriStateAction(AudioManager audioManager, Handler handler) { + mAudioManager = audioManager; + mHandler = handler; + } + + private int ringerModeToIndex(int ringerMode) { + // They just happen to coincide + return ringerMode; + } + + private int indexToRingerMode(int index) { + // They just happen to coincide + return index; + } + + @Override + public CharSequence getLabelForAccessibility(Context context) { + return null; + } + + @Override + public int getMessageResId() { + return 0; + } + + @Override + public CharSequence getMessage() { + return null; + } + + @Override + public Drawable getIcon(Context context) { + return null; + } + + + public View create(Context context, View convertView, ViewGroup parent, + LayoutInflater inflater) { + View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); + + int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); + for (int i = 0; i < 3; i++) { + View itemView = v.findViewById(ITEM_IDS[i]); + itemView.setSelected(selectedIndex == i); + // Set up click handler + itemView.setTag(i); + itemView.setOnClickListener(this); + } + return v; + } + + public void onPress() { + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + + public boolean isEnabled() { + return true; + } + + void willCreate() { + } + + public void onClick(View v) { + if (!(v.getTag() instanceof Integer)) return; + + int index = (Integer) v.getTag(); + mAudioManager.setRingerMode(indexToRingerMode(index)); + mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); + } + } + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) + || Intent.ACTION_SCREEN_OFF.equals(action)) { + String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); + if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { + mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISMISS, reason)); + } + } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { + // Airplane mode can be changed after ECM exits if airplane toggle button + // is pressed during ECM mode + if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)) + && mIsWaitingForEcmExit) { + mIsWaitingForEcmExit = false; + changeAirplaneModeSystemSetting(true); + } + } + } + }; + + private final TelephonyCallback.ServiceStateListener mPhoneStateListener = + new TelephonyCallback.ServiceStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + if (!mHasTelephony) return; + if (mAirplaneModeOn == null) { + Log.d(TAG, "Service changed before actions created"); + return; + } + final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; + mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off; + mAirplaneModeOn.updateState(mAirplaneState); + mAdapter.notifyDataSetChanged(); + mOverflowAdapter.notifyDataSetChanged(); + mPowerAdapter.notifyDataSetChanged(); + } + }; + + private ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) { + @Override + public void onChange(boolean selfChange) { + onAirplaneModeChanged(); + } + }; + + private static final int MESSAGE_DISMISS = 0; + private static final int MESSAGE_REFRESH = 1; + private static final int DIALOG_DISMISS_DELAY = 300; // ms + private static final int DIALOG_PRESS_DELAY = 850; // ms + + @VisibleForTesting void setZeroDialogPressDelayForTesting() { + mDialogPressDelay = 0; // ms + } + + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_DISMISS: + if (mDialog != null) { + if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { + mDialog.completeDismiss(); + } else { + mDialog.dismiss(); + } + mDialog = null; + } + break; + case MESSAGE_REFRESH: + refreshSilentMode(); + mAdapter.notifyDataSetChanged(); + break; + } + } + }; + + private void onAirplaneModeChanged() { + // Let the service state callbacks handle the state. + if (mHasTelephony || mAirplaneModeOn == null) return; + + boolean airplaneModeOn = mGlobalSettings.getInt( + Settings.Global.AIRPLANE_MODE_ON, + 0) == 1; + mAirplaneState = airplaneModeOn ? ToggleState.On : ToggleState.Off; + mAirplaneModeOn.updateState(mAirplaneState); + } + + /** + * Change the airplane mode system setting + */ + private void changeAirplaneModeSystemSetting(boolean on) { + mGlobalSettings.putInt(Settings.Global.AIRPLANE_MODE_ON, on ? 1 : 0); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra("state", on); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + if (!mHasTelephony) { + mAirplaneState = on ? ToggleState.On : ToggleState.Off; + } + } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return mLifecycle; + } + + @VisibleForTesting + static class ActionsDialogLite extends Dialog implements DialogInterface, + ColorExtractor.OnColorsChangedListener { + + protected final Context mContext; + protected MultiListLayout mGlobalActionsLayout; + protected final MyAdapter mAdapter; + protected final MyOverflowAdapter mOverflowAdapter; + protected final MyPowerOptionsAdapter mPowerOptionsAdapter; + protected final IStatusBarService mStatusBarService; + protected final IBinder mToken = new Binder(); + protected Drawable mBackgroundDrawable; + protected final SysuiColorExtractor mColorExtractor; + private boolean mKeyguardShowing; + protected boolean mShowing; + protected float mScrimAlpha; + protected final NotificationShadeWindowController mNotificationShadeWindowController; + protected final NotificationShadeDepthController mDepthController; + protected final SysUiState mSysUiState; + private ListPopupWindow mOverflowPopup; + private Dialog mPowerOptionsDialog; + protected final Runnable mOnRotateCallback; + + protected ViewGroup mContainer; + + ActionsDialogLite(Context context, int themeRes, MyAdapter adapter, + MyOverflowAdapter overflowAdapter, + NotificationShadeDepthController depthController, + SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, + NotificationShadeWindowController notificationShadeWindowController, + SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, + MyPowerOptionsAdapter powerAdapter) { + super(context, themeRes); + mContext = context; + mAdapter = adapter; + mOverflowAdapter = overflowAdapter; + mPowerOptionsAdapter = powerAdapter; + mDepthController = depthController; + mColorExtractor = sysuiColorExtractor; + mStatusBarService = statusBarService; + mNotificationShadeWindowController = notificationShadeWindowController; + mSysUiState = sysuiState; + mOnRotateCallback = onRotateCallback; + mKeyguardShowing = keyguardShowing; + + // Window initialization + Window window = getWindow(); + window.requestFeature(Window.FEATURE_NO_TITLE); + // Inflate the decor view, so the attributes below are not overwritten by the theme. + window.getDecorView(); + window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + window.setLayout(MATCH_PARENT, WRAP_CONTENT); + window.addFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); + window.getAttributes().setFitInsetsTypes(0 /* types */); + setTitle(R.string.global_actions); + + initializeLayout(); + } + + private ListPopupWindow createPowerOverflowPopup() { + GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu( + new ContextThemeWrapper( + mContext, + com.android.systemui.R.style.Control_ListPopupWindow + ), false /* isDropDownMode */); + popup.setOnItemClickListener( + (parent, view, position, id) -> mOverflowAdapter.onClickItem(position)); + popup.setOnItemLongClickListener( + (parent, view, position, id) -> mOverflowAdapter.onLongClickItem(position)); + View overflowButton = + findViewById(com.android.systemui.R.id.global_actions_overflow_button); + popup.setAnchorView(overflowButton); + popup.setAdapter(mOverflowAdapter); + return popup; + } + + public void showPowerOptionsMenu() { + mPowerOptionsDialog = GlobalActionsPowerDialog.create(mContext, mPowerOptionsAdapter); + mPowerOptionsDialog.show(); + } + + protected void showPowerOverflowMenu() { + mOverflowPopup = createPowerOverflowPopup(); + mOverflowPopup.show(); + } + + protected int getLayoutResource() { + return com.android.systemui.R.layout.global_actions_grid_lite; + } + + protected void initializeLayout() { + setContentView(getLayoutResource()); + fixNavBarClipping(); + + mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view); + mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() { + @Override + public boolean dispatchPopulateAccessibilityEvent( + View host, AccessibilityEvent event) { + // Populate the title here, just as Activity does + event.getText().add(mContext.getString(R.string.global_actions)); + return true; + } + }); + mGlobalActionsLayout.setRotationListener(this::onRotate); + mGlobalActionsLayout.setAdapter(mAdapter); + mContainer = findViewById(com.android.systemui.R.id.global_actions_container); + + View overflowButton = findViewById( + com.android.systemui.R.id.global_actions_overflow_button); + if (overflowButton != null) { + if (mOverflowAdapter.getCount() > 0) { + overflowButton.setOnClickListener((view) -> showPowerOverflowMenu()); + LinearLayout.LayoutParams params = + (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams(); + params.setMarginEnd(0); + mGlobalActionsLayout.setLayoutParams(params); + } else { + overflowButton.setVisibility(View.GONE); + LinearLayout.LayoutParams params = + (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams(); + params.setMarginEnd(mContext.getResources().getDimensionPixelSize( + com.android.systemui.R.dimen.global_actions_side_margin)); + mGlobalActionsLayout.setLayoutParams(params); + } + } + + if (mBackgroundDrawable == null) { + mBackgroundDrawable = new ScrimDrawable(); + mScrimAlpha = 1.0f; + } + } + + protected void fixNavBarClipping() { + ViewGroup content = findViewById(android.R.id.content); + content.setClipChildren(false); + content.setClipToPadding(false); + ViewGroup contentParent = (ViewGroup) content.getParent(); + contentParent.setClipChildren(false); + contentParent.setClipToPadding(false); + } + + @Override + protected void onStart() { + super.setCanceledOnTouchOutside(true); + super.onStart(); + mGlobalActionsLayout.updateList(); + + if (mBackgroundDrawable instanceof ScrimDrawable) { + mColorExtractor.addOnColorsChangedListener(this); + GradientColors colors = mColorExtractor.getNeutralColors(); + updateColors(colors, false /* animate */); + } + } + + /** + * Updates background and system bars according to current GradientColors. + * + * @param colors Colors and hints to use. + * @param animate Interpolates gradient if true, just sets otherwise. + */ + private void updateColors(GradientColors colors, boolean animate) { + if (!(mBackgroundDrawable instanceof ScrimDrawable)) { + return; + } + ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate); + View decorView = getWindow().getDecorView(); + if (colors.supportsDarkText()) { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } else { + decorView.setSystemUiVisibility(0); + } + } + + @Override + protected void onStop() { + super.onStop(); + mColorExtractor.removeOnColorsChangedListener(this); + } + + @Override + public void show() { + super.show(); + // split this up so we can override but still call Dialog.show + showDialog(); + } + + protected void showDialog() { + mShowing = true; + mNotificationShadeWindowController.setRequestTopUi(true, TAG); + mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true) + .commitUpdate(mContext.getDisplayId()); + + ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView(); + root.setOnApplyWindowInsetsListener((v, windowInsets) -> { + root.setPadding(windowInsets.getStableInsetLeft(), + windowInsets.getStableInsetTop(), + windowInsets.getStableInsetRight(), + windowInsets.getStableInsetBottom()); + return WindowInsets.CONSUMED; + }); + + mBackgroundDrawable.setAlpha(0); + float xOffset = mGlobalActionsLayout.getAnimationOffsetX(); + ObjectAnimator alphaAnimator = + ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f); + alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + alphaAnimator.setDuration(183); + alphaAnimator.addUpdateListener((animation) -> { + float animatedValue = animation.getAnimatedFraction(); + int alpha = (int) (animatedValue * mScrimAlpha * 255); + mBackgroundDrawable.setAlpha(alpha); + mDepthController.updateGlobalDialogVisibility(animatedValue, mGlobalActionsLayout); + }); + + ObjectAnimator xAnimator = + ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f); + xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + xAnimator.setDuration(350); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(alphaAnimator, xAnimator); + animatorSet.start(); + } + + @Override + public void dismiss() { + dismissWithAnimation(() -> { + dismissInternal(); + }); + } + + protected void dismissInternal() { + mContainer.setTranslationX(0); + ObjectAnimator alphaAnimator = + ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f); + alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); + alphaAnimator.setDuration(233); + alphaAnimator.addUpdateListener((animation) -> { + float animatedValue = 1f - animation.getAnimatedFraction(); + int alpha = (int) (animatedValue * mScrimAlpha * 255); + mBackgroundDrawable.setAlpha(alpha); + mDepthController.updateGlobalDialogVisibility(animatedValue, mGlobalActionsLayout); + }); + + float xOffset = mGlobalActionsLayout.getAnimationOffsetX(); + ObjectAnimator xAnimator = + ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset); + xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); + xAnimator.setDuration(350); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(alphaAnimator, xAnimator); + animatorSet.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + completeDismiss(); + } + }); + + animatorSet.start(); + + // close first, as popup windows will not fade during the animation + dismissOverflow(false); + dismissPowerOptions(false); + } + + void dismissWithAnimation(Runnable animation) { + if (!mShowing) { + return; + } + mShowing = false; + animation.run(); + } + + protected void completeDismiss() { + mShowing = false; + dismissOverflow(true); + dismissPowerOptions(true); + mNotificationShadeWindowController.setRequestTopUi(false, TAG); + mDepthController.updateGlobalDialogVisibility(0, null /* view */); + mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false) + .commitUpdate(mContext.getDisplayId()); + super.dismiss(); + } + + protected final void dismissOverflow(boolean immediate) { + if (mOverflowPopup != null) { + if (immediate) { + mOverflowPopup.dismissImmediate(); + } else { + mOverflowPopup.dismiss(); + } + } + } + + protected final void dismissPowerOptions(boolean immediate) { + if (mPowerOptionsDialog != null) { + if (immediate) { + mPowerOptionsDialog.dismiss(); + } else { + mPowerOptionsDialog.dismiss(); + } + } + } + + protected final void setRotationSuggestionsEnabled(boolean enabled) { + try { + final int userId = Binder.getCallingUserHandle().getIdentifier(); + final int what = enabled + ? StatusBarManager.DISABLE2_NONE + : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS; + mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + @Override + public void onColorsChanged(ColorExtractor extractor, int which) { + if (mKeyguardShowing) { + if ((WallpaperManager.FLAG_LOCK & which) != 0) { + updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK), + true /* animate */); + } + } else { + if ((WallpaperManager.FLAG_SYSTEM & which) != 0) { + updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM), + true /* animate */); + } + } + } + + public void setKeyguardShowing(boolean keyguardShowing) { + mKeyguardShowing = keyguardShowing; + } + + public void refreshDialog() { + // ensure dropdown menus are dismissed before re-initializing the dialog + dismissOverflow(true); + dismissPowerOptions(true); + + // re-create dialog + initializeLayout(); + mGlobalActionsLayout.updateList(); + } + + public void onRotate(int from, int to) { + if (mShowing) { + mOnRotateCallback.run(); + refreshDialog(); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java new file mode 100644 index 000000000000..eb4cd6b449a2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2021 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.globalactions; + +import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import androidx.constraintlayout.helper.widget.Flow; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.HardwareBgDrawable; +import com.android.systemui.R; + +/** + * ConstraintLayout implementation of the button layout created by the global actions dialog. + */ +public class GlobalActionsLayoutLite extends GlobalActionsLayout { + private final int mMaxColumns; + private final int mMaxRows; + + public GlobalActionsLayoutLite(Context context, AttributeSet attrs) { + super(context, attrs); + mMaxColumns = getResources().getInteger( + com.android.systemui.R.integer.power_menu_lite_max_columns); + mMaxRows = getResources().getInteger( + com.android.systemui.R.integer.power_menu_lite_max_rows); + } + + @VisibleForTesting + @Override + protected boolean shouldReverseListItems() { + // Handled in XML + return false; + } + + @Override + protected HardwareBgDrawable getBackgroundDrawable(int backgroundColor) { + return null; + } + + @Override + public void onUpdateList() { + super.onUpdateList(); + int nElementsWrap = (getCurrentRotation() == ROTATION_NONE) ? mMaxColumns : mMaxRows; + int nChildren = getListView().getChildCount() - 1; // don't count flow element + if (getCurrentRotation() != ROTATION_NONE && nChildren > mMaxRows) { + // up to 4 elements can fit in a row in landscape, otherwise limit for balance + nElementsWrap -= 1; + } + Flow flow = findViewById(R.id.list_flow); + flow.setMaxElementsWrap(nElementsWrap); + } + + @Override + protected void addToListView(View v, boolean reverse) { + super.addToListView(v, reverse); + Flow flow = findViewById(R.id.list_flow); + flow.addView(v); + } + + @Override + protected void removeAllListViews() { + View flow = findViewById(R.id.list_flow); + super.removeAllListViews(); + + // Add flow element back after clearing the list view + super.addToListView(flow, false); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + boolean anyTruncated = false; + ViewGroup listView = getListView(); + + // Check to see if any of the GlobalActionsItems have had their messages truncated + for (int i = 0; i < listView.getChildCount(); i++) { + View child = listView.getChildAt(i); + if (child instanceof GlobalActionsItem) { + GlobalActionsItem item = (GlobalActionsItem) child; + anyTruncated = anyTruncated || item.isTruncated(); + } + } + // If any of the items have been truncated, set the all to single-line marquee + if (anyTruncated) { + for (int i = 0; i < listView.getChildCount(); i++) { + View child = listView.getChildAt(i); + if (child instanceof GlobalActionsItem) { + GlobalActionsItem item = (GlobalActionsItem) child; + item.setMarquee(true); + } + } + } + } + + @VisibleForTesting + protected float getGridItemSize() { + return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height); + } + + @VisibleForTesting + protected float getAnimationDistance() { + return getGridItemSize() / 2; + } + + @Override + public float getAnimationOffsetX() { + return getAnimationDistance(); + } + + @Override + public float getAnimationOffsetY() { + return 0f; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java index 531ccb4eb1cb..9ea938325659 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java @@ -436,6 +436,9 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @Override public void abortCurrentGesture() { Log.d("b/63783866", "KeyButtonView.abortCurrentGesture"); + if (mCode != KeyEvent.KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); + } setPressed(false); mRipple.abortDelayedRipple(); mGestureAborted = true; diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index ba41d33bab92..02c12f6d51e5 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -19,6 +19,9 @@ package com.android.systemui.people; import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; +import static com.android.systemui.people.PeopleTileViewHelper.getPersonIconBitmap; +import static com.android.systemui.people.PeopleTileViewHelper.getSizeInDp; + import android.app.Activity; import android.app.INotificationManager; import android.app.people.IPeopleManager; @@ -149,11 +152,11 @@ public class PeopleSpaceActivity extends Activity { /** Sets {@code tileView} with the data in {@code conversation}. */ private void setTileView(PeopleSpaceTileView tileView, PeopleSpaceTile tile) { try { - String pkg = tile.getPackageName(); - tileView.setName(tile.getUserName().toString()); - tileView.setPackageIcon(mPackageManager.getApplicationIcon(pkg)); - tileView.setPersonIcon(tile.getUserIcon()); + tileView.setPersonIcon(getPersonIconBitmap(mContext, tile, + getSizeInDp(mContext, R.dimen.avatar_size_for_medium, + mContext.getResources().getDisplayMetrics().density))); + tileView.setOnClickListener(v -> storeWidgetConfiguration(tile)); } catch (Exception e) { Log.e(TAG, "Couldn't retrieve shortcut information", e); diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java index e30ad80be3f6..36b435b04fd6 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java @@ -19,8 +19,7 @@ package com.android.systemui.people; import android.app.people.PeopleSpaceTile; import android.content.Context; import android.content.pm.LauncherApps; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; +import android.graphics.Bitmap; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -37,7 +36,6 @@ public class PeopleSpaceTileView extends LinearLayout { private View mTileView; private TextView mNameView; - private ImageView mPackageIconView; private ImageView mPersonIconView; public PeopleSpaceTileView(Context context, ViewGroup view, String shortcutId, boolean isLast) { @@ -56,7 +54,6 @@ public class PeopleSpaceTileView extends LinearLayout { } } mNameView = mTileView.findViewById(R.id.tile_view_name); - mPackageIconView = mTileView.findViewById(R.id.tile_view_package_icon); mPersonIconView = mTileView.findViewById(R.id.tile_view_person_icon); } @@ -65,14 +62,9 @@ public class PeopleSpaceTileView extends LinearLayout { mNameView.setText(name); } - /** Sets the package drawable on the tile. */ - public void setPackageIcon(Drawable drawable) { - mPackageIconView.setImageDrawable(drawable); - } - - /** Sets the person bitmap on the tile. */ - public void setPersonIcon(Icon icon) { - mPersonIconView.setImageIcon(icon); + /** Sets the person and package drawable on the tile. */ + public void setPersonIcon(Bitmap bitmap) { + mPersonIconView.setImageBitmap(bitmap); } /** Sets the click listener of the tile. */ diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 8d1b712e0807..96fbe6987562 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -192,7 +192,11 @@ class PeopleTileViewHelper { } private int getSizeInDp(int dimenResourceId) { - return (int) (mContext.getResources().getDimension(dimenResourceId) / mDensity); + return getSizeInDp(mContext, dimenResourceId, mDensity); + } + + public static int getSizeInDp(Context context, int dimenResourceId, float density) { + return (int) (context.getResources().getDimension(dimenResourceId) / density); } private int getContentHeightForLayout(int lineHeight) { @@ -278,24 +282,11 @@ class PeopleTileViewHelper { } else { views.setViewVisibility(R.id.availability, View.GONE); } - boolean hasNewStory = - mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch( - c -> c.getActivity() == ACTIVITY_NEW_STORY); + views.setTextViewText(R.id.name, mTile.getUserName().toString()); views.setBoolean(R.id.image, "setClipToOutline", true); - - Icon icon = mTile.getUserIcon(); - PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(mContext, - mContext.getPackageManager(), - IconDrawableFactory.newInstance(mContext, false), - maxAvatarSize); - Drawable drawable = icon.loadDrawable(mContext); - Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable, - mTile.getPackageName(), getUserId(mTile), mTile.isImportantConversation(), - hasNewStory); - Bitmap bitmap = convertDrawableToBitmap(personDrawable); - views.setImageViewBitmap(R.id.person_icon, bitmap); - + views.setImageViewBitmap(R.id.person_icon, + getPersonIconBitmap(mContext, mTile, maxAvatarSize)); return views; } catch (Exception e) { Log.e(TAG, "Failed to set common fields: " + e); @@ -583,4 +574,23 @@ class PeopleTileViewHelper { return R.layout.people_tile_small; } } + + /** Returns a bitmap with the user icon and package icon. */ + public static Bitmap getPersonIconBitmap( + Context context, PeopleSpaceTile tile, int maxAvatarSize) { + boolean hasNewStory = + tile.getStatuses() != null && tile.getStatuses().stream().anyMatch( + c -> c.getActivity() == ACTIVITY_NEW_STORY); + + Icon icon = tile.getUserIcon(); + PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context, + context.getPackageManager(), + IconDrawableFactory.newInstance(context, false), + maxAvatarSize); + Drawable drawable = icon.loadDrawable(context); + Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable, + tile.getPackageName(), getUserId(tile), tile.isImportantConversation(), + hasNewStory); + return convertDrawableToBitmap(personDrawable); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index c57ff1276a3f..ea471b957d68 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -32,6 +32,7 @@ import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.TouchAnimator.Listener; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.tileimpl.HeightOverrideable; +import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -54,6 +55,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows"; public static final float EXPANDED_TILE_DELAY = .86f; + private static final long QQS_FADE_IN_DURATION = 200L; + // Fade out faster than fade in to finish before QQS hides. + private static final long QQS_FADE_OUT_DURATION = 50L; private final ArrayList<View> mAllViews = new ArrayList<>(); @@ -87,7 +91,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private HeightExpansionAnimator mOtherTilesExpandAnimator; private boolean mNeedsAnimatorUpdate = false; - + private boolean mToShowing; private boolean mOnKeyguard; private boolean mAllowFancy; @@ -150,6 +154,18 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } } + void startAlphaAnimation(boolean show) { + if (show == mToShowing) { + return; + } + mToShowing = show; + if (show) { + CrossFadeHelper.fadeIn(mQs.getView(), QQS_FADE_IN_DURATION, 0 /* delay */); + } else { + CrossFadeHelper.fadeOut(mQs.getView(), QQS_FADE_OUT_DURATION, 0 /* delay */, + null /* endRunnable */); + } + } /** * Sets whether or not the keyguard is currently being shown with a collapsed header. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index e8976d379fef..5256bc45b7b5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -92,6 +92,11 @@ public class QSContainerImpl extends FrameLayout { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } + @Override + public boolean hasOverlappingRendering() { + return false; + } + void onMediaVisibilityChanged(boolean qsVisible) { mAnimateBottomOnNextLayout = qsVisible; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index f1f4e16206a1..52e05a4fd6c5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -32,6 +32,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; +import com.android.systemui.globalactions.GlobalActionsDialogLite; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.settings.UserTracker; @@ -68,6 +69,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final PageIndicator mPageIndicator; private final View mPowerMenuLite; private final boolean mShowPMLiteButton; + private GlobalActionsDialogLite mGlobalActionsDialog; private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = new UserInfoController.OnUserInfoChangedListener() { @@ -115,6 +117,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme } else { startSettingsActivity(); } + } else if (v == mPowerMenuLite) { + mGlobalActionsDialog.showOrHideDialog(false, true); } } }; @@ -129,7 +133,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme QSPanelController qsPanelController, QSDetailDisplayer qsDetailDisplayer, QuickQSPanelController quickQSPanelController, TunerService tunerService, MetricsLogger metricsLogger, - @Named(PM_LITE_ENABLED) boolean showPMLiteButton) { + @Named(PM_LITE_ENABLED) boolean showPMLiteButton, + GlobalActionsDialogLite globalActionsDialog) { super(view); mUserManager = userManager; mUserInfoController = userInfoController; @@ -149,11 +154,15 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mPageIndicator = mView.findViewById(R.id.footer_page_indicator); mPowerMenuLite = mView.findViewById(R.id.pm_lite); mShowPMLiteButton = showPMLiteButton; + mGlobalActionsDialog = globalActionsDialog; } @Override protected void onViewAttached() { - if (!mShowPMLiteButton) { + if (mShowPMLiteButton) { + mPowerMenuLite.setVisibility(View.VISIBLE); + mPowerMenuLite.setOnClickListener(mSettingsOnClickListener); + } else { mPowerMenuLite.setVisibility(View.GONE); } mView.addOnLayoutChangeListener( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 7c9f0b082d8f..94b99c3d9f2a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -190,7 +190,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { boolean sizeChanged = (oldTop - oldBottom) != (top - bottom); if (sizeChanged) { - setQsExpansion(mLastQSExpansion, mLastQSExpansion); + setQsExpansion(mLastQSExpansion, mLastHeaderTranslation); } }); } @@ -395,6 +395,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca @Override public void setQsExpansion(float expansion, float headerTranslation) { if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation); + + if (mQSAnimator != null) { + final boolean showQSOnLockscreen = expansion > 0; + final boolean showQSUnlocked = headerTranslation == 0; + mQSAnimator.startAlphaAnimation(showQSOnLockscreen || showQSUnlocked); + } mContainer.setExpansion(expansion); final float translationScaleY = expansion - 1; boolean onKeyguardAndExpanded = isKeyguardShowing() && !mShowCollapsedOnKeyguard; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 1d307364d661..5f3933b827c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -105,7 +105,6 @@ public class NotificationChildrenContainer extends ViewGroup { private ViewGroup mCurrentHeader; private boolean mIsConversation; - private boolean mTintWithThemeAccent; private boolean mShowGroupCountInExpander; private boolean mShowDividersWhenExpanded; private boolean mHideDividersDuringExpand; @@ -149,8 +148,6 @@ public class NotificationChildrenContainer extends ViewGroup { com.android.internal.R.dimen.notification_content_margin); mEnableShadowOnChildNotifications = res.getBoolean(R.bool.config_enableShadowOnChildNotifications); - mTintWithThemeAccent = - res.getBoolean(com.android.internal.R.bool.config_tintNotificationsWithTheme); mShowGroupCountInExpander = res.getBoolean(R.bool.config_showNotificationGroupCountInExpander); mShowDividersWhenExpanded = @@ -1223,14 +1220,11 @@ public class NotificationChildrenContainer extends ViewGroup { return; } int color = mContainingNotification.getNotificationColor(); - if (mTintWithThemeAccent) { - // We're using the theme accent, color with the accent color instead of the notif color - Resources.Theme theme = new ContextThemeWrapper(mContext, - com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme(); - TypedArray ta = theme.obtainStyledAttributes( - new int[]{com.android.internal.R.attr.colorAccent}); + Resources.Theme theme = new ContextThemeWrapper(mContext, + com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme(); + try (TypedArray ta = theme.obtainStyledAttributes( + new int[]{com.android.internal.R.attr.colorAccent})) { color = ta.getColor(0, color); - ta.recycle(); } mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, color); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 6b69103f6030..5ff9b703924e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -555,6 +555,13 @@ public class KeyguardBouncer { pw.println(" mIsAnimatingAway: " + mIsAnimatingAway); } + /** Update keyguard position based on a tapped X coordinate. */ + public void updateKeyguardPosition(float x) { + if (mKeyguardViewController != null) { + mKeyguardViewController.updateKeyguardPosition(x); + } + } + public interface BouncerExpansionCallback { void onFullyShown(); void onStartingToHide(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index d7148b01a44f..a38cf61d5e4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3494,6 +3494,12 @@ public class NotificationPanelViewController extends PanelViewController { updateHorizontalPanelPosition(event.getX()); handled = true; } + + if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded() + && mStatusBarKeyguardViewManager.isShowing()) { + mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX()); + } + handled |= super.onTouch(v, event); return !mDozing || mPulsing || handled || showingOrAnimatingAltAuth; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 7ee7aa4100d4..0b6bbcd407f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -1150,6 +1150,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb || mAlternateAuthInterceptor.isAnimating()); } + /** Update keyguard position based on a tapped X coordinate. */ + public void updateKeyguardPosition(float x) { + if (mBouncer != null) { + mBouncer.updateKeyguardPosition(x); + } + } + private static class DismissWithActionRequest { final OnDismissAction dismissAction; final Runnable cancelAction; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java index 714f1f235c52..4d526508ce0f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java @@ -124,4 +124,11 @@ public class KeyguardHostViewControllerTest extends SysuiTestCase { ((FrameLayout.LayoutParams) mKeyguardHostView.getLayoutParams()).gravity, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); } + + @Test + public void testUpdateKeyguardPositionDelegatesToSecurityContainer() { + mKeyguardHostViewController.updateKeyguardPosition(1.0f); + + verify(mKeyguardSecurityContainerController).updateKeyguardPosition(1.0f); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 99b3f6f19f81..ca857c5dd05b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -177,4 +177,10 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardSecurityContainerController.updateResources(); verify(mView, times(1)).updateLayoutForSecurityMode(any()); } + + @Test + public void updateKeyguardPosition_callsThroughToView() { + mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f); + verify(mView).updateKeyguardPosition(1.0f); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 104318e0f4ae..9557d5cdb982 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -19,6 +19,9 @@ package com.android.keyguard; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -198,6 +201,46 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, expectedHeightMeasureSpec); } + private void setupForUpdateKeyguardPosition(SecurityMode securityMode) { + setUpKeyguard( + /* deviceConfigCanUseOneHandedKeyguard= */true, + /* sysuiResourceCanUseOneHandedKeyguard= */ true, + securityMode); + + mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); + mKeyguardSecurityContainer.layout(0, 0, SCREEN_WIDTH, SCREEN_WIDTH); + + // Start off left-aligned. This should happen anyway, but just do this to ensure + // definitely move to the left. + mKeyguardSecurityContainer.updateKeyguardPosition(0.0f); + + // Clear any interactions with the mock so we know the interactions definitely come from the + // below testing. + reset(mSecurityViewFlipper); + } + + @Test + public void updateKeyguardPosition_movesKeyguard() { + setupForUpdateKeyguardPosition(ONE_HANDED_SECURITY_MODE); + + mKeyguardSecurityContainer.updateKeyguardPosition((SCREEN_WIDTH / 4f) * 3f); + verify(mSecurityViewFlipper).setTranslationX(SCREEN_WIDTH / 2.0f); + + mKeyguardSecurityContainer.updateKeyguardPosition(0.0f); + verify(mSecurityViewFlipper).setTranslationX(0.0f); + } + + @Test + public void updateKeyguardPosition_doesntMoveTwoHandedKeyguard() { + setupForUpdateKeyguardPosition(TWO_HANDED_SECURITY_MODE); + + mKeyguardSecurityContainer.updateKeyguardPosition((SCREEN_WIDTH / 4f) * 3f); + verify(mSecurityViewFlipper, never()).setTranslationX(anyInt()); + + mKeyguardSecurityContainer.updateKeyguardPosition(0.0f); + verify(mSecurityViewFlipper, never()).setTranslationX(anyInt()); + } + private void setUpKeyguard( boolean deviceConfigCanUseOneHandedKeyguard, boolean sysuiResourceCanUseOneHandedKeyguard, diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt new file mode 100644 index 000000000000..feaedc53fbc1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 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.camera + +import android.content.Intent +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import com.android.systemui.SysuiTestCase +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class CameraIntentsTest : SysuiTestCase() { + companion object { + val VALID_SECURE_INTENT = Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION) + val VALID_INSECURE_INTENT = Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION) + } + + @Test + fun testIsSecureCameraIntent() { + assertTrue(CameraIntents.isSecureCameraIntent(VALID_SECURE_INTENT)) + // an intent with a specific package is still a valid secure camera intent + assertTrue(CameraIntents.isSecureCameraIntent( + Intent(VALID_SECURE_INTENT).setPackage("com.example"))) + assertFalse(CameraIntents.isSecureCameraIntent(null)) + assertFalse(CameraIntents.isSecureCameraIntent(Intent())) + assertFalse(CameraIntents.isSecureCameraIntent(Intent(Intent.ACTION_MAIN))) + } + @Test + fun testIsInsecureCameraIntent() { + assertTrue(CameraIntents.isInsecureCameraIntent(VALID_INSECURE_INTENT)) + // an intent with a specific package is still a valid secure camera intent + assertTrue(CameraIntents.isInsecureCameraIntent( + Intent(VALID_INSECURE_INTENT).setPackage("com.example"))) + assertFalse(CameraIntents.isInsecureCameraIntent(null)) + assertFalse(CameraIntents.isInsecureCameraIntent(Intent())) + assertFalse(CameraIntents.isInsecureCameraIntent(Intent(Intent.ACTION_MAIN))) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java new file mode 100644 index 000000000000..80716f9398f3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -0,0 +1,289 @@ +/* + * 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.globalactions; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.IActivityManager; +import android.app.admin.DevicePolicyManager; +import android.app.trust.TrustManager; +import android.content.res.Resources; +import android.graphics.Color; +import android.media.AudioManager; +import android.os.Handler; +import android.os.UserManager; +import android.service.dreams.IDreamManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.IWindowManager; +import android.view.WindowManagerPolicyConstants; + +import androidx.test.filters.SmallTest; + +import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.model.SysUiState; +import com.android.systemui.plugins.GlobalActions; +import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.telephony.TelephonyListenerManager; +import com.android.systemui.util.RingerModeLiveData; +import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.settings.SecureSettings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class GlobalActionsDialogLiteTest extends SysuiTestCase { + private GlobalActionsDialogLite mGlobalActionsDialogLite; + + @Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs; + @Mock private AudioManager mAudioManager; + @Mock private IDreamManager mDreamManager; + @Mock private DevicePolicyManager mDevicePolicyManager; + @Mock private LockPatternUtils mLockPatternUtils; + @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock private TelephonyListenerManager mTelephonyListenerManager; + @Mock private GlobalSettings mGlobalSettings; + @Mock private SecureSettings mSecureSettings; + @Mock private Resources mResources; + @Mock private ConfigurationController mConfigurationController; + @Mock private KeyguardStateController mKeyguardStateController; + @Mock private UserManager mUserManager; + @Mock private TrustManager mTrustManager; + @Mock private IActivityManager mActivityManager; + @Mock private MetricsLogger mMetricsLogger; + @Mock private NotificationShadeDepthController mDepthController; + @Mock private SysuiColorExtractor mColorExtractor; + @Mock private IStatusBarService mStatusBarService; + @Mock private NotificationShadeWindowController mNotificationShadeWindowController; + @Mock private IWindowManager mWindowManager; + @Mock private Executor mBackgroundExecutor; + @Mock private UiEventLogger mUiEventLogger; + @Mock private RingerModeTracker mRingerModeTracker; + @Mock private RingerModeLiveData mRingerModeLiveData; + @Mock private SysUiState mSysUiState; + @Mock private Handler mHandler; + @Mock private UserContextProvider mUserContextProvider; + + private TestableLooper mTestableLooper; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTestableLooper = TestableLooper.get(this); + allowTestableLooperAsMainThread(); + + when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); + when(mUserContextProvider.getUserContext()).thenReturn(mContext); + + mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext, + mWindowManagerFuncs, + mAudioManager, + mDreamManager, + mDevicePolicyManager, + mLockPatternUtils, + mBroadcastDispatcher, + mTelephonyListenerManager, + mGlobalSettings, + mSecureSettings, + null, + mResources, + mConfigurationController, + mKeyguardStateController, + mUserManager, + mTrustManager, + mActivityManager, + null, + mMetricsLogger, + mDepthController, + mColorExtractor, + mStatusBarService, + mNotificationShadeWindowController, + mWindowManager, + mBackgroundExecutor, + mUiEventLogger, + mRingerModeTracker, + mSysUiState, + mHandler + ); + mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); + + ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors(); + backdropColors.setMainColor(Color.BLACK); + when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors); + when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + } + + @Test + public void testShouldLogShow() { + mGlobalActionsDialogLite.onShow(null); + mTestableLooper.processAllMessages(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN); + } + + @Test + public void testShouldLogDismiss() { + mGlobalActionsDialogLite.onDismiss(mGlobalActionsDialogLite.mDialog); + mTestableLooper.processAllMessages(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_CLOSE); + } + + @Test + public void testShouldLogBugreportPress() throws InterruptedException { + GlobalActionsDialog.BugReportAction bugReportAction = + mGlobalActionsDialogLite.makeBugReportActionForTesting(); + bugReportAction.onPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_PRESS); + } + + @Test + public void testShouldLogBugreportLongPress() { + GlobalActionsDialog.BugReportAction bugReportAction = + mGlobalActionsDialogLite.makeBugReportActionForTesting(); + bugReportAction.onLongPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS); + } + + @Test + public void testShouldLogEmergencyDialerPress() { + GlobalActionsDialog.EmergencyDialerAction emergencyDialerAction = + mGlobalActionsDialogLite.makeEmergencyDialerActionForTesting(); + emergencyDialerAction.onPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS); + } + + @Test + public void testShouldLogScreenshotPress() { + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialogLite.makeScreenshotActionForTesting(); + screenshotAction.onPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_PRESS); + } + + @Test + public void testShouldShowScreenshot() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.integer.config_navBarInteractionMode, + WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON); + + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialogLite.makeScreenshotActionForTesting(); + assertThat(screenshotAction.shouldShow()).isTrue(); + } + + @Test + public void testShouldNotShowScreenshot() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.integer.config_navBarInteractionMode, + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON); + + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialogLite.makeScreenshotActionForTesting(); + assertThat(screenshotAction.shouldShow()).isFalse(); + } + + private void verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent event) { + mTestableLooper.processAllMessages(); + verify(mUiEventLogger, times(1)) + .log(event); + } + + @SafeVarargs + private static <T> void assertItemsOfType(List<T> stuff, Class<? extends T>... classes) { + assertThat(stuff).hasSize(classes.length); + for (int i = 0; i < stuff.size(); i++) { + assertThat(stuff.get(i)).isInstanceOf(classes[i]); + } + } + + @Test + public void testCreateActionItems_lockdownEnabled_doesShowLockdown() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + mGlobalActionsDialogLite.createActionItems(); + + assertItemsOfType(mGlobalActionsDialogLite.mItems, + GlobalActionsDialog.EmergencyAction.class, + GlobalActionsDialog.LockDownAction.class, + GlobalActionsDialog.ShutDownAction.class, + GlobalActionsDialog.RestartAction.class); + assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty(); + assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty(); + } + + @Test + public void testCreateActionItems_lockdownDisabled_doesNotShowLockdown() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + // make sure lockdown action will NOT be shown + doReturn(false).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + // lockdown action not allowed + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + mGlobalActionsDialogLite.createActionItems(); + + assertItemsOfType(mGlobalActionsDialogLite.mItems, + GlobalActionsDialog.EmergencyAction.class, + GlobalActionsDialog.ShutDownAction.class, + GlobalActionsDialog.RestartAction.class); + assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty(); + assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index 8add93032caa..93769c4600de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -33,12 +33,10 @@ import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; -import android.content.ContentResolver; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Color; import android.media.AudioManager; -import android.net.ConnectivityManager; import android.os.Handler; import android.os.RemoteException; import android.os.UserManager; @@ -77,6 +75,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; import org.junit.Before; @@ -105,9 +104,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock private DevicePolicyManager mDevicePolicyManager; @Mock private LockPatternUtils mLockPatternUtils; @Mock private BroadcastDispatcher mBroadcastDispatcher; - @Mock private ConnectivityManager mConnectivityManager; @Mock private TelephonyListenerManager mTelephonyListenerManager; - @Mock private ContentResolver mContentResolver; + @Mock private GlobalSettings mGlobalSettings; @Mock private Resources mResources; @Mock private ConfigurationController mConfigurationController; @Mock private ActivityStarter mActivityStarter; @@ -166,9 +164,9 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mDevicePolicyManager, mLockPatternUtils, mBroadcastDispatcher, - mConnectivityManager, mTelephonyListenerManager, - mContentResolver, + mGlobalSettings, + mSecureSettings, null, mResources, mConfigurationController, @@ -520,7 +518,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); - GlobalActionsDialog.ActionsDialog dialog = mGlobalActionsDialog.mDialog; + GlobalActionsDialog.ActionsDialog dialog = + (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog; assertThat(dialog).isNotNull(); assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.VISIBLE); @@ -543,7 +542,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); - GlobalActionsDialog.ActionsDialog dialog = mGlobalActionsDialog.mDialog; + GlobalActionsDialog.ActionsDialog dialog = + (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog; assertThat(dialog).isNotNull(); assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE); @@ -568,7 +568,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); - GlobalActionsDialog.ActionsDialog dialog = mGlobalActionsDialog.mDialog; + GlobalActionsDialog.ActionsDialog dialog = + (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog; assertThat(dialog).isNotNull(); assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java index fa29fd4f94ae..853684ab8dfc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java @@ -24,6 +24,7 @@ import static android.view.KeyEvent.KEYCODE_0; import static android.view.KeyEvent.KEYCODE_APP_SWITCH; import static android.view.KeyEvent.KEYCODE_BACK; import static android.view.KeyEvent.KEYCODE_HOME; +import static android.view.KeyEvent.KEYCODE_UNKNOWN; import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS; import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP; @@ -88,7 +89,7 @@ public class KeyButtonViewTest extends SysuiTestCase { } @Test - public void testLogOverviewPress() { + public void testLogOverviewPress() { checkmetrics(KEYCODE_APP_SWITCH, ACTION_UP, 0, NAVBAR_OVERVIEW_BUTTON_TAP); } @@ -134,6 +135,22 @@ public class KeyButtonViewTest extends SysuiTestCase { checkmetrics(KEYCODE_0, ACTION_UP, 0, null); } + @Test + public void testEventInjectedOnAbortGesture() { + mKeyButtonView.setCode(KEYCODE_HOME); + mKeyButtonView.abortCurrentGesture(); + verify(mInputManager, times(1)) + .injectInputEvent(any(KeyEvent.class), any(Integer.class)); + } + + @Test + public void testNoEventInjectedOnAbortUnknownGesture() { + mKeyButtonView.setCode(KEYCODE_UNKNOWN); + mKeyButtonView.abortCurrentGesture(); + verify(mInputManager, never()) + .injectInputEvent(any(KeyEvent.class), any(Integer.class)); + } + private void checkmetrics(int code, int action, int flag, KeyButtonView.NavBarButtonEvent expected) { mKeyButtonView.setCode(code); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java index d40e6a4586b8..650ee50f13af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java @@ -38,6 +38,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.globalactions.GlobalActionsDialogLite; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.MultiUserSwitch; @@ -91,6 +92,8 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { private MultiUserSwitch mMultiUserSwitch; @Mock private View mPowerMenuLiteView; + @Mock + private GlobalActionsDialogLite mGlobalActionsDialog; private QSFooterViewController mController; @@ -118,7 +121,7 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { mController = new QSFooterViewController(mView, mUserManager, mUserInfoController, mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController, new QSDetailDisplayer(), mQuickQSPanelController, mFakeTunerService, - mMetricsLogger, false); + mMetricsLogger, false, mGlobalActionsDialog); mController.init(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java index 278456859735..f2e7081e096b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java @@ -28,8 +28,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.Notification; -import android.os.Bundle; +import android.graphics.Color; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; @@ -120,7 +119,8 @@ public class AppOpsCoordinatorTest extends SysuiTestCase { mEntryBuilder .setFlag(mContext, FLAG_FOREGROUND_SERVICE, true) .setImportance(IMPORTANCE_DEFAULT) - .modifyNotification(mContext).setColorized(true); + .modifyNotification(mContext) + .setColorized(true).setColor(Color.WHITE); // THEN the entry is in the fgs section assertTrue(mFgsSection.isInSection(mEntryBuilder.build())); @@ -132,7 +132,8 @@ public class AppOpsCoordinatorTest extends SysuiTestCase { mEntryBuilder .setFlag(mContext, FLAG_FOREGROUND_SERVICE, true) .setImportance(IMPORTANCE_MIN) - .modifyNotification(mContext).setColorized(true); + .modifyNotification(mContext) + .setColorized(true).setColor(Color.WHITE); // THEN the entry is NOT in the fgs section assertFalse(mFgsSection.isInSection(mEntryBuilder.build())); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 18481bca1aa6..bfce2a568c78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -51,6 +51,7 @@ import android.content.Intent; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; +import android.graphics.Color; import android.os.Binder; import android.os.Handler; import android.provider.Settings; @@ -486,7 +487,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setContentTitle("foo") - .setColorized(true) + .setColorized(true).setColor(Color.RED) .setFlag(Notification.FLAG_CAN_COLORIZE, true) .setSmallIcon(android.R.drawable.sym_def_app_icon); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java index d9e938948f5e..e5f2aa7a93c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java @@ -441,4 +441,12 @@ public class KeyguardBouncerTest extends SysuiTestCase { // mKeyguardViewController.init(), only updateResources above. verify(mKeyguardHostViewController).updateResources(); } + + @Test + public void testUpdateKeyguardPosition_delegatesToRootView() { + mBouncer.ensureView(); + mBouncer.updateKeyguardPosition(1.0f); + + verify(mKeyguardHostViewController).updateKeyguardPosition(1.0f); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 45b791715904..20261e060601 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -283,4 +283,11 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { verify(mBouncer).updateResources(); } + + @Test + public void updateKeyguardPosition_delegatesToBouncer() { + mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f); + + verify(mBouncer).updateKeyguardPosition(1.0f); + } } diff --git a/rs/java/Android.bp b/rs/java/Android.bp index 9f854f7d5cb7..1c2b575e7c8d 100644 --- a/rs/java/Android.bp +++ b/rs/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-rs-sources", srcs: ["**/*.java"], diff --git a/sax/java/Android.bp b/sax/java/Android.bp index 97751891559c..0ed69e4bfb8e 100644 --- a/sax/java/Android.bp +++ b/sax/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-sax-sources", srcs: ["**/*.java"], diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 878ebc5bbfbd..80408518d84e 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -53,10 +53,13 @@ import com.android.server.accessibility.AccessibilityManagerService; * is triggered.</li> * <li> 4. {@link #onTripleTapped} updates magnification switch UI depending on magnification * capabilities and magnification active state when triple-tap gesture is detected. </li> + * <li> 4. {@link #onRequestMagnificationSpec} updates magnification switch UI depending on + * magnification capabilities and magnification active state when new magnification spec is + * changed by external request from calling public APIs. </li> * </ol> * - * <b>Note</b> Updates magnification switch UI when magnification mode transition - * is done {@link DisableMagnificationCallback#onResult}. + * <b>Note</b> Updates magnification switch UI when magnification mode transition + * is done and before invoking {@link TransitionCallBack#onResult}. */ public class MagnificationController implements WindowMagnificationManager.Callback, MagnificationGestureHandler.Callback, @@ -205,21 +208,22 @@ public class MagnificationController implements WindowMagnificationManager.Callb @Override public void onRequestMagnificationSpec(int displayId, int serviceId) { + final WindowMagnificationManager windowMagnificationManager; synchronized (mLock) { if (serviceId == AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID) { return; } - if (mWindowMagnificationMgr == null - || !mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId)) { - return; - } + updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + windowMagnificationManager = mWindowMagnificationMgr; + } + if (windowMagnificationManager != null) { mWindowMagnificationMgr.disableWindowMagnification(displayId, false); } } // TODO : supporting multi-display (b/182227245). @Override - public void onWindowMagnificationActivationState(boolean activated) { + public void onWindowMagnificationActivationState(int displayId, boolean activated) { if (activated) { mWindowModeEnabledTime = SystemClock.uptimeMillis(); @@ -227,6 +231,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; } logMagnificationModeWithImeOnIfNeeded(); + disableFullScreenMagnificationIfNeeded(displayId); } else { logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, SystemClock.uptimeMillis() - mWindowModeEnabledTime); @@ -237,6 +242,17 @@ public class MagnificationController implements WindowMagnificationManager.Callb } } + private void disableFullScreenMagnificationIfNeeded(int displayId) { + final FullScreenMagnificationController fullScreenMagnificationController = + getFullScreenMagnificationController(); + // Internal request may be for transition, so we just need to check external request. + final boolean isMagnifyByExternalRequest = + fullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) > 0; + if (isMagnifyByExternalRequest) { + fullScreenMagnificationController.reset(displayId, false); + } + } + @Override public void onFullScreenMagnificationActivationState(boolean activated) { if (activated) { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index ded601e70ff1..86f61eef2e7a 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -97,9 +97,10 @@ public class WindowMagnificationManager implements /** * Called when the state of the magnification activation is changed. * + * @param displayId The logical display id. * @param activated {@code true} if the magnification is activated, otherwise {@code false}. */ - void onWindowMagnificationActivationState(boolean activated); + void onWindowMagnificationActivationState(int displayId, boolean activated); } private final Callback mCallback; @@ -285,7 +286,7 @@ public class WindowMagnificationManager implements } if (enabled) { - mCallback.onWindowMagnificationActivationState(true); + mCallback.onWindowMagnificationActivationState(displayId, true); } } @@ -321,7 +322,7 @@ public class WindowMagnificationManager implements } if (disabled) { - mCallback.onWindowMagnificationActivationState(false); + mCallback.onWindowMagnificationActivationState(displayId, false); } } diff --git a/services/core/Android.bp b/services/core/Android.bp index 1e3f12a8edab..7fc7933529e2 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -225,7 +225,6 @@ filegroup { "java/com/android/server/NetIdManager.java", "java/com/android/server/TestNetworkService.java", "java/com/android/server/connectivity/AutodestructReference.java", - "java/com/android/server/connectivity/ConnectivityConstants.java", "java/com/android/server/connectivity/DnsManager.java", "java/com/android/server/connectivity/FullScore.java", "java/com/android/server/connectivity/KeepaliveTracker.java", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 6708fc380e08..57a93fa69a03 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -5543,12 +5543,7 @@ public class ConnectivityService extends IConnectivityManager.Stub incrementRequestCountOrThrow(this); mCallbackFlags = callbackFlags; mCallingAttributionTag = callingAttributionTag; - - try { - mBinder.linkToDeath(this, 0); - } catch (RemoteException e) { - binderDied(); - } + linkDeathRecipient(); } NetworkRequestInfo(@NonNull final NetworkRequestInfo nri, @@ -5586,6 +5581,7 @@ public class ConnectivityService extends IConnectivityManager.Stub incrementRequestCountOrThrow(this); mCallbackFlags = nri.mCallbackFlags; mCallingAttributionTag = nri.mCallingAttributionTag; + linkDeathRecipient(); } NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r) { @@ -5614,8 +5610,18 @@ public class ConnectivityService extends IConnectivityManager.Stub return Collections.unmodifiableList(tempRequests); } + void linkDeathRecipient() { + if (null != mBinder) { + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + } + } + } + void unlinkDeathRecipient() { - if (mBinder != null) { + if (null != mBinder) { mBinder.unlinkToDeath(this, 0); } } @@ -7742,7 +7748,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkAgentInfo bestNetwork = null; NetworkRequest bestRequest = null; for (final NetworkRequest req : nri.mRequests) { - bestNetwork = mNetworkRanker.getBestNetwork(req, nais); + bestNetwork = mNetworkRanker.getBestNetwork(req, nais, nri.getSatisfier()); // Stop evaluating as the highest possible priority request is satisfied. if (null != bestNetwork) { bestRequest = req; @@ -7995,7 +8001,6 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull final NetworkOffer offer, @NonNull final NetworkRanker networkRanker) { final NetworkRequest activeRequest = nri.isBeingSatisfied() ? nri.getActiveRequest() : null; final NetworkAgentInfo satisfier = null != activeRequest ? nri.getSatisfier() : null; - final FullScore satisfierScore = null != satisfier ? satisfier.getScore() : null; // Multi-layer requests have a currently active request, the one being satisfied. // Since the system will try to bring up a better network than is currently satisfying @@ -8034,7 +8039,7 @@ public class ConnectivityService extends IConnectivityManager.Stub && satisfier.factorySerialNumber == offer.providerId; final boolean newNeeded = (currentlyServing || (activeRequest.canBeSatisfiedBy(offer.caps) - && networkRanker.mightBeat(activeRequest, satisfierScore, offer))); + && networkRanker.mightBeat(activeRequest, satisfier, offer))); if (newNeeded != oldNeeded) { if (newNeeded) { offer.onNetworkNeeded(activeRequest); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b1543ee86565..d513ba418663 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -24,6 +24,8 @@ import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST; import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD; +import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN; +import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN; import static android.os.PowerWhitelistManager.REASON_ACTIVITY_STARTER; import static android.os.PowerWhitelistManager.REASON_ALLOWLISTED_PACKAGE; import static android.os.PowerWhitelistManager.REASON_BACKGROUND_ACTIVITY_PERMISSION; @@ -5865,6 +5867,17 @@ public final class ActiveServices { } } + if (ret == REASON_DENIED) { + final AppOpsManager appOpsManager = mAm.getAppOpsManager(); + if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, callingUid, + callingPackage) == AppOpsManager.MODE_ALLOWED) { + ret = REASON_OP_ACTIVATE_VPN; + } else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, + callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) { + ret = REASON_OP_ACTIVATE_PLATFORM_VPN; + } + } + return ret; } diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index e16a6cc6fefc..232005b7559b 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -89,6 +89,10 @@ final class DiscreteRegistry { private static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis"; private static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION = "discrete_history_quantization_millis"; + private static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags"; + private static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist"; + private static final String DEFAULT_DISCRETE_OPS = OP_CAMERA + "," + OP_RECORD_AUDIO + "," + + OP_FINE_LOCATION + "," + OP_COARSE_LOCATION; private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis(); private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis(); private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = @@ -96,6 +100,9 @@ final class DiscreteRegistry { private static long sDiscreteHistoryCutoff; private static long sDiscreteHistoryQuantization; + private static int[] sDiscreteOps; + private static int sDiscreteFlags; + private static final String TAG_HISTORY = "h"; private static final String ATTR_VERSION = "v"; @@ -155,6 +162,10 @@ final class DiscreteRegistry { PROPERTY_DISCRETE_HISTORY_CUTOFF, DEFAULT_DISCRETE_HISTORY_CUTOFF); sDiscreteHistoryQuantization = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_DISCRETE_HISTORY_QUANTIZATION, DEFAULT_DISCRETE_HISTORY_QUANTIZATION); + sDiscreteFlags = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PRIVACY, + PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE); + sDiscreteOps = parseOpsList(DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY, + PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)); } private void setDiscreteHistoryParameters(DeviceConfig.Properties p) { @@ -174,6 +185,13 @@ final class DiscreteRegistry { sDiscreteHistoryQuantization); } } + if (p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS)) { + sDiscreteFlags = p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE); + } + if (p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST)) { + sDiscreteOps = parseOpsList(p.getString(PROPERTY_DISCRETE_OPS_LIST, + DEFAULT_DISCRETE_OPS)); + } } private void createDiscreteAccessDir() { @@ -323,28 +341,13 @@ final class DiscreteRegistry { } public static boolean isDiscreteOp(int op, int uid, @AppOpsManager.OpFlags int flags) { - if (!isDiscreteOp(op)) { + if (!ArrayUtils.contains(sDiscreteOps, op)) { return false; } - if (!isDiscreteUid(uid)) { - return false; - } - if ((flags & (OP_FLAGS_DISCRETE)) == 0) { - return false; - } - return true; - } - - static boolean isDiscreteOp(int op) { - if (op != OP_CAMERA && op != OP_RECORD_AUDIO && op != OP_FINE_LOCATION - && op != OP_COARSE_LOCATION) { + if (uid < Process.FIRST_APPLICATION_UID) { return false; } - return true; - } - - static boolean isDiscreteUid(int uid) { - if (uid < Process.FIRST_APPLICATION_UID) { + if ((flags & (sDiscreteFlags)) == 0) { return false; } return true; @@ -876,6 +879,26 @@ final class DiscreteRegistry { } } + private static int[] parseOpsList(String opsList) { + String[] strArr; + if (opsList.isEmpty()) { + strArr = new String[0]; + } else { + strArr = opsList.split(","); + } + int nOps = strArr.length; + int[] result = new int[nOps]; + try { + for (int i = 0; i < nOps; i++) { + result[i] = Integer.parseInt(strArr[i]); + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage()); + return parseOpsList(DEFAULT_DISCRETE_OPS); + } + return result; + } + private static List<DiscreteOpEvent> stableListMerge(List<DiscreteOpEvent> a, List<DiscreteOpEvent> b) { int nA = a.size(); diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 75aa6c97d323..2bc81cb56996 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -234,7 +234,6 @@ public class ClipboardService extends SystemService { private final AppOpsManager mAppOps; private final ContentCaptureManagerInternal mContentCaptureInternal; private final AutofillManagerInternal mAutofillInternal; - private final TextClassificationManager mTextClassificationManager; private final IBinder mPermissionOwner; private final HostClipboardMonitor mHostClipboardMonitor; private final Handler mWorkerHandler; @@ -265,8 +264,6 @@ public class ClipboardService extends SystemService { mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mContentCaptureInternal = LocalServices.getService(ContentCaptureManagerInternal.class); mAutofillInternal = LocalServices.getService(AutofillManagerInternal.class); - mTextClassificationManager = (TextClassificationManager) - getContext().getSystemService(Context.TEXT_CLASSIFICATION_SERVICE); final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard"); mPermissionOwner = permOwner; if (IS_EMULATOR) { @@ -659,12 +656,12 @@ public class ClipboardService extends SystemService { } } + final int userId = UserHandle.getUserId(uid); if (clip != null) { - startClassificationLocked(clip); + startClassificationLocked(clip, userId); } // Update this user - final int userId = UserHandle.getUserId(uid); setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage); // Update related users @@ -763,14 +760,15 @@ public class ClipboardService extends SystemService { } @GuardedBy("mLock") - private void startClassificationLocked(@NonNull ClipData clip) { + private void startClassificationLocked(@NonNull ClipData clip, @UserIdInt int userId) { TextClassifier classifier; final long ident = Binder.clearCallingIdentity(); try { - classifier = mTextClassificationManager.createTextClassificationSession( - new TextClassificationContext.Builder( - getContext().getPackageName(), - TextClassifier.WIDGET_TYPE_CLIPBOARD + classifier = createTextClassificationManagerAsUser(userId) + .createTextClassificationSession( + new TextClassificationContext.Builder( + getContext().getPackageName(), + TextClassifier.WIDGET_TYPE_CLIPBOARD ).build() ); } finally { @@ -1125,4 +1123,8 @@ public class ClipboardService extends SystemService { && item.getIntent() == null; } + private TextClassificationManager createTextClassificationManagerAsUser(@UserIdInt int userId) { + Context context = getContext().createContextAsUser(UserHandle.of(userId), /* flags= */ 0); + return context.getSystemService(TextClassificationManager.class); + } } diff --git a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java deleted file mode 100644 index 325a2cd7bd69..000000000000 --- a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.connectivity; - -/** - * A class encapsulating various constants used by Connectivity. - * TODO : remove this class. - * @hide - */ -public class ConnectivityConstants { - // VPNs typically have priority over other networks. Give them a score that will - // let them win every single time. - public static final int VPN_DEFAULT_SCORE = 101; -} diff --git a/services/core/java/com/android/server/connectivity/FullScore.java b/services/core/java/com/android/server/connectivity/FullScore.java index 117253f9c75d..a8a83fc00491 100644 --- a/services/core/java/com/android/server/connectivity/FullScore.java +++ b/services/core/java/com/android/server/connectivity/FullScore.java @@ -86,9 +86,14 @@ public class FullScore { /** @hide */ public static final int POLICY_IS_UNMETERED = 59; + // This network is invincible. This is useful for offers until there is an API to listen + // to requests. + /** @hide */ + public static final int POLICY_IS_INVINCIBLE = 58; + // To help iterate when printing @VisibleForTesting - static final int MIN_CS_MANAGED_POLICY = POLICY_IS_UNMETERED; + static final int MIN_CS_MANAGED_POLICY = POLICY_IS_INVINCIBLE; @VisibleForTesting static final int MAX_CS_MANAGED_POLICY = POLICY_IS_VALIDATED; @@ -109,6 +114,7 @@ public class FullScore { case POLICY_YIELD_TO_BAD_WIFI: return "YIELD_TO_BAD_WIFI"; case POLICY_TRANSPORT_PRIMARY: return "TRANSPORT_PRIMARY"; case POLICY_EXITING: return "EXITING"; + case POLICY_IS_INVINCIBLE: return "INVINCIBLE"; } throw new IllegalArgumentException("Unknown policy : " + policy); } @@ -147,7 +153,8 @@ public class FullScore { caps.hasCapability(NET_CAPABILITY_NOT_METERED), config.explicitlySelected, config.acceptUnvalidated, - yieldToBadWiFi); + yieldToBadWiFi, + false /* invincible */); // only prospective scores can be invincible } /** @@ -178,8 +185,12 @@ public class FullScore { final boolean acceptUnvalidated = false; // Don't assume clinging to bad wifi final boolean yieldToBadWiFi = false; + // A prospective score is invincible if the legacy int in the filter is over the maximum + // score. + final boolean invincible = score.getLegacyInt() > NetworkRanker.LEGACY_INT_MAX; return withPolicies(score.getLegacyInt(), score.getPolicies(), KEEP_CONNECTED_NONE, - mayValidate, vpn, unmetered, everUserSelected, acceptUnvalidated, yieldToBadWiFi); + mayValidate, vpn, unmetered, everUserSelected, acceptUnvalidated, yieldToBadWiFi, + invincible); } /** @@ -193,14 +204,15 @@ public class FullScore { // telephony factory, so that it depends on the carrier. For now this is handled by // connectivity for backward compatibility. public FullScore mixInScore(@NonNull final NetworkCapabilities caps, - @NonNull final NetworkAgentConfig config, final boolean avoidBadWiFi) { + @NonNull final NetworkAgentConfig config, final boolean yieldToBadWifi) { return withPolicies(mLegacyInt, mPolicies, mKeepConnectedReason, caps.hasCapability(NET_CAPABILITY_VALIDATED), caps.hasTransport(TRANSPORT_VPN), caps.hasCapability(NET_CAPABILITY_NOT_METERED), config.explicitlySelected, config.acceptUnvalidated, - avoidBadWiFi); + yieldToBadWifi, + false /* invincible */); // only prospective scores can be invincible } // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the @@ -214,14 +226,16 @@ public class FullScore { final boolean isUnmetered, final boolean everUserSelected, final boolean acceptUnvalidated, - final boolean yieldToBadWiFi) { + final boolean yieldToBadWiFi, + final boolean invincible) { return new FullScore(legacyInt, (externalPolicies & EXTERNAL_POLICIES_MASK) | (isValidated ? 1L << POLICY_IS_VALIDATED : 0) | (isVpn ? 1L << POLICY_IS_VPN : 0) | (isUnmetered ? 1L << POLICY_IS_UNMETERED : 0) | (everUserSelected ? 1L << POLICY_EVER_USER_SELECTED : 0) | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0) - | (yieldToBadWiFi ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0), + | (yieldToBadWiFi ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0) + | (invincible ? 1L << POLICY_IS_INVINCIBLE : 0), keepConnectedReason); } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 3902573fa241..5d793fdda7b0 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -143,7 +143,7 @@ import java.util.TreeSet; // the network is no longer considered "lingering". After the linger timer expires, if the network // is satisfying one or more background NetworkRequests it is kept up in the background. If it is // not, ConnectivityService disconnects the NetworkAgent's AsyncChannel. -public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { +public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRanker.Scoreable { @NonNull public NetworkInfo networkInfo; // This Network object should always be used if possible, so as to encourage reuse of the @@ -890,7 +890,15 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { return isVPN(); } - public FullScore getScore() { + // Caller must not mutate. This method is called frequently and making a defensive copy + // would be too expensive. This is used by NetworkRanker.Scoreable, so it can be compared + // against other scoreables. + @Override public NetworkCapabilities getCaps() { + return networkCapabilities; + } + + // NetworkRanker.Scoreable + @Override public FullScore getScore() { return mScore; } diff --git a/services/core/java/com/android/server/connectivity/NetworkOffer.java b/services/core/java/com/android/server/connectivity/NetworkOffer.java index a0d392445472..5336593b4065 100644 --- a/services/core/java/com/android/server/connectivity/NetworkOffer.java +++ b/services/core/java/com/android/server/connectivity/NetworkOffer.java @@ -40,7 +40,7 @@ import java.util.Set; * * @hide */ -public class NetworkOffer { +public class NetworkOffer implements NetworkRanker.Scoreable { @NonNull public final FullScore score; @NonNull public final NetworkCapabilities caps; @NonNull public final INetworkOfferCallback callback; @@ -67,6 +67,20 @@ public class NetworkOffer { } /** + * Get the score filter of this offer + */ + @Override @NonNull public FullScore getScore() { + return score; + } + + /** + * Get the capabilities filter of this offer + */ + @Override @NonNull public NetworkCapabilities getCaps() { + return caps; + } + + /** * Tell the provider for this offer that the network is needed for a request. * @param request the request for which the offer is needed */ diff --git a/services/core/java/com/android/server/connectivity/NetworkRanker.java b/services/core/java/com/android/server/connectivity/NetworkRanker.java index 0e4dda22886e..3aaff59a7484 100644 --- a/services/core/java/com/android/server/connectivity/NetworkRanker.java +++ b/services/core/java/com/android/server/connectivity/NetworkRanker.java @@ -16,31 +16,233 @@ package com.android.server.connectivity; +import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkScore.POLICY_EXITING; +import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY; +import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI; + +import static com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED; +import static com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED; +import static com.android.server.connectivity.FullScore.POLICY_IS_INVINCIBLE; +import static com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED; +import static com.android.server.connectivity.FullScore.POLICY_IS_VPN; + import android.annotation.NonNull; import android.annotation.Nullable; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import com.android.net.module.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.List; +import java.util.function.Predicate; /** * A class that knows how to find the best network matching a request out of a list of networks. */ public class NetworkRanker { + // Historically the legacy ints have been 0~100 in principle (though the highest score in + // AOSP has always been 90). This is relied on by VPNs that send a legacy score of 101. + public static final int LEGACY_INT_MAX = 100; + + /** + * A class that can be scored against other scoreables. + */ + public interface Scoreable { + /** Get score of this scoreable */ + FullScore getScore(); + /** Get capabilities of this scoreable */ + NetworkCapabilities getCaps(); + } + + private static final boolean USE_POLICY_RANKING = false; + public NetworkRanker() { } + // TODO : move to module utils CollectionUtils. + @NonNull private static <T> ArrayList<T> filter(@NonNull final Collection<T> source, + @NonNull final Predicate<T> test) { + final ArrayList<T> matches = new ArrayList<>(); + for (final T e : source) { + if (test.test(e)) { + matches.add(e); + } + } + return matches; + } + /** * Find the best network satisfying this request among the list of passed networks. */ - // Almost equivalent to Collections.max(nais), but allows returning null if no network - // satisfies the request. @Nullable public NetworkAgentInfo getBestNetwork(@NonNull final NetworkRequest request, + @NonNull final Collection<NetworkAgentInfo> nais, + @Nullable final NetworkAgentInfo currentSatisfier) { + final ArrayList<NetworkAgentInfo> candidates = filter(nais, nai -> nai.satisfies(request)); + if (candidates.size() == 1) return candidates.get(0); // Only one potential satisfier + if (candidates.size() <= 0) return null; // No network can satisfy this request + if (USE_POLICY_RANKING) { + return getBestNetworkByPolicy(candidates, currentSatisfier); + } else { + return getBestNetworkByLegacyInt(candidates); + } + } + + // Transport preference order, if it comes down to that. + private static final int[] PREFERRED_TRANSPORTS_ORDER = { TRANSPORT_ETHERNET, TRANSPORT_WIFI, + TRANSPORT_BLUETOOTH, TRANSPORT_CELLULAR }; + + // Function used to partition a list into two working areas depending on whether they + // satisfy a predicate. All items satisfying the predicate will be put in |positive|, all + // items that don't will be put in |negative|. + // This is useful in this file because many of the ranking checks will retain only networks that + // satisfy a predicate if any of them do, but keep them all if all of them do. Having working + // areas is uncustomary in Java, but this function is called in a fairly intensive manner + // and doing allocation quite that often might affect performance quite badly. + private static <T> void partitionInto(@NonNull final List<T> source, @NonNull Predicate<T> test, + @NonNull final List<T> positive, @NonNull final List<T> negative) { + positive.clear(); + negative.clear(); + for (final T item : source) { + if (test.test(item)) { + positive.add(item); + } else { + negative.add(item); + } + } + } + + @Nullable private <T extends Scoreable> T getBestNetworkByPolicy( + @NonNull List<T> candidates, + @Nullable final T currentSatisfier) { + // Used as working areas. + final ArrayList<T> accepted = + new ArrayList<>(candidates.size() /* initialCapacity */); + final ArrayList<T> rejected = + new ArrayList<>(candidates.size() /* initialCapacity */); + + // The following tests will search for a network matching a given criterion. They all + // function the same way : if any network matches the criterion, drop from consideration + // all networks that don't. To achieve this, the tests below : + // 1. partition the list of remaining candidates into accepted and rejected networks. + // 2. if only one candidate remains, that's the winner : if accepted.size == 1 return [0] + // 3. if multiple remain, keep only the accepted networks and go on to the next criterion. + // Because the working areas will be wiped, a copy of the accepted networks needs to be + // made. + // 4. if none remain, the criterion did not help discriminate so keep them all. As an + // optimization, skip creating a new array and go on to the next criterion. + + // If a network is invincible, use it. + partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_INVINCIBLE), + accepted, rejected); + if (accepted.size() == 1) return accepted.get(0); + if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); + + // If there is a connected VPN, use it. + partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VPN), + accepted, rejected); + if (accepted.size() == 1) return accepted.get(0); + if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); + + // Selected & Accept-unvalidated policy : if any network has both of these, then don't + // choose one that doesn't. + partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_EVER_USER_SELECTED) + && nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED), + accepted, rejected); + if (accepted.size() == 1) return accepted.get(0); + if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); + + // Yield to bad wifi policy : if any wifi has ever been validated, keep only networks + // that don't yield to such a wifi network. + final boolean anyWiFiEverValidated = CollectionUtils.any(candidates, + nai -> nai.getScore().hasPolicy(POLICY_EVER_USER_SELECTED) + && nai.getCaps().hasTransport(TRANSPORT_WIFI)); + if (anyWiFiEverValidated) { + partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI), + accepted, rejected); + if (accepted.size() == 1) return accepted.get(0); + if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); + } + + // If any network is validated (or should be accepted even if it's not validated), then + // don't choose one that isn't. + partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VALIDATED) + || nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED), + accepted, rejected); + if (accepted.size() == 1) return accepted.get(0); + if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); + + // If any network is not exiting, don't choose one that is. + partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_EXITING), + accepted, rejected); + if (accepted.size() == 1) return accepted.get(0); + if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); + + // TODO : If any network is unmetered, don't choose a metered network. + // This can't be implemented immediately because prospective networks are always + // considered unmetered because factories don't know if the network will be metered. + // Saying an unmetered network always beats a metered one would mean that when metered wifi + // is connected, the offer for telephony would beat WiFi but the actual metered network + // would lose, so we'd have an infinite loop where telephony would continually bring up + // a network that is immediately torn down. + // Fix this by getting the agent to tell connectivity whether the network they will + // bring up is metered. Cell knows that in advance, while WiFi has a good estimate and + // can revise it if the network later turns out to be metered. + // partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_UNMETERED), + // accepted, rejected); + // if (accepted.size() == 1) return accepted.get(0); + // if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted); + + // If any network is for the default subscription, don't choose a network for another + // subscription with the same transport. + partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY), + accepted, rejected); + for (final Scoreable defaultSubNai : accepted) { + // Remove all networks without the DEFAULT_SUBSCRIPTION policy and the same transports + // as a network that has it. + final int[] transports = defaultSubNai.getCaps().getTransportTypes(); + candidates.removeIf(nai -> !nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY) + && Arrays.equals(transports, nai.getCaps().getTransportTypes())); + } + if (1 == candidates.size()) return candidates.get(0); + // It's guaranteed candidates.size() > 0 because there is at least one with DEFAULT_SUB + // policy and only those without it were removed. + + // If some of the networks have a better transport than others, keep only the ones with + // the best transports. + for (final int transport : PREFERRED_TRANSPORTS_ORDER) { + partitionInto(candidates, nai -> nai.getCaps().hasTransport(transport), + accepted, rejected); + if (accepted.size() == 1) return accepted.get(0); + if (accepted.size() > 0 && rejected.size() > 0) { + candidates = new ArrayList<>(accepted); + break; + } + } + + // At this point there are still multiple networks passing all the tests above. If any + // of them is the previous satisfier, keep it. + if (candidates.contains(currentSatisfier)) return currentSatisfier; + + // If there are still multiple options at this point but none of them is any of the + // transports above, it doesn't matter which is returned. They are all the same. + return candidates.get(0); + } + + // TODO : switch to the policy implementation and remove + // Almost equivalent to Collections.max(nais), but allows returning null if no network + // satisfies the request. + private NetworkAgentInfo getBestNetworkByLegacyInt( @NonNull final Collection<NetworkAgentInfo> nais) { NetworkAgentInfo bestNetwork = null; int bestScore = Integer.MIN_VALUE; for (final NetworkAgentInfo nai : nais) { - if (!nai.satisfies(request)) continue; if (nai.getCurrentScore() > bestScore) { bestNetwork = nai; bestScore = nai.getCurrentScore(); @@ -63,19 +265,37 @@ public class NetworkRanker { * beat a current champion. * * @param request The request to evaluate against. - * @param championScore The currently best network for this request. + * @param champion The currently best network for this request. * @param offer The offer. * @return Whether the offer stands a chance to beat the champion. */ public boolean mightBeat(@NonNull final NetworkRequest request, - @Nullable final FullScore championScore, + @Nullable final NetworkAgentInfo champion, @NonNull final NetworkOffer offer) { // If this network can't even satisfy the request then it can't beat anything, not // even an absence of network. It can't satisfy it anyway. if (!request.canBeSatisfiedBy(offer.caps)) return false; // If there is no satisfying network, then this network can beat, because some network // is always better than no network. - if (null == championScore) return true; + if (null == champion) return true; + if (USE_POLICY_RANKING) { + // If there is no champion, the offer can always beat. + // Otherwise rank them. + final ArrayList<Scoreable> candidates = new ArrayList<>(); + candidates.add(champion); + candidates.add(offer); + return offer == getBestNetworkByPolicy(candidates, champion); + } else { + return mightBeatByLegacyInt(request, champion.getScore(), offer); + } + } + + /** + * Returns whether an offer might beat a champion according to the legacy int. + */ + public boolean mightBeatByLegacyInt(@NonNull final NetworkRequest request, + @Nullable final FullScore championScore, + @NonNull final NetworkOffer offer) { final int offerIntScore; if (offer.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { // If the offer might have Internet access, then it might validate. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 1a86dba6d838..1279025b1485 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4217,8 +4217,9 @@ public class PackageManagerService extends IPackageManager.Stub // aware/unaware components they want to see, so fall through and // give them what they want } else { + final UserManagerInternal umInternal = mInjector.getUserManagerInternal(); // Caller expressed no opinion, so match based on user state - if (mUserManager.isUserUnlockingOrUnlocked(userId)) { + if (umInternal.isUserUnlockingOrUnlocked(userId)) { flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; } else { flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE; @@ -8202,9 +8203,10 @@ public class PackageManagerService extends IPackageManager.Stub // 11. Free storage service cache StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class); - // TODO(b/170481432): Decide what value of bytes needs to be sent instead of - // sending the bytes parameter of freeStorage - smInternal.freeCache(volumeUuid, bytes); + long freeBytesRequired = bytes - file.getUsableSpace(); + if (freeBytesRequired > 0) { + smInternal.freeCache(volumeUuid, freeBytesRequired); + } if (file.getUsableSpace() >= bytes) return; } else { try { @@ -15242,7 +15244,8 @@ public class PackageManagerService extends IPackageManager.Stub userId); // Deliver BOOT_COMPLETED only if user is unlocked - if (mUserManager.isUserUnlockingOrUnlocked(userId)) { + final UserManagerInternal umInternal = mInjector.getUserManagerInternal(); + if (umInternal.isUserUnlockingOrUnlocked(userId)) { Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED).setPackage(packageName); if (includeStopped) { bcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 142b36e8eaf3..7e3911ac9a6d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -3125,6 +3125,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (sourcePerms != null) { Permission bp = mRegistry.getPermission(newPerm); + if (bp == null) { + throw new IllegalStateException("Unknown new permission in split permission: " + + newPerm); + } if (bp.isRuntime()) { if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) { @@ -3140,6 +3144,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { sourcePermNum++) { final String sourcePerm = sourcePerms.valueAt(sourcePermNum); Permission sourceBp = mRegistry.getPermission(sourcePerm); + if (sourceBp == null) { + throw new IllegalStateException("Unknown source permission in split" + + " permission: " + sourcePerm); + } if (!sourceBp.isRuntime()) { inheritsFromInstallPerm = true; break; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index f931df8d7704..c7789eecc68f 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1092,7 +1092,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case LONG_PRESS_POWER_ASSISTANT: mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON, false, "Power - Long Press - Go To Assistant"); final int powerKeyDeviceId = Integer.MIN_VALUE; launchAssistAction(null, powerKeyDeviceId, eventTime); @@ -5073,6 +5073,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { pattern = mSafeModeEnabledVibePattern; break; + case HapticFeedbackConstants.ASSISTANT_BUTTON: + if (mVibrator.areAllPrimitivesSupported( + VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)) { + // quiet ramp, short pause, then sharp tick + return VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50) + .compose(); + } + // fallback for devices without composition support + return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); + default: return null; } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 91231c3032b4..a7e2d1dcceec 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -51,6 +51,9 @@ import static android.util.MathUtils.constrain; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC; +import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO; +import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__MANUAL; +import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; @@ -185,6 +188,8 @@ import com.android.server.stats.pull.netstats.NetworkStatsExt; import com.android.server.stats.pull.netstats.SubInfo; import com.android.server.storage.DiskStatsFileLogger; import com.android.server.storage.DiskStatsLoggingService; +import com.android.server.timezonedetector.MetricsTimeZoneDetectorState; +import com.android.server.timezonedetector.TimeZoneDetectorInternal; import libcore.io.IoUtils; @@ -410,6 +415,7 @@ public class StatsPullAtomService extends SystemService { private final Object mBuildInformationLock = new Object(); private final Object mRoleHolderLock = new Object(); private final Object mTimeZoneDataInfoLock = new Object(); + private final Object mTimeZoneDetectionInfoLock = new Object(); private final Object mExternalStorageInfoLock = new Object(); private final Object mAppsOnExternalStorageInfoLock = new Object(); private final Object mFaceSettingsLock = new Object(); @@ -644,6 +650,10 @@ public class StatsPullAtomService extends SystemService { synchronized (mTimeZoneDataInfoLock) { return pullTimeZoneDataInfoLocked(atomTag, data); } + case FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE: + synchronized (mTimeZoneDetectionInfoLock) { + return pullTimeZoneDetectorStateLocked(atomTag, data); + } case FrameworkStatsLog.EXTERNAL_STORAGE_INFO: synchronized (mExternalStorageInfoLock) { return pullExternalStorageInfoLocked(atomTag, data); @@ -849,6 +859,7 @@ public class StatsPullAtomService extends SystemService { registerBuildInformation(); registerRoleHolder(); registerTimeZoneDataInfo(); + registerTimeZoneDetectorState(); registerExternalStorageInfo(); registerAppsOnExternalStorageInfo(); registerFaceSettings(); @@ -3254,6 +3265,57 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } + private void registerTimeZoneDetectorState() { + int tagId = FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE; + mStatsManager.setPullAtomCallback( + tagId, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, + mStatsCallbackImpl + ); + } + + int pullTimeZoneDetectorStateLocked(int atomTag, List<StatsEvent> pulledData) { + final long token = Binder.clearCallingIdentity(); + try { + TimeZoneDetectorInternal timeZoneDetectorInternal = + LocalServices.getService(TimeZoneDetectorInternal.class); + MetricsTimeZoneDetectorState metricsState = + timeZoneDetectorInternal.generateMetricsState(); + pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, + metricsState.isTelephonyDetectionSupported(), + metricsState.isGeoDetectionSupported(), + metricsState.isUserLocationEnabled(), + metricsState.getAutoDetectionEnabledSetting(), + metricsState.getGeoDetectionEnabledSetting(), + convertToMetricsDetectionMode(metricsState.getDetectionMode()), + metricsState.getDeviceTimeZoneIdOrdinal(), + metricsState.getLatestManualSuggestionProtoBytes(), + metricsState.getLatestTelephonySuggestionProtoBytes(), + metricsState.getLatestGeolocationSuggestionProtoBytes() + )); + } catch (RuntimeException e) { + Slog.e(TAG, "Getting time zone detection state failed: ", e); + return StatsManager.PULL_SKIP; + } finally { + Binder.restoreCallingIdentity(token); + } + return StatsManager.PULL_SUCCESS; + } + + private int convertToMetricsDetectionMode(int detectionMode) { + switch (detectionMode) { + case MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL: + return TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__MANUAL; + case MetricsTimeZoneDetectorState.DETECTION_MODE_GEO: + return TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO; + case MetricsTimeZoneDetectorState.DETECTION_MODE_TELEPHONY: + return TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY; + default: + throw new IllegalArgumentException("" + detectionMode); + } + } + private void registerExternalStorageInfo() { int tagId = FrameworkStatsLog.EXTERNAL_STORAGE_INFO; mStatsManager.setPullAtomCallback( diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java index 8819b371b225..52ff48b6a277 100644 --- a/services/core/java/com/android/server/timedetector/ServerFlags.java +++ b/services/core/java/com/android/server/timedetector/ServerFlags.java @@ -64,7 +64,7 @@ public final class ServerFlags { @interface DeviceConfigKey {} /** - * Controls whether the location time zone manager service will started. Only observed if + * Controls whether the location time zone manager service will be started. Only observed if * the device build is configured to support location-based time zone detection. See * {@link ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupportedInConfig()} and {@link * ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupported()}. diff --git a/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java b/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java index dfff6f2dd5ae..e19ec8472e0a 100644 --- a/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java +++ b/services/core/java/com/android/server/timezonedetector/location/RealProviderMetricsLogger.java @@ -16,6 +16,21 @@ package com.android.server.timezonedetector.location; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__CERTAIN; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__DESTROYED; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__INITIALIZING; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__PERM_FAILED; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__STOPPED; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__UNCERTAIN; +import static com.android.internal.util.FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__UNKNOWN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN; + import android.annotation.IntRange; import com.android.internal.util.FrameworkStatsLog; @@ -37,6 +52,28 @@ public class RealProviderMetricsLogger implements ProviderMetricsLogger { @Override public void onProviderStateChanged(@ProviderStateEnum int stateEnum) { - // TODO(b/172934905): Implement once the atom has landed. + FrameworkStatsLog.write(FrameworkStatsLog.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED, + mProviderIndex, + metricsProviderState(stateEnum)); + } + + private static int metricsProviderState(@ProviderStateEnum int stateEnum) { + switch (stateEnum) { + case PROVIDER_STATE_STARTED_INITIALIZING: + return LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__INITIALIZING; + case PROVIDER_STATE_STARTED_UNCERTAIN: + return LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__UNCERTAIN; + case PROVIDER_STATE_STARTED_CERTAIN: + return LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__CERTAIN; + case PROVIDER_STATE_STOPPED: + return LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__STOPPED; + case PROVIDER_STATE_DESTROYED: + return LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__DESTROYED; + case PROVIDER_STATE_PERM_FAILED: + return LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__PERM_FAILED; + case PROVIDER_STATE_UNKNOWN: + default: + return LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED__STATE__UNKNOWN; + } } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 7644cf2fa592..580bd06194c8 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -274,7 +274,9 @@ class WallpaperController { void hideDeferredWallpapersIfNeededLegacy() { for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { final WallpaperWindowToken token = mWallpaperTokens.get(i); - token.commitVisibility(false); + if (!token.isVisibleRequested()) { + token.commitVisibility(false); + } } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 171e93fb4df9..95f3c3711136 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5035,7 +5035,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Cancel the existing exit animation for the next enter animation. if (isAnimating()) { cancelAnimation(); - destroySurfaceUnchecked(); } mAnimatingExit = false; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index 3dff36e8f379..81be2e7d8cea 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -64,6 +64,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; /** * Tests for MagnificationController. @@ -72,6 +73,7 @@ import org.mockito.MockitoAnnotations; public class MagnificationControllerTest { private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; + private static final int TEST_SERVICE_ID = 1; private static final Region MAGNIFICATION_REGION = new Region(0, 0, 500, 600); private static final float MAGNIFIED_CENTER_X = 100; private static final float MAGNIFIED_CENTER_Y = 200; @@ -96,6 +98,7 @@ public class MagnificationControllerTest { private WindowMagnificationManager mWindowMagnificationManager; private MockContentResolver mMockResolver; private MagnificationController mMagnificationController; + private FullScreenMagnificationControllerStubber mScreenMagnificationControllerStubber; @Before public void setUp() throws Exception { @@ -112,8 +115,11 @@ public class MagnificationControllerTest { mock(WindowMagnificationManager.Callback.class))); mMockConnection = new MockWindowMagnificationConnection(true); mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mScreenMagnificationControllerStubber = new FullScreenMagnificationControllerStubber( + mScreenMagnificationController); mMagnificationController = spy(new MagnificationController(mService, new Object(), mContext, mScreenMagnificationController, mWindowMagnificationManager)); + mMagnificationController.setMagnificationCapabilities( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); } @@ -240,9 +246,11 @@ public class MagnificationControllerTest { MODE_FULLSCREEN, mTransitionCallBack); - verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, - DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, - true, MAGNIFICATION_GESTURE_HANDLER_ID); + assertEquals(DEFAULT_SCALE, mScreenMagnificationController.getScale(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_X, mScreenMagnificationController.getCenterX(TEST_DISPLAY), + 0); + assertEquals(MAGNIFIED_CENTER_Y, mScreenMagnificationController.getCenterY(TEST_DISPLAY), + 0); verify(mTransitionCallBack).onResult(true); } @@ -286,13 +294,32 @@ public class MagnificationControllerTest { public void onMagnificationRequest_windowMagnifying_disableWindow() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); - mMagnificationController.onRequestMagnificationSpec(TEST_DISPLAY, 1); + mMagnificationController.onRequestMagnificationSpec(TEST_DISPLAY, TEST_SERVICE_ID); mMockConnection.invokeCallbacks(); assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); } @Test + public void magnifyThroughExternalRequest_showMagnificationButton() { + mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, + MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, false, TEST_SERVICE_ID); + mMagnificationController.onRequestMagnificationSpec(TEST_DISPLAY, TEST_SERVICE_ID); + + verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), + eq(MODE_FULLSCREEN)); + } + + @Test + public void setScaleOneThroughExternalRequest_removeMagnificationButton() { + mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, 1.0f, + MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, false, TEST_SERVICE_ID); + mMagnificationController.onRequestMagnificationSpec(TEST_DISPLAY, TEST_SERVICE_ID); + + verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY)); + } + + @Test public void onPerformScaleAction_magnifierEnabled_handleScaleChange() throws RemoteException { final float newScale = 4.0f; setMagnificationEnabled(MODE_WINDOW); @@ -305,15 +332,26 @@ public class MagnificationControllerTest { @Test public void onWindowMagnificationActivationState_windowActivated_logWindowDuration() { - mMagnificationController.onWindowMagnificationActivationState(true); + mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true); - mMagnificationController.onWindowMagnificationActivationState(false); + mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, false); verify(mMagnificationController).logMagnificationUsageState( eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong()); } @Test + public void onWinodwModeActivated_fullScreenIsActivatedByExternal_fullScreenIsDisabled() { + mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, + DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, + true, TEST_SERVICE_ID); + + mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true); + + assertFalse(mScreenMagnificationController.isMagnifying(TEST_DISPLAY)); + } + + @Test public void onFullScreenMagnificationActivationState_fullScreenActivated_logFullScreenDuration() { mMagnificationController.onFullScreenMagnificationActivationState(true); @@ -418,6 +456,18 @@ public class MagnificationControllerTest { } @Test + public void triggerShortcutToShowMagnificationBound_fullscreenMode_showMagnificationButton() { + setMagnificationModeSettings(MODE_FULLSCREEN); + + when(mScreenMagnificationController.isForceShowMagnifiableBounds(TEST_DISPLAY)).thenReturn( + true); + mMagnificationController.onShortcutTriggered(TEST_DISPLAY, MODE_FULLSCREEN); + + verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), + eq(MODE_FULLSCREEN)); + } + + @Test public void onShortcutTriggered_windowModeDisabled_removeMagnificationButton() throws RemoteException { @@ -488,7 +538,7 @@ public class MagnificationControllerTest { @Test public void imeWindowStateShown_windowMagnifying_logWindowMode() { - mMagnificationController.onWindowMagnificationActivationState(true); + mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true); mMagnificationController.onImeWindowVisibilityChanged(true); @@ -522,26 +572,19 @@ public class MagnificationControllerTest { @Test public void imeWindowStateHidden_fullScreenMagnifying_noLogAnyMode() { - mMagnificationController.onWindowMagnificationActivationState(true); + mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true); verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt()); } private void setMagnificationEnabled(int mode) throws RemoteException { - setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); } private void setMagnificationEnabled(int mode, float centerX, float centerY) throws RemoteException { setMagnificationModeSettings(mode); - Mockito.reset(mScreenMagnificationController); - doAnswer(invocation -> { - final Region outRegion = invocation.getArgument(1); - outRegion.set(MAGNIFICATION_REGION); - return null; - }).when(mScreenMagnificationController).getMagnificationRegion(anyInt(), any(Region.class)); - + mScreenMagnificationControllerStubber.resetAndStubMethods(); final boolean windowMagnifying = mWindowMagnificationManager.isWindowMagnifierEnabled( TEST_DISPLAY); if (windowMagnifying) { @@ -549,21 +592,9 @@ public class MagnificationControllerTest { mMockConnection.invokeCallbacks(); } if (mode == MODE_FULLSCREEN) { - when(mScreenMagnificationController.isMagnifying(TEST_DISPLAY)).thenReturn(true); - when(mScreenMagnificationController.isForceShowMagnifiableBounds( - TEST_DISPLAY)).thenReturn(true); - when(mScreenMagnificationController.getPersistedScale()).thenReturn(DEFAULT_SCALE); - when(mScreenMagnificationController.getScale(TEST_DISPLAY)).thenReturn(DEFAULT_SCALE); - when(mScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn( - centerX); - when(mScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn( - centerY); + mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, centerX, + centerY, true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); } else { - doAnswer(invocation -> { - when(mScreenMagnificationController.isMagnifying(TEST_DISPLAY)).thenReturn(true); - return null; - }).when(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), - eq(DEFAULT_SCALE), anyFloat(), anyFloat(), anyBoolean(), anyInt()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE, centerX, centerY, null); mMockConnection.invokeCallbacks(); @@ -574,4 +605,90 @@ public class MagnificationControllerTest { Settings.Secure.putIntForUser(mMockResolver, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, mode, CURRENT_USER_ID); } + + /** + * Stubs public methods to simulate the real beahviours. + */ + private static class FullScreenMagnificationControllerStubber { + private final FullScreenMagnificationController mScreenMagnificationController; + private boolean mIsMagnifying = false; + private float mScale = 1.0f; + private float mCenterX = 0; + private float mCenterY = 0; + private int mServiceId = -1; + + FullScreenMagnificationControllerStubber( + FullScreenMagnificationController screenMagnificationController) { + mScreenMagnificationController = screenMagnificationController; + resetCenter(); + stubMethods(); + } + + private void stubMethods() { + doAnswer(invocation -> mIsMagnifying).when(mScreenMagnificationController).isMagnifying( + TEST_DISPLAY); + doAnswer(invocation -> mIsMagnifying).when( + mScreenMagnificationController).isForceShowMagnifiableBounds(TEST_DISPLAY); + doAnswer(invocation -> mScale).when(mScreenMagnificationController).getPersistedScale(); + doAnswer(invocation -> mScale).when(mScreenMagnificationController).getScale( + TEST_DISPLAY); + doAnswer(invocation -> mCenterX).when(mScreenMagnificationController).getCenterX( + TEST_DISPLAY); + doAnswer(invocation -> mCenterY).when(mScreenMagnificationController).getCenterY( + TEST_DISPLAY); + doAnswer(invocation -> mServiceId).when( + mScreenMagnificationController).getIdOfLastServiceToMagnify(TEST_DISPLAY); + + doAnswer(invocation -> { + final Region outRegion = invocation.getArgument(1); + outRegion.set(MAGNIFICATION_REGION); + return null; + }).when(mScreenMagnificationController).getMagnificationRegion(anyInt(), + any(Region.class)); + + Answer setScaleAndCenterStubAnswer = invocation -> { + final float scale = invocation.getArgument(1); + mScale = Float.isNaN(scale) ? mScale : scale; + mIsMagnifying = mScale > 1.0f; + if (mIsMagnifying) { + mCenterX = invocation.getArgument(2); + mCenterY = invocation.getArgument(3); + mServiceId = invocation.getArgument(5); + } else { + mServiceId = -1; + resetCenter(); + } + return true; + }; + doAnswer(setScaleAndCenterStubAnswer).when( + mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), + anyFloat(), anyFloat(), anyFloat(), any(), anyInt()); + + doAnswer(setScaleAndCenterStubAnswer).when( + mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), + anyFloat(), anyFloat(), anyFloat(), anyBoolean(), anyInt()); + + Answer resetStubAnswer = invocation -> { + mScale = 1.0f; + mIsMagnifying = false; + mServiceId = -1; + resetCenter(); + return true; + }; + doAnswer(resetStubAnswer).when(mScreenMagnificationController).reset(eq(TEST_DISPLAY), + any()); + doAnswer(resetStubAnswer).when(mScreenMagnificationController).reset(eq(TEST_DISPLAY), + anyBoolean()); + } + + private void resetCenter() { + mCenterX = MAGNIFICATION_REGION.getBounds().exactCenterX(); + mCenterY = MAGNIFICATION_REGION.getBounds().exactCenterY(); + } + + public void resetAndStubMethods() { + Mockito.reset(mScreenMagnificationController); + stubMethods(); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index 955217c7d93f..ffa0185b4542 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -397,7 +397,7 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); - verify(mMockCallback).onWindowMagnificationActivationState(eq(true)); + verify(mMockCallback).onWindowMagnificationActivationState(TEST_DISPLAY, true); } @Test @@ -406,7 +406,7 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN); mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, true); - verify(mMockCallback).onWindowMagnificationActivationState(eq(false)); + verify(mMockCallback).onWindowMagnificationActivationState(TEST_DISPLAY, false); } private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java index a37d5c8a956a..9433bf28e237 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java @@ -31,6 +31,7 @@ import android.app.NotificationManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.graphics.Color; import android.os.Build; import android.os.UserHandle; import android.provider.Settings; @@ -193,7 +194,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { Notification n11 = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_MESSAGE) - .setColorized(true) + .setColorized(true).setColor(Color.WHITE) .build(); mRecordCheaterColorized = new NotificationRecord(mContext, new StatusBarNotification(pkg2, pkg2, 1, "cheater", uid2, uid2, n11, new UserHandle(userId), @@ -202,7 +203,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { Notification n12 = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_MESSAGE) - .setColorized(true) + .setColorized(true).setColor(Color.WHITE) .setStyle(new Notification.MediaStyle()) .build(); mNoMediaSessionMedia = new NotificationRecord(mContext, new StatusBarNotification( @@ -212,7 +213,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { Notification n13 = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) - .setColorized(true /* colorized */) + .setColorized(true).setColor(Color.WHITE) .build(); mRecordColorized = new NotificationRecord(mContext, new StatusBarNotification(pkg2, pkg2, 1, "colorized", uid2, uid2, n13, @@ -221,7 +222,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { Notification n14 = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_CALL) - .setColorized(true) + .setColorized(true).setColor(Color.WHITE) .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .build(); mRecordColorizedCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg, 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 b24e78828f86..9a63782a4079 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3284,7 +3284,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) .setContentTitle("foo") - .setColorized(true) + .setColorized(true).setColor(Color.WHITE) .setFlag(Notification.FLAG_CAN_COLORIZE, true) .setSmallIcon(android.R.drawable.sym_def_app_icon); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 16d83d133fbe..a3b5fc7ba872 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -470,7 +470,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { ModelData modelData = getKeyphraseModelDataLocked(keyphraseId); if (modelData == null || !modelData.isKeyphraseModel()) { - Slog.e(TAG, "No model exists for given keyphrase Id " + keyphraseId); + Slog.w(TAG, "No model exists for given keyphrase Id " + keyphraseId); return STATUS_ERROR; } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 7a53f1e159a9..eac21b492f10 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -56,6 +56,7 @@ import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; +import android.media.permission.ClearCallingIdentityContext; import android.media.permission.Identity; import android.media.permission.IdentityContext; import android.media.permission.PermissionUtil; @@ -141,7 +142,7 @@ public class SoundTriggerService extends SystemService { } if (stat.mIsStarted) { - Slog.e(TAG, "error onStart(): Model " + id + " already started"); + Slog.w(TAG, "error onStart(): Model " + id + " already started"); return; } @@ -153,12 +154,12 @@ public class SoundTriggerService extends SystemService { public synchronized void onStop(UUID id) { SoundModelStat stat = mModelStats.get(id); if (stat == null) { - Slog.e(TAG, "error onStop(): Model " + id + " has no stats available"); + Slog.w(TAG, "error onStop(): Model " + id + " has no stats available"); return; } if (!stat.mIsStarted) { - Slog.e(TAG, "error onStop(): Model " + id + " already stopped"); + Slog.w(TAG, "error onStop(): Model " + id + " already stopped"); return; } @@ -242,7 +243,7 @@ public class SoundTriggerService extends SystemService { @NonNull IBinder client) { try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect( originatorIdentity)) { - return new SoundTriggerSessionStub(newSoundTriggerHelper(), client); + return new SoundTriggerSessionStub(client); } } @@ -253,7 +254,7 @@ public class SoundTriggerService extends SystemService { try (SafeCloseable ignored = PermissionUtil.establishIdentityIndirect(mContext, SOUNDTRIGGER_DELEGATE_IDENTITY, middlemanIdentity, originatorIdentity)) { - return new SoundTriggerSessionStub(newSoundTriggerHelper(), client); + return new SoundTriggerSessionStub(client); } } } @@ -262,14 +263,15 @@ public class SoundTriggerService extends SystemService { private final SoundTriggerHelper mSoundTriggerHelper; // Used to detect client death. private final IBinder mClient; + private final Identity mOriginatorIdentity; private final TreeMap<UUID, SoundModel> mLoadedModels = new TreeMap<>(); private final Object mCallbacksLock = new Object(); private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks = new TreeMap<>(); - SoundTriggerSessionStub( - SoundTriggerHelper soundTriggerHelper, @NonNull IBinder client) { - mSoundTriggerHelper = soundTriggerHelper; + SoundTriggerSessionStub(@NonNull IBinder client) { + mSoundTriggerHelper = newSoundTriggerHelper(); mClient = client; + mOriginatorIdentity = IdentityContext.getNonNull(); try { mClient.linkToDeath(() -> { clientDied(); @@ -297,464 +299,496 @@ public class SoundTriggerService extends SystemService { @Override public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback, RecognitionConfig config, boolean runInBatterySaverMode) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (runInBatterySaverMode) { - enforceCallingPermission(Manifest.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER); - } - if (DEBUG) { - Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid); - } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (runInBatterySaverMode) { + enforceCallingPermission(Manifest.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : " - + parcelUuid)); + if (DEBUG) { + Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid); + } - GenericSoundModel model = getSoundModel(parcelUuid); - if (model == null) { - Slog.e(TAG, "Null model in database for id: " + parcelUuid); + sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : " + + parcelUuid)); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "startRecognition(): Null model in database for id: " + parcelUuid)); + GenericSoundModel model = getSoundModel(parcelUuid); + if (model == null) { + Slog.w(TAG, "Null model in database for id: " + parcelUuid); - return STATUS_ERROR; - } + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognition(): Null model in database for id: " + parcelUuid)); - int ret = mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model, - callback, config, runInBatterySaverMode); - if (ret == STATUS_OK) { - mSoundModelStatTracker.onStart(parcelUuid.getUuid()); + return STATUS_ERROR; + } + + int ret = mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model, + callback, config, runInBatterySaverMode); + if (ret == STATUS_OK) { + mSoundModelStatTracker.onStart(parcelUuid.getUuid()); + } + return ret; } - return ret; } @Override public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid); - } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (DEBUG) { + Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : " - + parcelUuid)); + sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : " + + parcelUuid)); - int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback); - if (ret == STATUS_OK) { - mSoundModelStatTracker.onStop(parcelUuid.getUuid()); + int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), + callback); + if (ret == STATUS_OK) { + mSoundModelStatTracker.onStop(parcelUuid.getUuid()); + } + return ret; } - return ret; } @Override public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.i(TAG, "getSoundModel(): id = " + soundModelId); - } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (DEBUG) { + Slog.i(TAG, "getSoundModel(): id = " + soundModelId); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = " - + soundModelId)); + sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = " + + soundModelId)); - SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel( - soundModelId.getUuid()); - return model; + SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel( + soundModelId.getUuid()); + return model; + } } @Override public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.i(TAG, "updateSoundModel(): model = " + soundModel); - } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (DEBUG) { + Slog.i(TAG, "updateSoundModel(): model = " + soundModel); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = " - + soundModel)); + sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = " + + soundModel)); - mDbHelper.updateGenericSoundModel(soundModel); + mDbHelper.updateGenericSoundModel(soundModel); + } } @Override public void deleteSoundModel(ParcelUuid soundModelId) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId); - } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (DEBUG) { + Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = " - + soundModelId)); + sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = " + + soundModelId)); - // Unload the model if it is loaded. - mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid()); + // Unload the model if it is loaded. + mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid()); - // Stop tracking recognition if it is started. - mSoundModelStatTracker.onStop(soundModelId.getUuid()); + // Stop tracking recognition if it is started. + mSoundModelStatTracker.onStop(soundModelId.getUuid()); - mDbHelper.deleteGenericSoundModel(soundModelId.getUuid()); + mDbHelper.deleteGenericSoundModel(soundModelId.getUuid()); + } } @Override public int loadGenericSoundModel(GenericSoundModel soundModel) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (soundModel == null || soundModel.getUuid() == null) { - Slog.e(TAG, "Invalid sound model"); + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (soundModel == null || soundModel.getUuid() == null) { + Slog.w(TAG, "Invalid sound model"); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "loadGenericSoundModel(): Invalid sound model")); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "loadGenericSoundModel(): Invalid sound model")); - return STATUS_ERROR; - } - if (DEBUG) { - Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.getUuid()); - } + return STATUS_ERROR; + } + if (DEBUG) { + Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.getUuid()); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = " - + soundModel.getUuid())); + sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = " + + soundModel.getUuid())); - synchronized (mLock) { - SoundModel oldModel = mLoadedModels.get(soundModel.getUuid()); - // If the model we're loading is actually different than what we had loaded, we - // should unload that other model now. We don't care about return codes since we - // don't know if the other model is loaded. - if (oldModel != null && !oldModel.equals(soundModel)) { - mSoundTriggerHelper.unloadGenericSoundModel(soundModel.getUuid()); - synchronized (mCallbacksLock) { - mCallbacks.remove(soundModel.getUuid()); + synchronized (mLock) { + SoundModel oldModel = mLoadedModels.get(soundModel.getUuid()); + // If the model we're loading is actually different than what we had loaded, we + // should unload that other model now. We don't care about return codes since we + // don't know if the other model is loaded. + if (oldModel != null && !oldModel.equals(soundModel)) { + mSoundTriggerHelper.unloadGenericSoundModel(soundModel.getUuid()); + synchronized (mCallbacksLock) { + mCallbacks.remove(soundModel.getUuid()); + } } + mLoadedModels.put(soundModel.getUuid(), soundModel); } - mLoadedModels.put(soundModel.getUuid(), soundModel); + return STATUS_OK; } - return STATUS_OK; } @Override public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (soundModel == null || soundModel.getUuid() == null) { - Slog.e(TAG, "Invalid sound model"); + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (soundModel == null || soundModel.getUuid() == null) { + Slog.w(TAG, "Invalid sound model"); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "loadKeyphraseSoundModel(): Invalid sound model")); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "loadKeyphraseSoundModel(): Invalid sound model")); - return STATUS_ERROR; - } - if (soundModel.getKeyphrases() == null || soundModel.getKeyphrases().length != 1) { - Slog.e(TAG, "Only one keyphrase per model is currently supported."); + return STATUS_ERROR; + } + if (soundModel.getKeyphrases() == null || soundModel.getKeyphrases().length != 1) { + Slog.w(TAG, "Only one keyphrase per model is currently supported."); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "loadKeyphraseSoundModel(): Only one keyphrase per model" - + " is currently supported.")); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "loadKeyphraseSoundModel(): Only one keyphrase per model" + + " is currently supported.")); - return STATUS_ERROR; - } - if (DEBUG) { - Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.getUuid()); - } + return STATUS_ERROR; + } + if (DEBUG) { + Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.getUuid()); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = " - + soundModel.getUuid())); + sEventLogger.log( + new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = " + + soundModel.getUuid())); - synchronized (mLock) { - SoundModel oldModel = mLoadedModels.get(soundModel.getUuid()); - // If the model we're loading is actually different than what we had loaded, we - // should unload that other model now. We don't care about return codes since we - // don't know if the other model is loaded. - if (oldModel != null && !oldModel.equals(soundModel)) { - mSoundTriggerHelper.unloadKeyphraseSoundModel( - soundModel.getKeyphrases()[0].getId()); - synchronized (mCallbacksLock) { - mCallbacks.remove(soundModel.getUuid()); + synchronized (mLock) { + SoundModel oldModel = mLoadedModels.get(soundModel.getUuid()); + // If the model we're loading is actually different than what we had loaded, we + // should unload that other model now. We don't care about return codes since we + // don't know if the other model is loaded. + if (oldModel != null && !oldModel.equals(soundModel)) { + mSoundTriggerHelper.unloadKeyphraseSoundModel( + soundModel.getKeyphrases()[0].getId()); + synchronized (mCallbacksLock) { + mCallbacks.remove(soundModel.getUuid()); + } } + mLoadedModels.put(soundModel.getUuid(), soundModel); } - mLoadedModels.put(soundModel.getUuid(), soundModel); + return STATUS_OK; } - return STATUS_OK; } @Override public int startRecognitionForService(ParcelUuid soundModelId, Bundle params, ComponentName detectionService, SoundTrigger.RecognitionConfig config) { - Objects.requireNonNull(soundModelId); - Objects.requireNonNull(detectionService); - Objects.requireNonNull(config); - - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + Objects.requireNonNull(soundModelId); + Objects.requireNonNull(detectionService); + Objects.requireNonNull(config); - enforceDetectionPermissions(detectionService); + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.i(TAG, "startRecognition(): id = " + soundModelId); - } - - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "startRecognitionForService(): id = " + soundModelId)); + enforceDetectionPermissions(detectionService); - IRecognitionStatusCallback callback = - new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params, - detectionService, Binder.getCallingUserHandle(), config); + if (DEBUG) { + Slog.i(TAG, "startRecognition(): id = " + soundModelId); + } - synchronized (mLock) { - SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); - if (soundModel == null) { - Slog.e(TAG, soundModelId + " is not loaded"); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognitionForService(): id = " + soundModelId)); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "startRecognitionForService():" + soundModelId + " is not loaded")); + IRecognitionStatusCallback callback = + new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params, + detectionService, Binder.getCallingUserHandle(), config); - return STATUS_ERROR; - } - IRecognitionStatusCallback existingCallback = null; - synchronized (mCallbacksLock) { - existingCallback = mCallbacks.get(soundModelId.getUuid()); - } - if (existingCallback != null) { - Slog.e(TAG, soundModelId + " is already running"); + synchronized (mLock) { + SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); + if (soundModel == null) { + Slog.w(TAG, soundModelId + " is not loaded"); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "startRecognitionForService():" - + soundModelId + " is already running")); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognitionForService():" + soundModelId + " is not loaded")); - return STATUS_ERROR; - } - int ret; - switch (soundModel.getType()) { - case SoundModel.TYPE_GENERIC_SOUND: - ret = mSoundTriggerHelper.startGenericRecognition(soundModel.getUuid(), - (GenericSoundModel) soundModel, callback, config, false); - break; - default: - Slog.e(TAG, "Unknown model type"); + return STATUS_ERROR; + } + IRecognitionStatusCallback existingCallback = null; + synchronized (mCallbacksLock) { + existingCallback = mCallbacks.get(soundModelId.getUuid()); + } + if (existingCallback != null) { + Slog.w(TAG, soundModelId + " is already running"); sEventLogger.log(new SoundTriggerLogger.StringEvent( - "startRecognitionForService(): Unknown model type")); + "startRecognitionForService():" + + soundModelId + " is already running")); return STATUS_ERROR; - } + } + int ret; + switch (soundModel.getType()) { + case SoundModel.TYPE_GENERIC_SOUND: + ret = mSoundTriggerHelper.startGenericRecognition(soundModel.getUuid(), + (GenericSoundModel) soundModel, callback, config, false); + break; + default: + Slog.e(TAG, "Unknown model type"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognitionForService(): Unknown model type")); + + return STATUS_ERROR; + } - if (ret != STATUS_OK) { - Slog.e(TAG, "Failed to start model: " + ret); + if (ret != STATUS_OK) { + Slog.e(TAG, "Failed to start model: " + ret); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "startRecognitionForService(): Failed to start model:")); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognitionForService(): Failed to start model:")); - return ret; - } - synchronized (mCallbacksLock) { - mCallbacks.put(soundModelId.getUuid(), callback); - } + return ret; + } + synchronized (mCallbacksLock) { + mCallbacks.put(soundModelId.getUuid(), callback); + } - mSoundModelStatTracker.onStart(soundModelId.getUuid()); + mSoundModelStatTracker.onStart(soundModelId.getUuid()); + } + return STATUS_OK; } - return STATUS_OK; } @Override public int stopRecognitionForService(ParcelUuid soundModelId) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.i(TAG, "stopRecognition(): id = " + soundModelId); - } - - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "stopRecognitionForService(): id = " + soundModelId)); - - synchronized (mLock) { - SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); - if (soundModel == null) { - Slog.e(TAG, soundModelId + " is not loaded"); + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (DEBUG) { + Slog.i(TAG, "stopRecognition(): id = " + soundModelId); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "stopRecognitionForService(): " + soundModelId - + " is not loaded")); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "stopRecognitionForService(): id = " + soundModelId)); - return STATUS_ERROR; - } - IRecognitionStatusCallback callback = null; - synchronized (mCallbacksLock) { - callback = mCallbacks.get(soundModelId.getUuid()); - } - if (callback == null) { - Slog.e(TAG, soundModelId + " is not running"); + synchronized (mLock) { + SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); + if (soundModel == null) { + Slog.w(TAG, soundModelId + " is not loaded"); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "stopRecognitionForService(): " + soundModelId - + " is not running")); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "stopRecognitionForService(): " + soundModelId + + " is not loaded")); - return STATUS_ERROR; - } - int ret; - switch (soundModel.getType()) { - case SoundModel.TYPE_GENERIC_SOUND: - ret = mSoundTriggerHelper.stopGenericRecognition( - soundModel.getUuid(), callback); - break; - default: - Slog.e(TAG, "Unknown model type"); + return STATUS_ERROR; + } + IRecognitionStatusCallback callback = null; + synchronized (mCallbacksLock) { + callback = mCallbacks.get(soundModelId.getUuid()); + } + if (callback == null) { + Slog.w(TAG, soundModelId + " is not running"); sEventLogger.log(new SoundTriggerLogger.StringEvent( - "stopRecognitionForService(): Unknown model type")); + "stopRecognitionForService(): " + soundModelId + + " is not running")); return STATUS_ERROR; - } + } + int ret; + switch (soundModel.getType()) { + case SoundModel.TYPE_GENERIC_SOUND: + ret = mSoundTriggerHelper.stopGenericRecognition( + soundModel.getUuid(), callback); + break; + default: + Slog.e(TAG, "Unknown model type"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "stopRecognitionForService(): Unknown model type")); + + return STATUS_ERROR; + } - if (ret != STATUS_OK) { - Slog.e(TAG, "Failed to stop model: " + ret); + if (ret != STATUS_OK) { + Slog.e(TAG, "Failed to stop model: " + ret); - sEventLogger.log(new SoundTriggerLogger.StringEvent( + sEventLogger.log(new SoundTriggerLogger.StringEvent( "stopRecognitionForService(): Failed to stop model: " + ret)); - return ret; - } - synchronized (mCallbacksLock) { - mCallbacks.remove(soundModelId.getUuid()); - } + return ret; + } + synchronized (mCallbacksLock) { + mCallbacks.remove(soundModelId.getUuid()); + } - mSoundModelStatTracker.onStop(soundModelId.getUuid()); + mSoundModelStatTracker.onStop(soundModelId.getUuid()); + } + return STATUS_OK; } - return STATUS_OK; } @Override public int unloadSoundModel(ParcelUuid soundModelId) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId); - } - - sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = " - + soundModelId)); - - synchronized (mLock) { - SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); - if (soundModel == null) { - Slog.e(TAG, soundModelId + " is not loaded"); + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (DEBUG) { + Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "unloadSoundModel(): " + soundModelId + " is not loaded")); + sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = " + + soundModelId)); - return STATUS_ERROR; - } - int ret; - switch (soundModel.getType()) { - case SoundModel.TYPE_KEYPHRASE: - ret = mSoundTriggerHelper.unloadKeyphraseSoundModel( - ((KeyphraseSoundModel) soundModel).getKeyphrases()[0].getId()); - break; - case SoundModel.TYPE_GENERIC_SOUND: - ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.getUuid()); - break; - default: - Slog.e(TAG, "Unknown model type"); + synchronized (mLock) { + SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); + if (soundModel == null) { + Slog.w(TAG, soundModelId + " is not loaded"); sEventLogger.log(new SoundTriggerLogger.StringEvent( - "unloadSoundModel(): Unknown model type")); + "unloadSoundModel(): " + soundModelId + " is not loaded")); return STATUS_ERROR; - } - if (ret != STATUS_OK) { - Slog.e(TAG, "Failed to unload model"); + } + int ret; + switch (soundModel.getType()) { + case SoundModel.TYPE_KEYPHRASE: + ret = mSoundTriggerHelper.unloadKeyphraseSoundModel( + ((KeyphraseSoundModel) soundModel).getKeyphrases()[0].getId()); + break; + case SoundModel.TYPE_GENERIC_SOUND: + ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.getUuid()); + break; + default: + Slog.e(TAG, "Unknown model type"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "unloadSoundModel(): Unknown model type")); + + return STATUS_ERROR; + } + if (ret != STATUS_OK) { + Slog.e(TAG, "Failed to unload model"); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "unloadSoundModel(): Failed to unload model")); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "unloadSoundModel(): Failed to unload model")); - return ret; + return ret; + } + mLoadedModels.remove(soundModelId.getUuid()); + return STATUS_OK; } - mLoadedModels.remove(soundModelId.getUuid()); - return STATUS_OK; } } @Override public boolean isRecognitionActive(ParcelUuid parcelUuid) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - synchronized (mCallbacksLock) { - IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid()); - if (callback == null) { - return false; + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + synchronized (mCallbacksLock) { + IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid()); + if (callback == null) { + return false; + } } + return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid()); } - return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid()); } @Override public int getModelState(ParcelUuid soundModelId) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - int ret = STATUS_ERROR; - if (DEBUG) { - Slog.i(TAG, "getModelState(): id = " + soundModelId); - } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + int ret = STATUS_ERROR; + if (DEBUG) { + Slog.i(TAG, "getModelState(): id = " + soundModelId); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = " - + soundModelId)); + sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = " + + soundModelId)); - synchronized (mLock) { - SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); - if (soundModel == null) { - Slog.e(TAG, soundModelId + " is not loaded"); + synchronized (mLock) { + SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); + if (soundModel == null) { + Slog.w(TAG, soundModelId + " is not loaded"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): " + + soundModelId + " is not loaded")); - sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): " - + soundModelId + " is not loaded")); + return ret; + } + switch (soundModel.getType()) { + case SoundModel.TYPE_GENERIC_SOUND: + ret = mSoundTriggerHelper.getGenericModelState(soundModel.getUuid()); + break; + default: + // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy. + Slog.e(TAG, "Unsupported model type, " + soundModel.getType()); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "getModelState(): Unsupported model type, " + + soundModel.getType())); + break; + } return ret; } - switch (soundModel.getType()) { - case SoundModel.TYPE_GENERIC_SOUND: - ret = mSoundTriggerHelper.getGenericModelState(soundModel.getUuid()); - break; - default: - // SoundModel.TYPE_KEYPHRASE is not supported to increase privacy. - Slog.e(TAG, "Unsupported model type, " + soundModel.getType()); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "getModelState(): Unsupported model type, " - + soundModel.getType())); - break; - } - - return ret; } } @Override @Nullable public ModuleProperties getModuleProperties() { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.i(TAG, "getModuleProperties()"); - } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (DEBUG) { + Slog.i(TAG, "getModuleProperties()"); + } - synchronized (mLock) { - ModuleProperties properties = mSoundTriggerHelper.getModuleProperties(); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "getModuleProperties(): " + properties)); - return properties; + synchronized (mLock) { + ModuleProperties properties = mSoundTriggerHelper.getModuleProperties(); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "getModuleProperties(): " + properties)); + return properties; + } } } @Override public int setParameter(ParcelUuid soundModelId, @ModelParams int modelParam, int value) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.d(TAG, "setParameter(): id=" + soundModelId - + ", param=" + modelParam - + ", value=" + value); - } - - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "setParameter(): id=" + soundModelId + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (DEBUG) { + Slog.d(TAG, "setParameter(): id=" + soundModelId + ", param=" + modelParam - + ", value=" + value)); + + ", value=" + value); + } - synchronized (mLock) { - SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); - if (soundModel == null) { - Slog.e(TAG, soundModelId + " is not loaded. Loaded models: " - + mLoadedModels.toString()); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "setParameter(): id=" + soundModelId + + ", param=" + modelParam + + ", value=" + value)); - sEventLogger.log(new SoundTriggerLogger.StringEvent("setParameter(): " - + soundModelId + " is not loaded")); + synchronized (mLock) { + SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); + if (soundModel == null) { + Slog.w(TAG, soundModelId + " is not loaded. Loaded models: " + + mLoadedModels.toString()); - return STATUS_BAD_VALUE; - } + sEventLogger.log(new SoundTriggerLogger.StringEvent("setParameter(): " + + soundModelId + " is not loaded")); - return mSoundTriggerHelper.setParameter(soundModel.getUuid(), modelParam, value); + return STATUS_BAD_VALUE; + } + + return mSoundTriggerHelper.setParameter(soundModel.getUuid(), modelParam, + value); + } } } @@ -762,28 +796,30 @@ public class SoundTriggerService extends SystemService { public int getParameter(@NonNull ParcelUuid soundModelId, @ModelParams int modelParam) throws UnsupportedOperationException, IllegalArgumentException { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.d(TAG, "getParameter(): id=" + soundModelId - + ", param=" + modelParam); - } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (DEBUG) { + Slog.d(TAG, "getParameter(): id=" + soundModelId + + ", param=" + modelParam); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "getParameter(): id=" + soundModelId - + ", param=" + modelParam)); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "getParameter(): id=" + soundModelId + + ", param=" + modelParam)); - synchronized (mLock) { - SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); - if (soundModel == null) { - Slog.e(TAG, soundModelId + " is not loaded"); + synchronized (mLock) { + SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); + if (soundModel == null) { + Slog.w(TAG, soundModelId + " is not loaded"); - sEventLogger.log(new SoundTriggerLogger.StringEvent("getParameter(): " - + soundModelId + " is not loaded")); + sEventLogger.log(new SoundTriggerLogger.StringEvent("getParameter(): " + + soundModelId + " is not loaded")); - throw new IllegalArgumentException("sound model is not loaded"); - } + throw new IllegalArgumentException("sound model is not loaded"); + } - return mSoundTriggerHelper.getParameter(soundModel.getUuid(), modelParam); + return mSoundTriggerHelper.getParameter(soundModel.getUuid(), modelParam); + } } } @@ -791,29 +827,31 @@ public class SoundTriggerService extends SystemService { @Nullable public ModelParamRange queryParameter(@NonNull ParcelUuid soundModelId, @ModelParams int modelParam) { - enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); - if (DEBUG) { - Slog.d(TAG, "queryParameter(): id=" + soundModelId - + ", param=" + modelParam); - } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); + if (DEBUG) { + Slog.d(TAG, "queryParameter(): id=" + soundModelId + + ", param=" + modelParam); + } - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "queryParameter(): id=" + soundModelId - + ", param=" + modelParam)); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "queryParameter(): id=" + soundModelId + + ", param=" + modelParam)); - synchronized (mLock) { - SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); - if (soundModel == null) { - Slog.e(TAG, soundModelId + " is not loaded"); + synchronized (mLock) { + SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); + if (soundModel == null) { + Slog.w(TAG, soundModelId + " is not loaded"); - sEventLogger.log(new SoundTriggerLogger.StringEvent( - "queryParameter(): " - + soundModelId + " is not loaded")); + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "queryParameter(): " + + soundModelId + " is not loaded")); - return null; - } + return null; + } - return mSoundTriggerHelper.queryParameter(soundModel.getUuid(), modelParam); + return mSoundTriggerHelper.queryParameter(soundModel.getUuid(), modelParam); + } } } @@ -824,6 +862,20 @@ public class SoundTriggerService extends SystemService { mSoundTriggerHelper.detach(); } + private void enforceCallingPermission(String permission) { + PermissionUtil.checkPermissionForPreflight(mContext, mOriginatorIdentity, permission); + } + + private void enforceDetectionPermissions(ComponentName detectionService) { + PackageManager packageManager = mContext.getPackageManager(); + String packageName = detectionService.getPackageName(); + if (packageManager.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException(detectionService.getPackageName() + " does not have" + + " permission " + Manifest.permission.CAPTURE_AUDIO_HOTWORD); + } + } + /** * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and * executed when the service connects. @@ -1577,23 +1629,6 @@ public class SoundTriggerService extends SystemService { } } - private void enforceCallingPermission(String permission) { - if (mContext.checkCallingOrSelfPermission(permission) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller does not hold the permission " + permission); - } - } - - private void enforceDetectionPermissions(ComponentName detectionService) { - PackageManager packageManager = mContext.getPackageManager(); - String packageName = detectionService.getPackageName(); - if (packageManager.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException(detectionService.getPackageName() + " does not have" - + " permission " + Manifest.permission.CAPTURE_AUDIO_HOTWORD); - } - } - //================================================================= // For logging diff --git a/telecomm/java/Android.bp b/telecomm/java/Android.bp index bac7228ca03b..3bd595352a55 100644 --- a/telecomm/java/Android.bp +++ b/telecomm/java/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-telecomm-sources", srcs: [ diff --git a/telephony/common/Android.bp b/telephony/common/Android.bp index 9572c695c552..201ab530fe86 100644 --- a/telephony/common/Android.bp +++ b/telephony/common/Android.bp @@ -1,3 +1,12 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-telephony-common-sources", srcs: [ diff --git a/telephony/java/Android.bp b/telephony/java/Android.bp index 1bd582789572..3941b300206f 100644 --- a/telephony/java/Android.bp +++ b/telephony/java/Android.bp @@ -1,3 +1,13 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + // SPDX-license-identifier-BSD + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-telephony-sources", srcs: [ diff --git a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java index df58da5f157a..039ba512e95b 100644 --- a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java +++ b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java @@ -117,6 +117,7 @@ public class PlatformCompatChangeRule extends CoreCompatChangeRule { uiAutomation.adoptShellPermissionIdentity( Manifest.permission.LOG_COMPAT_CHANGE, Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG, + Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD, Manifest.permission.READ_COMPAT_CHANGE_CONFIG); } } diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java index 429e676155f8..ef324e7c1377 100644 --- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java +++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java @@ -25,27 +25,22 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.Context; import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Icon; import android.media.AudioAttributes; +import android.net.Uri; import android.os.Bundle; -import android.os.Vibrator; import android.os.Handler; -import android.os.UserHandle; -import android.util.Log; -import android.net.Uri; +import android.os.PowerManager; import android.os.SystemClock; +import android.os.Vibrator; +import android.util.Log; import android.widget.RemoteViews; -import android.os.PowerManager; - -// private NM API -import android.app.INotificationManager; import android.widget.Toast; public class NotificationTestList extends TestActivity @@ -185,6 +180,7 @@ public class NotificationTestList extends TestActivity .setContentTitle("default priority group 1") .setGroup("group1") .setOngoing(true) + .setColor(Color.WHITE) .setColorized(true) .build(); mNM.notify(6002, n); diff --git a/tests/net/integration/AndroidManifest.xml b/tests/net/integration/AndroidManifest.xml index f5a4234ede9e..db1850031ff5 100644 --- a/tests/net/integration/AndroidManifest.xml +++ b/tests/net/integration/AndroidManifest.xml @@ -37,6 +37,7 @@ <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <!-- Reading DeviceConfig flags --> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/> + <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <application android:debuggable="true"> <uses-library android:name="android.test.runner"/> diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index 6245e8542b04..40d068d7e324 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -52,7 +52,6 @@ import android.util.Log; import android.util.Range; import com.android.net.module.util.ArrayTrackRecord; -import com.android.server.connectivity.ConnectivityConstants; import com.android.testutils.HandlerUtils; import com.android.testutils.TestableNetworkCallback; @@ -70,7 +69,7 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { private final ConditionVariable mDisconnected = new ConditionVariable(); private final ConditionVariable mPreventReconnectReceived = new ConditionVariable(); private final AtomicBoolean mConnected = new AtomicBoolean(false); - private int mScore; + private NetworkScore mScore; private NetworkAgent mNetworkAgent; private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED; private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE; @@ -91,23 +90,23 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { mNetworkCapabilities.addTransportType(transport); switch (transport) { case TRANSPORT_ETHERNET: - mScore = 70; + mScore = new NetworkScore.Builder().setLegacyInt(70).build(); break; case TRANSPORT_WIFI: - mScore = 60; + mScore = new NetworkScore.Builder().setLegacyInt(60).build(); break; case TRANSPORT_CELLULAR: - mScore = 50; + mScore = new NetworkScore.Builder().setLegacyInt(50).build(); break; case TRANSPORT_WIFI_AWARE: - mScore = 20; + mScore = new NetworkScore.Builder().setLegacyInt(20).build(); break; case TRANSPORT_VPN: mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN); // VPNs deduce the SUSPENDED capability from their underlying networks and there // is no public API to let VPN services set it. mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); - mScore = ConnectivityConstants.VPN_DEFAULT_SCORE; + mScore = new NetworkScore.Builder().setLegacyInt(101).build(); break; default: throw new UnsupportedOperationException("unimplemented network type"); @@ -201,16 +200,22 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { } public void setScore(@NonNull final NetworkScore score) { - mScore = score.getLegacyInt(); + mScore = score; mNetworkAgent.sendNetworkScore(score); } public void adjustScore(int change) { - mScore += change; + final int newLegacyScore = mScore.getLegacyInt() + change; + final NetworkScore.Builder builder = new NetworkScore.Builder() + .setLegacyInt(newLegacyScore); + if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI) && newLegacyScore < 50) { + builder.setExiting(true); + } + mScore = builder.build(); mNetworkAgent.sendNetworkScore(mScore); } - public int getScore() { + public NetworkScore getScore() { return mScore; } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index c3cb5f80920e..b15d25250d4d 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -290,7 +290,6 @@ import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; import com.android.net.module.util.ArrayTrackRecord; import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo; -import com.android.server.connectivity.ConnectivityConstants; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; import com.android.server.connectivity.NetworkAgentInfo; @@ -3033,8 +3032,9 @@ public class ConnectivityServiceTest { } NetworkCapabilities filter = new NetworkCapabilities(); + filter.addTransportType(TRANSPORT_CELLULAR); filter.addCapability(capability); - // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add + // Add NOT_VCN_MANAGED capability into filter unconditionally since some requests will add // NOT_VCN_MANAGED automatically but not for NetworkCapabilities, // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details. filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); @@ -3063,15 +3063,11 @@ public class ConnectivityServiceTest { // Now bring in a higher scored network. TestNetworkAgentWrapper testAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - // Rather than create a validated network which complicates things by registering it's - // own NetworkRequest during startup, just bump up the score to cancel out the - // unvalidated penalty. - testAgent.adjustScore(40); - - // When testAgent connects, because of its 50 score (50 for cell + 40 adjustment score - // - 40 penalty for not being validated), it will beat the testFactory's offer, so - // the request will be removed. - testAgent.connect(false); + // When testAgent connects, because of its score (50 legacy int / cell transport) + // it will beat or equal the testFactory's offer, so the request will be removed. + // Note the agent as validated only if the capability is INTERNET, as it's the only case + // where it makes sense. + testAgent.connect(NET_CAPABILITY_INTERNET == capability /* validated */); testAgent.addCapability(capability); testFactory.expectRequestRemove(); testFactory.assertRequestCountEquals(0); @@ -3085,17 +3081,18 @@ public class ConnectivityServiceTest { testFactory.assertRequestCountEquals(0); assertFalse(testFactory.getMyStartRequested()); - // Make the test agent weak enough to have the exact same score as the - // factory (50 for cell + 40 adjustment -40 validation penalty - 5 adjustment). Make sure - // the factory doesn't see the request. + // If using legacy scores, make the test agent weak enough to have the exact same score as + // the factory (50 for cell - 5 adjustment). Make sure the factory doesn't see the request. + // If not using legacy score, this is a no-op and the "same score removes request" behavior + // has already been tested above. testAgent.adjustScore(-5); expectNoRequestChanged(testFactory); assertFalse(testFactory.getMyStartRequested()); - // Make the test agent weak enough to see the two requests (the one that was just sent, - // and either the default one or the one sent at the top of this test if the default - // won't be seen). - testAgent.adjustScore(-45); + // Make the test agent weak enough that the factory will see the two requests (the one that + // was just sent, and either the default one or the one sent at the top of this test if + // the default won't be seen). + testAgent.setScore(new NetworkScore.Builder().setLegacyInt(2).setExiting(true).build()); testFactory.expectRequestAdds(2); testFactory.assertRequestCountEquals(2); assertTrue(testFactory.getMyStartRequested()); @@ -3127,7 +3124,7 @@ public class ConnectivityServiceTest { assertTrue(testFactory.getMyStartRequested()); // Adjust the agent score up again. Expect the request to be withdrawn. - testAgent.adjustScore(50); + testAgent.setScore(new NetworkScore.Builder().setLegacyInt(50).build()); testFactory.expectRequestRemove(); testFactory.assertRequestCountEquals(0); assertFalse(testFactory.getMyStartRequested()); @@ -3168,22 +3165,35 @@ public class ConnectivityServiceTest { @Test public void testRegisterIgnoringScore() throws Exception { mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - mWiFiNetworkAgent.adjustScore(30); // score = 60 (wifi) + 30 = 90 + mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(90).build()); mWiFiNetworkAgent.connect(true /* validated */); // Make sure the factory sees the default network final NetworkCapabilities filter = new NetworkCapabilities(); + filter.addTransportType(TRANSPORT_CELLULAR); filter.addCapability(NET_CAPABILITY_INTERNET); filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); handlerThread.start(); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), mServiceContext, "testFactory", filter, mCsHandlerThread); - testFactory.registerIgnoringScore(); - testFactory.expectRequestAdd(); + testFactory.register(); + + final MockNetworkFactory testFactoryAll = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "testFactoryAll", filter, mCsHandlerThread); + testFactoryAll.registerIgnoringScore(); + + // The regular test factory should not see the request, because WiFi is stronger than cell. + expectNoRequestChanged(testFactory); + // With ignoringScore though the request is seen. + testFactoryAll.expectRequestAdd(); + + // The legacy int will be ignored anyway, set the only other knob to true + mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(110) + .setTransportPrimary(true).build()); - mWiFiNetworkAgent.adjustScore(20); // exceed the maximum score - expectNoRequestChanged(testFactory); // still seeing the request + expectNoRequestChanged(testFactory); // still not seeing the request + expectNoRequestChanged(testFactoryAll); // still seeing the request mWiFiNetworkAgent.disconnect(); } @@ -6047,7 +6057,8 @@ public class ConnectivityServiceTest { // called again, it does. For example, connect Ethernet, but with a low score, such that it // does not become the default network. mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); - mEthernetNetworkAgent.adjustScore(-40); + mEthernetNetworkAgent.setScore( + new NetworkScore.Builder().setLegacyInt(30).setExiting(true).build()); mEthernetNetworkAgent.connect(false); waitForIdle(); verify(mStatsManager).notifyNetworkStatus(any(List.class), @@ -6922,8 +6933,6 @@ public class ConnectivityServiceTest { callback.expectAvailableCallbacksUnvalidated(mMockVpn); callback.assertNoCallback(); - assertTrue(mMockVpn.getAgent().getScore() > mEthernetNetworkAgent.getScore()); - assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, mMockVpn.getAgent().getScore()); assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); @@ -8225,12 +8234,12 @@ public class ConnectivityServiceTest { assertExtraInfoFromCmPresent(mWiFiNetworkAgent); b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); + b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); b1.expectBroadcast(); callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI)); - b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED); mMockVpn.expectStopVpnRunnerPrivileged(); callback.expectCallback(CallbackEntry.LOST, mMockVpn); b2.expectBroadcast(); @@ -11645,14 +11654,14 @@ public class ConnectivityServiceTest { new LinkProperties(), oemPaidNc); oemPaidAgent.connect(true); - // The oemPaidAgent has score 50 (default for cell) so it beats what the oemPaidFactory can + // The oemPaidAgent has score 50/cell transport, so it beats what the oemPaidFactory can // provide, therefore it loses the request. oemPaidFactory.expectRequestRemove(); oemPaidFactory.assertRequestCountEquals(0); expectNoRequestChanged(internetFactory); internetFactory.assertRequestCountEquals(0); - oemPaidAgent.adjustScore(-30); + oemPaidAgent.setScore(new NetworkScore.Builder().setLegacyInt(20).setExiting(true).build()); // Now the that the agent is weak, the oemPaidFactory can beat the existing network for the // OEM_PAID request. The internet factory however can't beat a network that has OEM_PAID // for the preference request, so it doesn't see the request. @@ -11682,7 +11691,8 @@ public class ConnectivityServiceTest { // Now WiFi connects and it's unmetered, but it's weaker than cell. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); - mWiFiNetworkAgent.adjustScore(-30); // Not the best Internet network, but unmetered + mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).setExiting(true) + .build()); // Not the best Internet network, but unmetered mWiFiNetworkAgent.connect(true); // The OEM_PAID preference prefers an unmetered network to an OEM_PAID network, so diff --git a/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt index 86c91165f61b..1348c6a1ac19 100644 --- a/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt +++ b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt @@ -43,7 +43,7 @@ class NetworkRankerTest { val nais = scores.map { makeNai(true, it) } val bestNetwork = nais[2] // The one with the top score val someRequest = mock(NetworkRequest::class.java) - assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais)) + assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, bestNetwork)) } @Test @@ -52,20 +52,20 @@ class NetworkRankerTest { makeNai(false, 60), makeNai(true, 23), makeNai(false, 68)) val bestNetwork = nais[1] // Top score that's satisfying val someRequest = mock(NetworkRequest::class.java) - assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais)) + assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, nais[1])) } @Test fun testNoMatch() { val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90)) val someRequest = mock(NetworkRequest::class.java) - assertNull(ranker.getBestNetwork(someRequest, nais)) + assertNull(ranker.getBestNetwork(someRequest, nais, null)) } @Test fun testEmpty() { val someRequest = mock(NetworkRequest::class.java) - assertNull(ranker.getBestNetwork(someRequest, emptyList())) + assertNull(ranker.getBestNetwork(someRequest, emptyList(), null)) } // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST @@ -75,10 +75,10 @@ class NetworkRankerTest { val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), makeNai(true, 30)) val someRequest = mock(NetworkRequest::class.java) - assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1)) + assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1, nais1[0])) val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20), makeNai(true, 50), makeNai(true, 50), makeNai(true, 40)) - assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2)) + assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2, nais2[1])) } } |