diff options
232 files changed, 4645 insertions, 2047 deletions
diff --git a/apex/permission/service/java/com/android/permission/persistence/IoUtils.java b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java index 0ae44603516e..569a78c0ab41 100644 --- a/apex/permission/service/java/com/android/permission/persistence/IoUtils.java +++ b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; /** * Utility class for IO. + * + * @hide */ public class IoUtils { diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index 1f479963eaa9..15a2f22e0fea 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -20,8 +20,8 @@ genrule { name: "statslog-statsd-java-gen", tools: ["stats-log-api-gen"], cmd: "$(location stats-log-api-gen) --java $(out) --module statsd" + - " --javaPackage com.android.internal.util --javaClass StatsdStatsLog", - out: ["com/android/internal/util/StatsdStatsLog.java"], + " --javaPackage com.android.internal.statsd --javaClass StatsdStatsLog", + out: ["com/android/internal/statsd/StatsdStatsLog.java"], } java_library_static { @@ -60,7 +60,7 @@ java_sdk_library { "android.os", "android.util", // From :statslog-statsd-java-gen - "com.android.internal.util", + "com.android.internal.statsd", ], api_packages: [ diff --git a/apex/statsd/framework/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java index 4eeae57fe195..0a9f4ebabdf0 100644 --- a/apex/statsd/framework/java/android/util/StatsLog.java +++ b/apex/statsd/framework/java/android/util/StatsLog.java @@ -28,7 +28,7 @@ import android.os.IStatsd; import android.os.Process; import android.util.proto.ProtoOutputStream; -import com.android.internal.util.StatsdStatsLog; +import com.android.internal.statsd.StatsdStatsLog; /** * StatsLog provides an API for developers to send events to statsd. The events can be used to diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 36a8b2cfc084..81d059ed84d9 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -444,6 +444,11 @@ message Atom { TvTunerStateChanged tv_tuner_state_changed = 276 [(module) = "framework"]; MediaOutputOpSwitchReported mediaoutput_op_switch_reported = 277 [(module) = "settings"]; + CellBroadcastMessageFiltered cb_message_filtered = + 278 [(module) = "cellbroadcast"]; + TvTunerDvrStatus tv_tuner_dvr_status = 279 [(module) = "framework"]; + TvCasSessionOpenStatus tv_cas_session_open_status = + 280 [(module) = "framework"]; // StatsdStats tracks platform atoms with ids upto 500. // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value. @@ -9134,8 +9139,10 @@ message IntegrityRulesPushed { /** * Logs when a cell broadcast message is received on the device. * - * Logged from CellBroadcastService module: + * Logged from Cell Broadcast module and platform: * packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/ + * packages/apps/CellBroadcastReceiver/ + * frameworks/opt/telephony/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java */ message CellBroadcastMessageReported { // The type of Cell Broadcast message @@ -9146,8 +9153,40 @@ message CellBroadcastMessageReported { CDMA_SPC = 3; } + // The parts of the cell broadcast message pipeline + enum ReportSource { + UNKNOWN_SOURCE = 0; + FRAMEWORK = 1; + CB_SERVICE = 2; + CB_RECEIVER_APP = 3; + } + // GSM, CDMA, CDMA-SCP optional CbType type = 1; + + // The source of the report + optional ReportSource source = 2; +} + +/** + * Logs when a cell broadcast message is filtered out, or otherwise intentionally not sent to CBR. + * + * Logged from CellBroadcastService module: + * packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/ + */ +message CellBroadcastMessageFiltered { + enum FilterReason { + NOT_FILTERED = 0; + DUPLICATE_MESSAGE = 1; + GEOFENCED_MESSAGE = 2; + AREA_INFO_MESSAGE = 3; + } + + // GSM, CDMA, CDMA-SCP + optional CellBroadcastMessageReported.CbType type = 1; + + // The source of the report + optional FilterReason filter = 2; } /** @@ -9174,6 +9213,7 @@ message CellBroadcastMessageError { UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK = 12; UNEXPECTED_CDMA_MESSAGE_TYPE_FROM_FWK = 13; UNEXPECTED_CDMA_SCP_MESSAGE_TYPE_FROM_FWK = 14; + NO_CONNECTION_TO_CB_SERVICE = 15; } // What kind of error occurred @@ -9205,6 +9245,58 @@ message TvTunerStateChanged { // new state optional State state = 2; } + +/** + * Logs the status of a dvr playback or record. + * This is atom ID 279. + * + * Logged from: + * frameworks/base/media/java/android/media/tv/tuner/dvr + */ +message TvTunerDvrStatus { + enum Type { + UNKNOWN_TYPE = 0; + PLAYBACK = 1; // is a playback + RECORD = 2; // is a record + } + enum State { + UNKNOWN_STATE = 0; + STARTED = 1; // DVR is started + STOPPED = 2; // DVR is stopped + } + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + // DVR type + optional Type type = 2; + // DVR state + optional State state = 3; + // Identify the segment of a record or playback + optional int32 segment_id = 4; + // indicate how many overflow or underflow happened between started to stopped + optional int32 overflow_underflow_count = 5; +} + +/** + * Logs when a cas session opened through MediaCas. + * This is atom ID 280. + * + * Logged from: + * frameworks/base/media/java/android/media/MediaCas.java + */ +message TvCasSessionOpenStatus { + enum State { + UNKNOWN = 0; + SUCCEEDED = 1; // indicate that the session is opened successfully. + FAILED = 2; // indicate that the session isn’t opened successfully. + } + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + // Cas system Id + optional int32 cas_system_id = 2; + // State of the session + optional State state = 3; +} + /** * Logs when an app is frozen or unfrozen. * diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index ed0ea556dc9d..ac00a042b79e 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -520,6 +520,12 @@ public abstract class AccessibilityService extends Service { */ public static final int GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 13; + /** + * Action to show Launcher's all apps. + * @hide + */ + public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14; + private static final String LOG_TAG = "AccessibilityService"; /** diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index cffa59c06a53..108b9eec34fb 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3252,18 +3252,56 @@ public final class ActivityThread extends ClientTransactionHandler { @Override public void handleFixedRotationAdjustments(@NonNull IBinder token, @Nullable FixedRotationAdjustments fixedRotationAdjustments) { - final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null - ? displayAdjustments -> displayAdjustments.setFixedRotationAdjustments( - fixedRotationAdjustments) - : null; + handleFixedRotationAdjustments(token, fixedRotationAdjustments, null /* overrideConfig */); + } + + /** + * Applies the rotation adjustments to override display information in resources belong to the + * provided token. If the token is activity token, the adjustments also apply to application + * because the appearance of activity is usually more sensitive to the application resources. + * + * @param token The token to apply the adjustments. + * @param fixedRotationAdjustments The information to override the display adjustments of + * corresponding resources. If it is null, the exiting override + * will be cleared. + * @param overrideConfig The override configuration of activity. It is used to override + * application configuration. If it is non-null, it means the token is + * confirmed as activity token. Especially when launching new activity, + * {@link #mActivities} hasn't put the new token. + */ + private void handleFixedRotationAdjustments(@NonNull IBinder token, + @Nullable FixedRotationAdjustments fixedRotationAdjustments, + @Nullable Configuration overrideConfig) { + // The element of application configuration override is set only if the application + // adjustments are needed, because activity already has its own override configuration. + final Configuration[] appConfigOverride; + final Consumer<DisplayAdjustments> override; + if (fixedRotationAdjustments != null) { + appConfigOverride = new Configuration[1]; + override = displayAdjustments -> { + displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments); + if (appConfigOverride[0] != null) { + displayAdjustments.getConfiguration().updateFrom(appConfigOverride[0]); + } + }; + } else { + appConfigOverride = null; + override = null; + } if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) { // No resources are associated with the token. return; } - if (mActivities.get(token) == null) { - // Only apply the override to application for activity token because the appearance of - // activity is usually more sensitive to the application resources. - return; + if (overrideConfig == null) { + final ActivityClientRecord r = mActivities.get(token); + if (r == null) { + // It is not an activity token. Nothing to do for application. + return; + } + overrideConfig = r.overrideConfig; + } + if (appConfigOverride != null) { + appConfigOverride[0] = overrideConfig; } // Apply the last override to application resources for compatibility. Because the Resources @@ -3503,7 +3541,8 @@ public final class ActivityThread extends ClientTransactionHandler { // The rotation adjustments must be applied before creating the activity, so the activity // can get the adjusted display info during creation. if (r.mPendingFixedRotationAdjustments != null) { - handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments); + handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments, + r.overrideConfig); r.mPendingFixedRotationAdjustments = null; } @@ -7388,6 +7427,10 @@ public final class ActivityThread extends ClientTransactionHandler { } } + public Bundle getCoreSettings() { + return mCoreSettings; + } + public int getIntCoreSetting(String key, int defaultValue) { synchronized (mResourcesManager) { if (mCoreSettings != null) { @@ -7397,6 +7440,18 @@ public final class ActivityThread extends ClientTransactionHandler { } } + /** + * Get the string value of the given key from core settings. + */ + public String getStringCoreSetting(String key, String defaultValue) { + synchronized (mResourcesManager) { + if (mCoreSettings != null) { + return mCoreSettings.getString(key, defaultValue); + } + return defaultValue; + } + } + float getFloatCoreSetting(String key, float defaultValue) { synchronized (mResourcesManager) { if (mCoreSettings != null) { diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 6bd8fd7c6ecf..6f8233d5de9b 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -139,6 +139,10 @@ public class ApplicationPackageManager extends PackageManager { public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS = "app_permission_button_allow_always"; + // Name of the package which the permission controller's resources are in. + public static final String PERMISSION_CONTROLLER_RESOURCE_PACKAGE = + "com.android.permissioncontroller"; + private final Object mLock = new Object(); @GuardedBy("mLock") @@ -893,8 +897,7 @@ public class ApplicationPackageManager extends PackageManager { mContext.createPackageContext(permissionController, 0); int textId = context.getResources().getIdentifier(APP_PERMISSION_BUTTON_ALLOW_ALWAYS, - "string", "com.android.permissioncontroller"); -// permissionController); STOPSHIP b/147434671 + "string", PERMISSION_CONTROLLER_RESOURCE_PACKAGE); if (textId != 0) { return context.getText(textId); } diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index 2d06ee8d06bc..b68639eb9ac6 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -216,4 +216,14 @@ oneway interface ITaskStackListener { * in {@link android.content.pm.ActivityInfo}. */ void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation); + + /** + * Called when a rotation is about to start on the foreground activity. + * This applies for: + * * free sensor rotation + * * forced rotation + * * rotation settings set through adb command line + * * rotation that occurs when rotation tile is toggled in quick settings + */ + void onActivityRotation(); } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 10f7835b3d69..f9b48e710148 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -38,6 +38,7 @@ import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.os.FileUtils; +import android.os.GraphicsEnvironment; import android.os.Handler; import android.os.IBinder; import android.os.Process; @@ -46,6 +47,7 @@ import android.os.StrictMode; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.provider.Settings; import android.security.net.config.NetworkSecurityConfigProvider; import android.sysprop.VndkProperties; import android.text.TextUtils; @@ -824,6 +826,32 @@ public final class LoadedApk { final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths); + if (mActivityThread != null) { + final String gpuDebugApp = mActivityThread.getStringCoreSetting( + Settings.Global.GPU_DEBUG_APP, ""); + if (!gpuDebugApp.isEmpty() && mPackageName.equals(gpuDebugApp)) { + + // The current application is used to debug, attempt to get the debug layers. + try { + // Get the ApplicationInfo from PackageManager so that metadata fields present. + final ApplicationInfo ai = ActivityThread.getPackageManager() + .getApplicationInfo(mPackageName, PackageManager.GET_META_DATA, + UserHandle.myUserId()); + final String debugLayerPath = GraphicsEnvironment.getInstance() + .getDebugLayerPathsFromSettings(mActivityThread.getCoreSettings(), + ActivityThread.getPackageManager(), mPackageName, ai); + if (debugLayerPath != null) { + libraryPermittedPath += File.pathSeparator + debugLayerPath; + } + } catch (RemoteException e) { + // Unlikely to fail for applications, but in case of failure, something is wrong + // inside the system server, hence just skip. + Slog.e(ActivityThread.TAG, + "RemoteException when fetching debug layer paths for: " + mPackageName); + } + } + } + // If we're not asked to include code, we construct a classloader that has // no code path included. We still need to set up the library search paths // and permitted path because NativeActivity relies on it (it attempts to diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e8ce92db62ad..980fdb87f3fb 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -7625,9 +7625,8 @@ public class Notification implements Parcelable } boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT; - Icon largeIcon = isConversationLayout && mShortcutIcon != null - ? mShortcutIcon - : mBuilder.mN.mLargeIcon; + Icon conversationIcon = mShortcutIcon; + Icon largeIcon = mBuilder.mN.mLargeIcon; TemplateBindResult bindResult = new TemplateBindResult(); StandardTemplateParams p = mBuilder.mParams.reset() .hasProgress(false) @@ -7671,6 +7670,8 @@ public class Notification implements Parcelable contentView.setCharSequence(R.id.status_bar_latest_event_content, "setConversationTitle", conversationTitle); if (isConversationLayout) { + contentView.setIcon(R.id.status_bar_latest_event_content, + "setConversationIcon", conversationIcon); contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsImportantConversation", isImportantConversation); } diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java index 5d8daf88a8de..843d1c7414f8 100644 --- a/core/java/android/app/TaskStackListener.java +++ b/core/java/android/app/TaskStackListener.java @@ -199,4 +199,8 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { @Override public void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation) { } + + @Override + public void onActivityRotation() { + } } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e331471a33db..be3cfeff729e 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1884,6 +1884,9 @@ public class Intent implements Parcelable, Cloneable { /** * Activity action: Launch UI to manage auto-revoke state. + * + * This is equivalent to Intent#ACTION_APPLICATION_DETAILS_SETTINGS + * * <p> * Input: {@link Intent#setData data} should be a {@code package}-scheme {@link Uri} with * a package name, whose auto-revoke state will be reviewed (mandatory). diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index ed75504529b9..fc4ccd072e75 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1449,6 +1449,13 @@ public class PackageInstaller { /** {@hide} */ public static final int UID_UNKNOWN = -1; + /** + * This value is derived from the maximum file name length. No package above this limit + * can ever be successfully installed on the device. + * @hide + */ + public static final int MAX_PACKAGE_NAME_LENGTH = 255; + /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int mode = MODE_INVALID; @@ -1642,6 +1649,8 @@ public class PackageInstaller { /** * Optionally set a label representing the app being installed. + * + * This value will be trimmed to the first 1000 characters. */ public void setAppLabel(@Nullable CharSequence appLabel) { this.appLabel = (appLabel != null) ? appLabel.toString() : null; @@ -1711,7 +1720,8 @@ public class PackageInstaller { * * <p>Initially, all restricted permissions are whitelisted but you can change * which ones are whitelisted by calling this method or the corresponding ones - * on the {@link PackageManager}. + * on the {@link PackageManager}. Only soft or hard restricted permissions on the current + * Android version are supported and any invalid entries will be removed. * * @see PackageManager#addWhitelistedRestrictedPermission(String, String, int) * @see PackageManager#removeWhitelistedRestrictedPermission(String, String, int) diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index f354bdb5a08b..65ce1e7ef079 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -49,8 +49,16 @@ import java.util.Objects; * in the implementation of Parcelable in subclasses. */ public class PackageItemInfo { - /** The maximum length of a safe label, in characters */ - private static final int MAX_SAFE_LABEL_LENGTH = 50000; + + /** + * The maximum length of a safe label, in characters + * + * TODO(b/157997155): It may make sense to expose this publicly so that apps can check for the + * value and truncate the strings/use a different label, without having to hardcode and make + * assumptions about the value. + * @hide + */ + public static final int MAX_SAFE_LABEL_LENGTH = 1000; /** @hide */ public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f; diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java index f64560a14832..fb8fd74545c7 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java @@ -302,7 +302,14 @@ public class ParsedActivityUtils { } String permission = array.getNonConfigurationString(permissionAttr, 0); - activity.setPermission(permission != null ? permission : pkg.getPermission()); + if (isAlias) { + // An alias will override permissions to allow referencing an Activity through its alias + // without needing the original permission. If an alias needs the same permission, + // it must be re-declared. + activity.setPermission(permission); + } else { + activity.setPermission(permission != null ? permission : pkg.getPermission()); + } final boolean setExported = array.hasValue(exportedAttr); if (setExported) { diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java index b37b61757053..6811e06fbe7e 100644 --- a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java @@ -20,7 +20,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.ParsingUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -29,9 +32,6 @@ import android.text.TextUtils; import android.util.TypedValue; import com.android.internal.annotations.VisibleForTesting; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.result.ParseInput; -import android.content.pm.parsing.result.ParseResult; /** @hide */ class ParsedComponentUtils { @@ -60,16 +60,27 @@ class ParsedComponentUtils { component.setName(className); component.setPackageName(packageName); - if (useRoundIcon) { - component.icon = array.getResourceId(roundIconAttr, 0); + int roundIconVal = useRoundIcon ? array.getResourceId(roundIconAttr, 0) : 0; + if (roundIconVal != 0) { + component.icon = roundIconVal; + component.nonLocalizedLabel = null; + } else { + int iconVal = array.getResourceId(iconAttr, 0); + if (iconVal != 0) { + component.icon = iconVal; + component.nonLocalizedLabel = null; + } } - if (component.icon == 0) { - component.icon = array.getResourceId(iconAttr, 0); + int logoVal = array.getResourceId(logoAttr, 0); + if (logoVal != 0) { + component.logo = logoVal; } - component.logo = array.getResourceId(logoAttr, 0); - component.banner = array.getResourceId(bannerAttr, 0); + int bannerVal = array.getResourceId(bannerAttr, 0); + if (bannerVal != 0) { + component.banner = bannerVal; + } if (descriptionAttr != null) { component.descriptionRes = array.getResourceId(descriptionAttr, 0); diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 8d472da1fb7c..570cc2c11738 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -116,22 +116,25 @@ public class BiometricManager { /** * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the - * requirements for <strong>Strong</strong>, as defined by the Android CDD. + * requirements for <strong>Tier 3</strong> (formerly <strong>Strong</strong>), as defined + * by the Android CDD. */ int BIOMETRIC_STRONG = 0x000F; /** * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the - * requirements for <strong>Weak</strong>, as defined by the Android CDD. + * requirements for <strong>Tier 2</strong> (formerly <strong>Weak</strong>), as defined by + * the Android CDD. * * <p>Note that this is a superset of {@link #BIOMETRIC_STRONG} and is defined such that - * <code>BIOMETRIC_STRONG | BIOMETRIC_WEAK == BIOMETRIC_WEAK</code>. + * {@code BIOMETRIC_STRONG | BIOMETRIC_WEAK == BIOMETRIC_WEAK}. */ int BIOMETRIC_WEAK = 0x00FF; /** * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the - * requirements for <strong>Convenience</strong>, as defined by the Android CDD. + * requirements for <strong>Tier 1</strong> (formerly <strong>Convenience</strong>), as + * defined by the Android CDD. * * <p>This constant is intended for use by {@link android.provider.DeviceConfig} to adjust * the reported strength of a biometric sensor. It is not a valid parameter for any of the diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index 6bc962b67576..208406566e52 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -18,6 +18,7 @@ package android.hardware.hdmi; import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +31,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.content.pm.PackageManager; +import android.os.Binder; import android.os.RemoteException; import android.os.SystemProperties; import android.util.ArrayMap; @@ -40,6 +42,7 @@ import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; /** * The {@link HdmiControlManager} class is used to send HDMI control messages @@ -818,6 +821,24 @@ public final class HdmiControlManager { mHdmiControlStatusChangeListeners = new ArrayMap<>(); /** + * Listener used to get the status of the HDMI CEC volume control feature (enabled/disabled). + * @hide + */ + public interface HdmiCecVolumeControlFeatureListener { + /** + * Called when the HDMI Control (CEC) volume control feature is enabled/disabled. + * + * @param enabled status of HDMI CEC volume control feature + * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(boolean)} ()} + **/ + void onHdmiCecVolumeControlFeature(boolean enabled); + } + + private final ArrayMap<HdmiCecVolumeControlFeatureListener, + IHdmiCecVolumeControlFeatureListener> + mHdmiCecVolumeControlFeatureListeners = new ArrayMap<>(); + + /** * Listener used to get vendor-specific commands. */ public interface VendorCommandListener { @@ -979,4 +1000,76 @@ public final class HdmiControlManager { }; } + /** + * Adds a listener to get informed of changes to the state of the HDMI CEC volume control + * feature. + * + * Upon adding a listener, the current state of the HDMI CEC volume control feature will be + * sent immediately. + * + * <p>To stop getting the notification, + * use {@link #removeHdmiCecVolumeControlFeatureListener(HdmiCecVolumeControlFeatureListener)}. + * + * @param listener {@link HdmiCecVolumeControlFeatureListener} instance + * @hide + * @see #removeHdmiCecVolumeControlFeatureListener(HdmiCecVolumeControlFeatureListener) + */ + @RequiresPermission(android.Manifest.permission.HDMI_CEC) + public void addHdmiCecVolumeControlFeatureListener(@NonNull @CallbackExecutor Executor executor, + @NonNull HdmiCecVolumeControlFeatureListener listener) { + if (mService == null) { + Log.e(TAG, "HdmiControlService is not available"); + return; + } + if (mHdmiCecVolumeControlFeatureListeners.containsKey(listener)) { + Log.e(TAG, "listener is already registered"); + return; + } + IHdmiCecVolumeControlFeatureListener wrappedListener = + createHdmiCecVolumeControlFeatureListenerWrapper(executor, listener); + mHdmiCecVolumeControlFeatureListeners.put(listener, wrappedListener); + try { + mService.addHdmiCecVolumeControlFeatureListener(wrappedListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes a listener to stop getting informed of changes to the state of the HDMI CEC volume + * control feature. + * + * @param listener {@link HdmiCecVolumeControlFeatureListener} instance to be removed + * @hide + */ + @RequiresPermission(android.Manifest.permission.HDMI_CEC) + public void removeHdmiCecVolumeControlFeatureListener( + HdmiCecVolumeControlFeatureListener listener) { + if (mService == null) { + Log.e(TAG, "HdmiControlService is not available"); + return; + } + IHdmiCecVolumeControlFeatureListener wrappedListener = + mHdmiCecVolumeControlFeatureListeners.remove(listener); + if (wrappedListener == null) { + Log.e(TAG, "tried to remove not-registered listener"); + return; + } + try { + mService.removeHdmiCecVolumeControlFeatureListener(wrappedListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private IHdmiCecVolumeControlFeatureListener createHdmiCecVolumeControlFeatureListenerWrapper( + Executor executor, final HdmiCecVolumeControlFeatureListener listener) { + return new android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener.Stub() { + @Override + public void onHdmiCecVolumeControlFeature(boolean enabled) { + Binder.clearCallingIdentity(); + executor.execute(() -> listener.onHdmiCecVolumeControlFeature(enabled)); + } + }; + } } diff --git a/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl b/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl new file mode 100644 index 000000000000..873438bb1d20 --- /dev/null +++ b/core/java/android/hardware/hdmi/IHdmiCecVolumeControlFeatureListener.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.hdmi; + +/** + * Listener used to get the status of the HDMI CEC volume control feature (enabled/disabled). + * @hide + */ +oneway interface IHdmiCecVolumeControlFeatureListener { + + /** + * Called when the HDMI Control (CEC) volume control feature is enabled/disabled. + * + * @param enabled status of HDMI CEC volume control feature + * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(boolean)} ()} + **/ + void onHdmiCecVolumeControlFeature(boolean enabled); +} diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl index 3582a927ff46..4c724ef62ea9 100644 --- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl +++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl @@ -18,6 +18,7 @@ package android.hardware.hdmi; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.hdmi.IHdmiControlStatusChangeListener; import android.hardware.hdmi.IHdmiDeviceEventListener; @@ -44,6 +45,8 @@ interface IHdmiControlService { void queryDisplayStatus(IHdmiControlCallback callback); void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener); void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener); + void addHdmiCecVolumeControlFeatureListener(IHdmiCecVolumeControlFeatureListener listener); + void removeHdmiCecVolumeControlFeatureListener(IHdmiCecVolumeControlFeatureListener listener); void addHotplugEventListener(IHdmiHotplugEventListener listener); void removeHotplugEventListener(IHdmiHotplugEventListener listener); void addDeviceEventListener(IHdmiDeviceEventListener listener); diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 034e6a7a06c4..df58a6c636f5 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -22,6 +22,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -93,8 +94,8 @@ public class GraphicsEnvironment { private static final int GAME_DRIVER_GLOBAL_OPT_IN_OFF = 3; private ClassLoader mClassLoader; - private String mLayerPath; - private String mDebugLayerPath; + private String mLibrarySearchPaths; + private String mLibraryPermittedPaths; /** * Set up GraphicsEnvironment @@ -185,118 +186,131 @@ public class GraphicsEnvironment { } /** - * Store the layer paths available to the loader. + * Store the class loader for namespace lookup later. */ public void setLayerPaths(ClassLoader classLoader, - String layerPath, - String debugLayerPath) { + String searchPaths, + String permittedPaths) { // We have to store these in the class because they are set up before we // have access to the Context to properly set up GraphicsEnvironment mClassLoader = classLoader; - mLayerPath = layerPath; - mDebugLayerPath = debugLayerPath; + mLibrarySearchPaths = searchPaths; + mLibraryPermittedPaths = permittedPaths; + } + + /** + * Returns the debug layer paths from settings. + * Returns null if: + * 1) The application process is not debuggable or layer injection metadata flag is not + * true; Or + * 2) ENABLE_GPU_DEBUG_LAYERS is not true; Or + * 3) Package name is not equal to GPU_DEBUG_APP. + */ + public String getDebugLayerPathsFromSettings( + Bundle coreSettings, IPackageManager pm, String packageName, + ApplicationInfo ai) { + if (!debugLayerEnabled(coreSettings, packageName, ai)) { + return null; + } + Log.i(TAG, "GPU debug layers enabled for " + packageName); + String debugLayerPaths = ""; + + // Grab all debug layer apps and add to paths. + final String gpuDebugLayerApps = + coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP, ""); + if (!gpuDebugLayerApps.isEmpty()) { + Log.i(TAG, "GPU debug layer apps: " + gpuDebugLayerApps); + // If a colon is present, treat this as multiple apps, so Vulkan and GLES + // layer apps can be provided at the same time. + final String[] layerApps = gpuDebugLayerApps.split(":"); + for (int i = 0; i < layerApps.length; i++) { + String paths = getDebugLayerAppPaths(pm, layerApps[i]); + if (!paths.isEmpty()) { + // Append the path so files placed in the app's base directory will + // override the external path + debugLayerPaths += paths + File.pathSeparator; + } + } + } + return debugLayerPaths; } /** * Return the debug layer app's on-disk and in-APK lib directories */ - private static String getDebugLayerAppPaths(PackageManager pm, String app) { + private static String getDebugLayerAppPaths(IPackageManager pm, String packageName) { final ApplicationInfo appInfo; try { - appInfo = pm.getApplicationInfo(app, PackageManager.MATCH_ALL); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Debug layer app '" + app + "' not installed"); - - return null; + appInfo = pm.getApplicationInfo(packageName, PackageManager.MATCH_ALL, + UserHandle.myUserId()); + } catch (RemoteException e) { + return ""; + } + if (appInfo == null) { + Log.w(TAG, "Debug layer app '" + packageName + "' not installed"); } final String abi = chooseAbi(appInfo); - final StringBuilder sb = new StringBuilder(); sb.append(appInfo.nativeLibraryDir) - .append(File.pathSeparator); - sb.append(appInfo.sourceDir) + .append(File.pathSeparator) + .append(appInfo.sourceDir) .append("!/lib/") .append(abi); final String paths = sb.toString(); - if (DEBUG) Log.v(TAG, "Debug layer app libs: " + paths); return paths; } + private boolean debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai) { + // Only enable additional debug functionality if the following conditions are met: + // 1. App is debuggable or device is rooted or layer injection metadata flag is true + // 2. ENABLE_GPU_DEBUG_LAYERS is true + // 3. Package name is equal to GPU_DEBUG_APP + if (!isDebuggable() && !canInjectLayers(ai)) { + return false; + } + final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0); + if (enable == 0) { + return false; + } + final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP, ""); + if (packageName == null + || (gpuDebugApp.isEmpty() || packageName.isEmpty()) + || !gpuDebugApp.equals(packageName)) { + return false; + } + return true; + } + /** * Set up layer search paths for all apps - * If debuggable, check for additional debug settings */ private void setupGpuLayers( Context context, Bundle coreSettings, PackageManager pm, String packageName, ApplicationInfo ai) { + final boolean enabled = debugLayerEnabled(coreSettings, packageName, ai); String layerPaths = ""; + if (enabled) { + layerPaths = mLibraryPermittedPaths; - // Only enable additional debug functionality if the following conditions are met: - // 1. App is debuggable or device is rooted or layer injection metadata flag is true - // 2. ENABLE_GPU_DEBUG_LAYERS is true - // 3. Package name is equal to GPU_DEBUG_APP + final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS); + Log.i(TAG, "Vulkan debug layer list: " + layers); + if (layers != null && !layers.isEmpty()) { + setDebugLayers(layers); + } - if (isDebuggable() || canInjectLayers(ai)) { - - final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0); - - if (enable != 0) { - - final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP); - - if ((gpuDebugApp != null && packageName != null) - && (!gpuDebugApp.isEmpty() && !packageName.isEmpty()) - && gpuDebugApp.equals(packageName)) { - Log.i(TAG, "GPU debug layers enabled for " + packageName); - - // Prepend the debug layer path as a searchable path. - // This will ensure debug layers added will take precedence over - // the layers specified by the app. - layerPaths = mDebugLayerPath + ":"; - - // If there is a debug layer app specified, add its path. - final String gpuDebugLayerApp = - coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP); - - if (gpuDebugLayerApp != null && !gpuDebugLayerApp.isEmpty()) { - Log.i(TAG, "GPU debug layer app: " + gpuDebugLayerApp); - // If a colon is present, treat this as multiple apps, so Vulkan and GLES - // layer apps can be provided at the same time. - String[] layerApps = gpuDebugLayerApp.split(":"); - for (int i = 0; i < layerApps.length; i++) { - String paths = getDebugLayerAppPaths(pm, layerApps[i]); - if (paths != null) { - // Append the path so files placed in the app's base directory will - // override the external path - layerPaths += paths + ":"; - } - } - } - - final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS); - - Log.i(TAG, "Vulkan debug layer list: " + layers); - if (layers != null && !layers.isEmpty()) { - setDebugLayers(layers); - } - - final String layersGLES = - coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES); - - Log.i(TAG, "GLES debug layer list: " + layersGLES); - if (layersGLES != null && !layersGLES.isEmpty()) { - setDebugLayersGLES(layersGLES); - } - } + final String layersGLES = + coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES); + Log.i(TAG, "GLES debug layer list: " + layersGLES); + if (layersGLES != null && !layersGLES.isEmpty()) { + setDebugLayersGLES(layersGLES); } } // Include the app's lib directory in all cases - layerPaths += mLayerPath; - + layerPaths += mLibrarySearchPaths; setLayerPaths(mClassLoader, layerPaths); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 7845200f4bf7..a8391c2b5461 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -4090,13 +4090,6 @@ public class UserManager { public static int getMaxSupportedUsers() { // Don't allow multiple users on certain builds if (android.os.Build.ID.startsWith("JVP")) return 1; - if (ActivityManager.isLowRamDeviceStatic()) { - // Low-ram devices are Svelte. Most of the time they don't get multi-user. - if ((Resources.getSystem().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK) - != Configuration.UI_MODE_TYPE_TELEVISION) { - return 1; - } - } return SystemProperties.getInt("fw.max_users", Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers)); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1b19e1290121..e10fceaa5bc7 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1778,6 +1778,15 @@ public final class Settings { = "android.settings.NOTIFICATION_SETTINGS"; /** + * Activity Action: Show conversation settings. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CONVERSATION_SETTINGS + = "android.settings.CONVERSATION_SETTINGS"; + + /** * Activity Action: Show notification history screen. * * @hide @@ -14245,15 +14254,6 @@ public final class Settings { public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader"; /** - * Persistent user id that is last logged in to. - * - * They map to user ids, for example, 10, 11, 12. - * - * @hide - */ - public static final String LAST_ACTIVE_USER_ID = "last_active_persistent_user_id"; - - /** * Whether we've enabled native flags health check on this device. Takes effect on * reboot. The value "1" enables native flags health check; otherwise it's disabled. * @hide diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index b34268d04238..a2489b9b68d9 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4325,6 +4325,15 @@ public final class Telephony { public static final String ETWS_WARNING_TYPE = "etws_warning_type"; /** + * ETWS (Earthquake and Tsunami Warning System) primary message or not (ETWS alerts only). + * <p>See {@link android.telephony.SmsCbEtwsInfo}</p> + * <P>Type: BOOLEAN</P> + * + * @hide // TODO: Unhide this for S. + */ + public static final String ETWS_IS_PRIMARY = "etws_is_primary"; + + /** * CMAS (Commercial Mobile Alert System) message class (CMAS alerts only). * <p>See {@link android.telephony.SmsCbCmasInfo}</p> * <P>Type: INTEGER</P> @@ -4464,37 +4473,6 @@ public final class Telephony { CMAS_URGENCY, CMAS_CERTAINTY }; - - /** - * Query columns for instantiating {@link android.telephony.SmsCbMessage} objects. - * @hide - */ - public static final String[] QUERY_COLUMNS_FWK = { - _ID, - SLOT_INDEX, - SUBSCRIPTION_ID, - GEOGRAPHICAL_SCOPE, - PLMN, - LAC, - CID, - SERIAL_NUMBER, - SERVICE_CATEGORY, - LANGUAGE_CODE, - MESSAGE_BODY, - MESSAGE_FORMAT, - MESSAGE_PRIORITY, - ETWS_WARNING_TYPE, - CMAS_MESSAGE_CLASS, - CMAS_CATEGORY, - CMAS_RESPONSE_TYPE, - CMAS_SEVERITY, - CMAS_URGENCY, - CMAS_CERTAINTY, - RECEIVED_TIME, - MESSAGE_BROADCASTED, - GEOMETRIES, - MAXIMUM_WAIT_TIME - }; } /** diff --git a/core/java/android/service/autofill/InlineSuggestionRoot.java b/core/java/android/service/autofill/InlineSuggestionRoot.java index c879653859d8..16c3f1d4e476 100644 --- a/core/java/android/service/autofill/InlineSuggestionRoot.java +++ b/core/java/android/service/autofill/InlineSuggestionRoot.java @@ -58,7 +58,9 @@ public class InlineSuggestionRoot extends FrameLayout { case MotionEvent.ACTION_DOWN: { mDownX = event.getX(); mDownY = event.getY(); - } break; + } + // Intentionally fall through to the next case so that when the window is obscured + // we transfer the touch to the remote IME window and don't handle it locally. case MotionEvent.ACTION_MOVE: { final float distance = MathUtils.dist(mDownX, mDownY, diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java index d01bc2524332..8383072a48e3 100644 --- a/core/java/android/service/controls/Control.java +++ b/core/java/android/service/controls/Control.java @@ -73,25 +73,37 @@ public final class Control implements Parcelable { }) public @interface Status {}; + /** + * Reserved for use with the {@link StatelessBuilder}, and while loading. When state is + * requested via {@link ControlsProviderService#createPublisherFor}, use other status codes + * to indicate the proper device state. + */ public static final int STATUS_UNKNOWN = 0; /** - * The device corresponding to the {@link Control} is responding correctly. + * Used to indicate that the state of the device was successfully retrieved. This includes + * all scenarios where the device may have a warning for the user, such as "Lock jammed", + * or "Vacuum stuck". Any information for the user should be set through + * {@link StatefulBuilder#setStatusText}. */ public static final int STATUS_OK = 1; /** - * The device corresponding to the {@link Control} cannot be found or was removed. + * The device corresponding to the {@link Control} cannot be found or was removed. The user + * will be alerted and directed to the application to resolve. */ public static final int STATUS_NOT_FOUND = 2; /** - * The device corresponding to the {@link Control} is in an error state. + * Used to indicate that there was a temporary error while loading the device state. A default + * error message will be displayed in place of any custom text that was set through + * {@link StatefulBuilder#setStatusText}. */ public static final int STATUS_ERROR = 3; /** - * The {@link Control} is currently disabled. + * The {@link Control} is currently disabled. A default error message will be displayed in + * place of any custom text that was set through {@link StatefulBuilder#setStatusText}. */ public static final int STATUS_DISABLED = 4; diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index e4fbf9f0e187..9b293eb463e5 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -340,6 +340,10 @@ public class PhoneStateListener { /** * Listen for display info changed event. * + * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE + * READ_PHONE_STATE} or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). + * * @see #onDisplayInfoChanged */ public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000; diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index a9af59543c78..f6c72c4eefbc 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -289,11 +289,6 @@ public class GestureDetector { private VelocityTracker mVelocityTracker; /** - * True if the detector can throw exception when touch steam is unexpected . - */ - private boolean mExceptionForTouchStream; - - /** * Consistency verifier for debugging purposes. */ private final InputEventConsistencyVerifier mInputEventConsistencyVerifier = @@ -472,8 +467,6 @@ public class GestureDetector { mTouchSlopSquare = touchSlop * touchSlop; mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop; mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop; - mExceptionForTouchStream = context != null - && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R; } /** @@ -646,13 +639,6 @@ public class GestureDetector { break; case MotionEvent.ACTION_MOVE: - if (mExceptionForTouchStream && !mStillDown) { - throw new IllegalStateException("Incomplete event stream received: " - + "Received ACTION_MOVE before ACTION_DOWN. ACTION_DOWN must precede " - + "ACTION_MOVE following ACTION_UP or ACTION_CANCEL, or when this " - + "GestureDetector has not yet received any events."); - } - if (mInLongPress || mInContextClick) { break; } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index bd811fc1f052..a954f3631a01 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1200,8 +1200,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } if (mDeferredDestroySurfaceControl != null) { - mTmpTransaction.remove(mDeferredDestroySurfaceControl).apply(); - mDeferredDestroySurfaceControl = null; + synchronized (mSurfaceControlLock) { + mTmpTransaction.remove(mDeferredDestroySurfaceControl).apply(); + mDeferredDestroySurfaceControl = null; + } } runOnUiThread(this::performDrawFinished); diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java index b387a68dd8a3..68943bf2a83a 100644 --- a/core/java/android/view/autofill/AutofillId.java +++ b/core/java/android/view/autofill/AutofillId.java @@ -75,7 +75,10 @@ public final class AutofillId implements Parcelable { /** @hide */ public static AutofillId withoutSession(@NonNull AutofillId id) { final int flags = id.mFlags & ~FLAG_HAS_SESSION; - return new AutofillId(flags, id.mViewId, id.mVirtualLongId, NO_SESSION); + final long virtualChildId = + ((id.mFlags & FLAG_IS_VIRTUAL_LONG) != 0) ? id.mVirtualLongId + : id.mVirtualIntId; + return new AutofillId(flags, id.mViewId, virtualChildId, NO_SESSION); } /** @hide */ diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 7042f29fc4e4..4a6551176198 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -205,6 +205,8 @@ public class WebChromeClient { * <p>Note that if the {@link WebChromeClient} is set to be {@code null}, * or if {@link WebChromeClient} is not set at all, the default dialog will * be suppressed and Javascript execution will continue immediately. + * <p>Note that the default dialog does not inherit the {@link + * android.view.Display#FLAG_SECURE} flag from the parent window. * * @param view The WebView that initiated the callback. * @param url The url of the page requesting the dialog. @@ -240,6 +242,8 @@ public class WebChromeClient { * or if {@link WebChromeClient} is not set at all, the default dialog will * be suppressed and the default value of {@code false} will be returned to * the JavaScript code immediately. + * <p>Note that the default dialog does not inherit the {@link + * android.view.Display#FLAG_SECURE} flag from the parent window. * * @param view The WebView that initiated the callback. * @param url The url of the page requesting the dialog. @@ -274,6 +278,8 @@ public class WebChromeClient { * or if {@link WebChromeClient} is not set at all, the default dialog will * be suppressed and {@code null} will be returned to the JavaScript code * immediately. + * <p>Note that the default dialog does not inherit the {@link + * android.view.Display#FLAG_SECURE} flag from the parent window. * * @param view The WebView that initiated the callback. * @param url The url of the page requesting the dialog. @@ -308,6 +314,8 @@ public class WebChromeClient { * <p>Note that if the {@link WebChromeClient} is set to be {@code null}, * or if {@link WebChromeClient} is not set at all, the default dialog will * be suppressed and the navigation will be resumed immediately. + * <p>Note that the default dialog does not inherit the {@link + * android.view.Display#FLAG_SECURE} flag from the parent window. * * @param view The WebView that initiated the callback. * @param url The url of the page requesting the dialog. diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java index d2614da31ff9..9ccb4c172158 100644 --- a/core/java/android/window/VirtualDisplayTaskEmbedder.java +++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java @@ -365,8 +365,8 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { // Found the topmost stack on target display. Now check if the topmost task's // description changed. if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { - mHost.onTaskBackgroundColorChanged(VirtualDisplayTaskEmbedder.this, - taskInfo.taskDescription.getBackgroundColor()); + mHost.post(()-> mHost.onTaskBackgroundColorChanged(VirtualDisplayTaskEmbedder.this, + taskInfo.taskDescription.getBackgroundColor())); } } diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index 493865ac563f..b723db287823 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -151,6 +151,13 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { mOnProfileSelectedListener.onProfileSelected(position); } } + + @Override + public void onPageScrollStateChanged(int state) { + if (mOnProfileSelectedListener != null) { + mOnProfileSelectedListener.onProfilePageStateChanged(state); + } + } }); viewPager.setAdapter(this); viewPager.setCurrentItem(mCurrentPage); @@ -606,6 +613,17 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * {@link #PROFILE_WORK} if the work profile was selected. */ void onProfileSelected(int profileIndex); + + + /** + * Callback for when the scroll state changes. Useful for discovering when the user begins + * dragging, when the pager is automatically settling to the current page, or when it is + * fully stopped/idle. + * @param state {@link ViewPager#SCROLL_STATE_IDLE}, {@link ViewPager#SCROLL_STATE_DRAGGING} + * or {@link ViewPager#SCROLL_STATE_SETTLING} + * @see ViewPager.OnPageChangeListener#onPageScrollStateChanged + */ + void onProfilePageStateChanged(int state); } /** diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 2a43287a3ae3..8f3edb8f1787 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -102,6 +102,7 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.Button; @@ -129,6 +130,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.GridLayoutManager; import com.android.internal.widget.RecyclerView; import com.android.internal.widget.ResolverDrawerLayout; +import com.android.internal.widget.ViewPager; import com.google.android.collect.Lists; @@ -204,6 +206,10 @@ public class ChooserActivity extends ResolverActivity implements public static final int SELECTION_TYPE_STANDARD = 3; public static final int SELECTION_TYPE_COPY = 4; + private static final int SCROLL_STATUS_IDLE = 0; + private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1; + private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2; + // statsd logger wrapper protected ChooserActivityLogger mChooserActivityLogger; @@ -293,6 +299,7 @@ public class ChooserActivity extends ResolverActivity implements protected MetricsLogger mMetricsLogger; private ContentPreviewCoordinator mPreviewCoord; + private int mScrollStatus = SCROLL_STATUS_IDLE; @VisibleForTesting protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter; @@ -2647,6 +2654,7 @@ public class ChooserActivity extends ResolverActivity implements if (recyclerView.getVisibility() == View.VISIBLE) { int directShareHeight = 0; rowsToShow = Math.min(4, rowsToShow); + boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow); mLastNumberOfChildren = recyclerView.getChildCount(); for (int i = 0, childCount = recyclerView.getChildCount(); i < childCount && rowsToShow > 0; i++) { @@ -2657,6 +2665,9 @@ public class ChooserActivity extends ResolverActivity implements } int height = child.getHeight(); offset += height; + if (shouldShowExtraRow) { + offset += height; + } if (gridAdapter.getTargetType( recyclerView.getChildAdapterPosition(child)) @@ -2680,7 +2691,7 @@ public class ChooserActivity extends ResolverActivity implements offset = Math.min(offset, minHeight); } } else { - ViewGroup currentEmptyStateView = getCurrentEmptyStateView(); + ViewGroup currentEmptyStateView = getActiveEmptyStateView(); if (currentEmptyStateView.getVisibility() == View.VISIBLE) { offset += currentEmptyStateView.getHeight(); } @@ -2692,6 +2703,18 @@ public class ChooserActivity extends ResolverActivity implements } /** + * If we have a tabbed view and are showing 1 row in the current profile and an empty + * state screen in the other profile, to prevent cropping of the empty state screen we show + * a second row in the current profile. + */ + private boolean shouldShowExtraRow(int rowsToShow) { + return shouldShowTabs() + && rowsToShow == 1 + && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen( + mChooserMultiProfilePagerAdapter.getInactiveListAdapter()); + } + + /** * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle * does not match either the personal or work user handle. **/ @@ -2705,7 +2728,7 @@ public class ChooserActivity extends ResolverActivity implements return -1; } - private ViewGroup getCurrentEmptyStateView() { + private ViewGroup getActiveEmptyStateView() { int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage(); return mChooserMultiProfilePagerAdapter.getItem(currentPage).getEmptyStateView(); } @@ -2822,10 +2845,20 @@ public class ChooserActivity extends ResolverActivity implements final float defaultElevation = elevatedView.getElevation(); final float chooserHeaderScrollElevation = getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation); - mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener( new RecyclerView.OnScrollListener() { public void onScrollStateChanged(RecyclerView view, int scrollState) { + if (scrollState == RecyclerView.SCROLL_STATE_IDLE) { + if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) { + mScrollStatus = SCROLL_STATUS_IDLE; + setHorizontalScrollingEnabled(true); + } + } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) { + if (mScrollStatus == SCROLL_STATUS_IDLE) { + mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL; + setHorizontalScrollingEnabled(false); + } + } } public void onScrolled(RecyclerView view, int dx, int dy) { @@ -3026,8 +3059,42 @@ public class ChooserActivity extends ResolverActivity implements currentRootAdapter.updateDirectShareExpansion(); } - void prepareIntentForCrossProfileLaunch(Intent intent) { - intent.fixUris(UserHandle.myUserId()); + @Override + protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + if (shouldShowTabs()) { + mChooserMultiProfilePagerAdapter + .setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom()); + mChooserMultiProfilePagerAdapter.setupContainerPadding( + getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container)); + } + return super.onApplyWindowInsets(v, insets); + } + + private void setHorizontalScrollingEnabled(boolean enabled) { + ResolverViewPager viewPager = findViewById(R.id.profile_pager); + viewPager.setSwipingEnabled(enabled); + } + + private void setVerticalScrollEnabled(boolean enabled) { + ChooserGridLayoutManager layoutManager = + (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView() + .getLayoutManager(); + layoutManager.setVerticalScrollEnabled(enabled); + } + + @Override + void onHorizontalSwipeStateChanged(int state) { + if (state == ViewPager.SCROLL_STATE_DRAGGING) { + if (mScrollStatus == SCROLL_STATUS_IDLE) { + mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL; + setVerticalScrollEnabled(false); + } + } else if (state == ViewPager.SCROLL_STATE_IDLE) { + if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) { + mScrollStatus = SCROLL_STATUS_IDLE; + setVerticalScrollEnabled(true); + } + } } /** diff --git a/core/java/com/android/internal/app/ChooserGridLayoutManager.java b/core/java/com/android/internal/app/ChooserGridLayoutManager.java index 317a987cf359..c50ebd9562c9 100644 --- a/core/java/com/android/internal/app/ChooserGridLayoutManager.java +++ b/core/java/com/android/internal/app/ChooserGridLayoutManager.java @@ -28,6 +28,8 @@ import com.android.internal.widget.RecyclerView; */ public class ChooserGridLayoutManager extends GridLayoutManager { + private boolean mVerticalScrollEnabled = true; + /** * Constructor used when layout manager is set in XML by RecyclerView attribute * "layoutManager". If spanCount is not specified in the XML, it defaults to a @@ -67,4 +69,13 @@ public class ChooserGridLayoutManager extends GridLayoutManager { // Do not count the footer view in the official count return super.getRowCountForAccessibility(recycler, state) - 1; } + + void setVerticalScrollEnabled(boolean verticalScrollEnabled) { + mVerticalScrollEnabled = verticalScrollEnabled; + } + + @Override + public boolean canScrollVertically() { + return mVerticalScrollEnabled && super.canScrollVertically(); + } } diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index 774be3c9c4b8..ffa6041721c6 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -38,6 +38,7 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd private final ChooserProfileDescriptor[] mItems; private final boolean mIsSendAction; + private int mBottomOffset; ChooserMultiProfilePagerAdapter(Context context, ChooserActivity.ChooserGridAdapter adapter, @@ -245,6 +246,16 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd } } + void setEmptyStateBottomOffset(int bottomOffset) { + mBottomOffset = bottomOffset; + } + + @Override + protected void setupContainerPadding(View container) { + container.setPadding(container.getPaddingLeft(), container.getPaddingTop(), + container.getPaddingRight(), container.getPaddingBottom() + mBottomOffset); + } + class ChooserProfileDescriptor extends ProfileDescriptor { private ChooserActivity.ChooserGridAdapter chooserGridAdapter; private RecyclerView recyclerView; diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index e65d1fe9ce53..61a52bcc03f9 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -18,6 +18,7 @@ package com.android.internal.app; import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; +import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER; import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE; import android.annotation.Nullable; @@ -246,6 +247,7 @@ public class IntentForwarderActivity extends Activity { int selectedProfile = findSelectedProfile(className); sanitizeIntent(intentReceived); intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile); + intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId)); startActivityAsCaller(intentReceived, null, null, false, userId); finish(); } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index f96f560fc60f..f8eec57bbd6b 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -184,6 +184,18 @@ public class ResolverActivity extends Activity implements static final String EXTRA_SELECTED_PROFILE = "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE"; + /** + * {@link UserHandle} extra to indicate the user of the user that the starting intent + * originated from. + * <p>This is not necessarily the same as {@link #getUserId()} or {@link UserHandle#myUserId()}, + * as there are edge cases when the intent resolver is launched in the other profile. + * For example, when we have 0 resolved apps in current profile and multiple resolved + * apps in the other profile, opening a link from the current profile launches the intent + * resolver in the other one. b/148536209 for more info. + */ + static final String EXTRA_CALLING_USER = + "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER"; + static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL; static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK; @@ -470,17 +482,20 @@ public class ResolverActivity extends Activity implements // the intent resolver is started in the other profile. Since this is the only case when // this happens, we check for it here and set the current profile's tab. int selectedProfile = getCurrentProfile(); - UserHandle intentUser = UserHandle.of(getLaunchingUserId()); + UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER) + ? getIntent().getParcelableExtra(EXTRA_CALLING_USER) + : getUser(); if (!getUser().equals(intentUser)) { if (getPersonalProfileUserHandle().equals(intentUser)) { selectedProfile = PROFILE_PERSONAL; } else if (getWorkProfileUserHandle().equals(intentUser)) { selectedProfile = PROFILE_WORK; } - } - int selectedProfileExtra = getSelectedProfileExtra(); - if (selectedProfileExtra != -1) { - selectedProfile = selectedProfileExtra; + } else { + int selectedProfileExtra = getSelectedProfileExtra(); + if (selectedProfileExtra != -1) { + selectedProfile = selectedProfileExtra; + } } // We only show the default app for the profile of the current user. The filterLastUsed // flag determines whether to show a default app and that app is not shown in the @@ -535,22 +550,6 @@ public class ResolverActivity extends Activity implements return selectedProfile; } - /** - * Returns the user id of the user that the starting intent originated from. - * <p>This is not necessarily equal to {@link #getUserId()} or {@link UserHandle#myUserId()}, - * as there are edge cases when the intent resolver is launched in the other profile. - * For example, when we have 0 resolved apps in current profile and multiple resolved apps - * in the other profile, opening a link from the current profile launches the intent resolver - * in the other one. b/148536209 for more info. - */ - private int getLaunchingUserId() { - int contentUserHint = getIntent().getContentUserHint(); - if (contentUserHint == UserHandle.USER_CURRENT) { - return UserHandle.myUserId(); - } - return contentUserHint; - } - protected @Profile int getCurrentProfile() { return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK); } @@ -1250,7 +1249,9 @@ public class ResolverActivity extends Activity implements return true; } - void prepareIntentForCrossProfileLaunch(Intent intent) {} + private void prepareIntentForCrossProfileLaunch(Intent intent) { + intent.fixUris(UserHandle.myUserId()); + } private boolean isLaunchingTargetInOtherProfile() { return mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() @@ -1653,10 +1654,18 @@ public class ResolverActivity extends Activity implements viewPager.setVisibility(View.VISIBLE); tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage()); mMultiProfilePagerAdapter.setOnProfileSelectedListener( - index -> { - tabHost.setCurrentTab(index); - resetButtonBar(); - resetCheckedItem(); + new AbstractMultiProfilePagerAdapter.OnProfileSelectedListener() { + @Override + public void onProfileSelected(int index) { + tabHost.setCurrentTab(index); + resetButtonBar(); + resetCheckedItem(); + } + + @Override + public void onProfilePageStateChanged(int state) { + onHorizontalSwipeStateChanged(state); + } }); mMultiProfilePagerAdapter.setOnSwitchOnWorkSelectedListener( () -> { @@ -1668,6 +1677,8 @@ public class ResolverActivity extends Activity implements findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE); } + void onHorizontalSwipeStateChanged(int state) {} + private void maybeHideDivider() { if (!isIntentPicker()) { return; diff --git a/core/java/com/android/internal/app/ResolverViewPager.java b/core/java/com/android/internal/app/ResolverViewPager.java index 4eb6e3bd2071..9cdfc2f5c763 100644 --- a/core/java/com/android/internal/app/ResolverViewPager.java +++ b/core/java/com/android/internal/app/ResolverViewPager.java @@ -18,6 +18,7 @@ package com.android.internal.app; import android.content.Context; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import com.android.internal.widget.ViewPager; @@ -30,6 +31,8 @@ import com.android.internal.widget.ViewPager; */ public class ResolverViewPager extends ViewPager { + private boolean mSwipingEnabled = true; + public ResolverViewPager(Context context) { super(context); } @@ -70,4 +73,13 @@ public class ResolverViewPager extends ViewPager { heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + + void setSwipingEnabled(boolean swipingEnabled) { + mSwipingEnabled = swipingEnabled; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return mSwipingEnabled && super.onInterceptTouchEvent(ev); + } } diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java index 67ffd4d93404..5212265f6c8a 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -60,4 +60,28 @@ public interface UiEventLogger { */ void logWithInstanceId(@NonNull UiEventEnum event, int uid, @Nullable String packageName, @Nullable InstanceId instance); + + /** + * Log an event with ranked-choice information along with package. + * Does nothing if event.getId() <= 0. + * @param event an enum implementing UiEventEnum interface. + * @param uid the uid of the relevant app, if known (0 otherwise). + * @param packageName the package name of the relevant app, if known (null otherwise). + * @param position the position picked. + */ + void logWithPosition(@NonNull UiEventEnum event, int uid, @Nullable String packageName, + int position); + + /** + * Log an event with ranked-choice information along with package and instance ID. + * Does nothing if event.getId() <= 0. + * @param event an enum implementing UiEventEnum interface. + * @param uid the uid of the relevant app, if known (0 otherwise). + * @param packageName the package name of the relevant app, if known (null otherwise). + * @param instance An identifier obtained from an InstanceIdSequence. If null, reduces to + * logWithPosition(). + * @param position the position picked. + */ + void logWithInstanceIdAndPosition(@NonNull UiEventEnum event, int uid, + @Nullable String packageName, @Nullable InstanceId instance, int position); } diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index 4d171ec8a3a8..c9156c13aae3 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -48,4 +48,31 @@ public class UiEventLoggerImpl implements UiEventLogger { log(event, uid, packageName); } } + + @Override + public void logWithPosition(UiEventEnum event, int uid, String packageName, int position) { + final int eventID = event.getId(); + if (eventID > 0) { + FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED, + /* event_id = 1 */ eventID, + /* package_name = 2 */ packageName, + /* instance_id = 3 */ 0, + /* position_picked = 4 */ position); + } + } + + @Override + public void logWithInstanceIdAndPosition(UiEventEnum event, int uid, String packageName, + InstanceId instance, int position) { + final int eventID = event.getId(); + if ((eventID > 0) && (instance != null)) { + FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED, + /* event_id = 1 */ eventID, + /* package_name = 2 */ packageName, + /* instance_id = 3 */ instance.getId(), + /* position_picked = 4 */ position); + } else { + logWithPosition(event, uid, packageName, position); + } + } } diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java index 180ab0810f5b..2d09434807a6 100644 --- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java +++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java @@ -35,13 +35,15 @@ public class UiEventLoggerFake implements UiEventLogger { public final int eventId; public final int uid; public final String packageName; - public final InstanceId instanceId; // Used only for WithInstanceId variant + public final InstanceId instanceId; // Used only for WithInstanceId variants + public final int position; // Used only for Position variants FakeUiEvent(int eventId, int uid, String packageName) { this.eventId = eventId; this.uid = uid; this.packageName = packageName; this.instanceId = null; + this.position = 0; } FakeUiEvent(int eventId, int uid, String packageName, InstanceId instanceId) { @@ -49,6 +51,15 @@ public class UiEventLoggerFake implements UiEventLogger { this.uid = uid; this.packageName = packageName; this.instanceId = instanceId; + this.position = 0; + } + + FakeUiEvent(int eventId, int uid, String packageName, InstanceId instanceId, int position) { + this.eventId = eventId; + this.uid = uid; + this.packageName = packageName; + this.instanceId = instanceId; + this.position = position; } } @@ -92,4 +103,21 @@ public class UiEventLoggerFake implements UiEventLogger { mLogs.add(new FakeUiEvent(eventId, uid, packageName, instance)); } } + + @Override + public void logWithPosition(UiEventEnum event, int uid, String packageName, int position) { + final int eventId = event.getId(); + if (eventId > 0) { + mLogs.add(new FakeUiEvent(eventId, uid, packageName, null, position)); + } + } + + @Override + public void logWithInstanceIdAndPosition(UiEventEnum event, int uid, String packageName, + InstanceId instance, int position) { + final int eventId = event.getId(); + if (eventId > 0) { + mLogs.add(new FakeUiEvent(eventId, uid, packageName, instance, position)); + } + } } diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index ad6c7e8f7f60..adc7ba30c157 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -8,10 +8,10 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -37,10 +37,12 @@ public class ScreenshotHelper { private int mSource; private boolean mHasStatusBar; private boolean mHasNavBar; - private Bitmap mBitmap; + private Bundle mBitmapBundle; private Rect mBoundsInScreen; private Insets mInsets; private int mTaskId; + private int mUserId; + private ComponentName mTopComponent; ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) { mSource = source; @@ -48,24 +50,29 @@ public class ScreenshotHelper { mHasNavBar = hasNav; } - ScreenshotRequest( - int source, Bitmap bitmap, Rect boundsInScreen, Insets insets, int taskId) { + ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen, Insets insets, + int taskId, int userId, ComponentName topComponent) { mSource = source; - mBitmap = bitmap; + mBitmapBundle = bitmapBundle; mBoundsInScreen = boundsInScreen; mInsets = insets; mTaskId = taskId; + mUserId = userId; + mTopComponent = topComponent; } ScreenshotRequest(Parcel in) { mSource = in.readInt(); mHasStatusBar = in.readBoolean(); mHasNavBar = in.readBoolean(); + if (in.readInt() == 1) { - mBitmap = in.readParcelable(Bitmap.class.getClassLoader()); + mBitmapBundle = in.readBundle(getClass().getClassLoader()); mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader()); mInsets = in.readParcelable(Insets.class.getClassLoader()); mTaskId = in.readInt(); + mUserId = in.readInt(); + mTopComponent = in.readParcelable(ComponentName.class.getClassLoader()); } } @@ -81,8 +88,8 @@ public class ScreenshotHelper { return mHasNavBar; } - public Bitmap getBitmap() { - return mBitmap; + public Bundle getBitmapBundle() { + return mBitmapBundle; } public Rect getBoundsInScreen() { @@ -97,6 +104,15 @@ public class ScreenshotHelper { return mTaskId; } + + public int getUserId() { + return mUserId; + } + + public ComponentName getTopComponent() { + return mTopComponent; + } + @Override public int describeContents() { return 0; @@ -107,14 +123,16 @@ public class ScreenshotHelper { dest.writeInt(mSource); dest.writeBoolean(mHasStatusBar); dest.writeBoolean(mHasNavBar); - if (mBitmap == null) { + if (mBitmapBundle == null) { dest.writeInt(0); } else { dest.writeInt(1); - dest.writeParcelable(mBitmap, 0); + dest.writeBundle(mBitmapBundle); dest.writeParcelable(mBoundsInScreen, 0); dest.writeParcelable(mInsets, 0); dest.writeInt(mTaskId); + dest.writeInt(mUserId); + dest.writeParcelable(mTopComponent, 0); } } @@ -234,19 +252,22 @@ public class ScreenshotHelper { /** * Request that provided image be handled as if it was a screenshot. * - * @param screenshot The bitmap to treat as the screen shot. + * @param screenshotBundle Bundle containing the buffer and color space of the screenshot. * @param boundsInScreen The bounds in screen coordinates that the bitmap orginated from. * @param insets The insets that the image was shown with, inside the screenbounds. * @param taskId The taskId of the task that the screen shot was taken of. + * @param userId The userId of user running the task provided in taskId. + * @param topComponent The component name of the top component running in the task. * @param handler A handler used in case the screenshot times out * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the * screenshot was taken. */ - public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen, - @NonNull Insets insets, int taskId, int source, + public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen, + @NonNull Insets insets, int taskId, int userId, ComponentName topComponent, int source, @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) { ScreenshotRequest screenshotRequest = - new ScreenshotRequest(source, screenshot, boundsInScreen, insets, taskId); + new ScreenshotRequest(source, screenshotBundle, boundsInScreen, insets, taskId, + userId, topComponent); takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest, completionConsumer); } diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 688e00bc5a29..0d2dbefb9cd7 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -480,23 +480,25 @@ public class ConversationLayout extends FrameLayout // (This usually happens for most 1:1 conversations) conversationText = messagingGroup.getSenderName(); } - Icon avatarIcon = messagingGroup.getAvatarIcon(); - if (avatarIcon == null) { - avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor); + if (mConversationIcon == null) { + Icon avatarIcon = messagingGroup.getAvatarIcon(); + if (avatarIcon == null) { + avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor); + } + mConversationIcon = avatarIcon; } - mConversationIcon = avatarIcon; - mConversationIconView.setImageIcon(mConversationIcon); break; } } } else { - if (mLargeIcon != null) { + if (mConversationIcon == null && mLargeIcon != null) { mConversationIcon = mLargeIcon; + } + if (mConversationIcon != null) { mConversationIconView.setVisibility(VISIBLE); mConversationFacePile.setVisibility(GONE); - mConversationIconView.setImageIcon(mLargeIcon); + mConversationIconView.setImageIcon(mConversationIcon); } else { - mConversationIcon = null; mConversationIconView.setVisibility(GONE); // This will also inflate it! mConversationFacePile.setVisibility(VISIBLE); @@ -709,6 +711,11 @@ public class ConversationLayout extends FrameLayout mLargeIcon = largeIcon; } + @RemotableViewMethod + public void setConversationIcon(Icon conversationIcon) { + mConversationIcon = conversationIcon; + } + /** * Sets the conversation title of this conversation. * @@ -1216,7 +1223,6 @@ public class ConversationLayout extends FrameLayout mExpandButtonContainer.setVisibility(VISIBLE); mExpandButtonInnerContainer.setOnClickListener(onClickListener); } else { - // TODO: handle content paddings to end of layout mExpandButtonContainer.setVisibility(GONE); } updateContentEndPaddings(); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index fc2005a31696..3d8cae8e74d0 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -526,8 +526,16 @@ static void UnsetChldSignalHandler() { // Calls POSIX setgroups() using the int[] object as an argument. // A nullptr argument is tolerated. -static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) { +static void SetGids(JNIEnv* env, jintArray managed_gids, jboolean is_child_zygote, + fail_fn_t fail_fn) { if (managed_gids == nullptr) { + if (is_child_zygote) { + // For child zygotes like webview and app zygote, we want to clear out + // any supplemental groups the parent zygote had. + if (setgroups(0, NULL) == -1) { + fail_fn(CREATE_ERROR("Failed to remove supplementary groups for child zygote")); + } + } return; } @@ -1665,7 +1673,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } - SetGids(env, gids, fail_fn); + SetGids(env, gids, is_child_zygote, fail_fn); SetRLimits(env, rlimits, fail_fn); if (need_pre_initialize_native_bridge) { diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto index 23fcf6ebc2cc..787074ba494e 100644 --- a/core/proto/android/server/connectivity/data_stall_event.proto +++ b/core/proto/android/server/connectivity/data_stall_event.proto @@ -32,6 +32,7 @@ enum ApBand { AP_BAND_UNKNOWN = 0; AP_BAND_2GHZ = 1; AP_BAND_5GHZ = 2; + AP_BAND_6GHZ = 3; } // Refer to definition in TelephonyManager.java. diff --git a/core/proto/android/stats/mediametrics/mediametrics.proto b/core/proto/android/stats/mediametrics/mediametrics.proto index e1af9622adb3..9f0ff591a506 100644 --- a/core/proto/android/stats/mediametrics/mediametrics.proto +++ b/core/proto/android/stats/mediametrics/mediametrics.proto @@ -131,7 +131,7 @@ message AudioTrackData { * Logged from: * frameworks/av/media/libstagefright/MediaCodec.cpp * frameworks/av/services/mediaanalytics/statsd_codec.cpp - * Next Tag: 21 + * Next Tag: 26 */ message CodecData { optional string codec = 1; @@ -156,6 +156,9 @@ message CodecData { optional int64 latency_unknown = 20; optional int32 queue_input_buffer_error = 21; optional int32 queue_secure_input_buffer_error = 22; + optional string bitrate_mode = 23; + optional int32 bitrate = 24; + optional int64 lifetime_millis = 25; } /** diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index fd8460f9c478..464a47002b17 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3656,7 +3656,8 @@ <p>The package installer v2 APIs are still a work in progress and we're currently validating they work in all scenarios. <p>Not for use by third-party applications. - TODO(b/152310230): remove this permission once the APIs are confirmed to be sufficient. + TODO(b/152310230): use this permission to protect only Incremental installations + once the APIs are confirmed to be sufficient. @hide --> <permission android:name="com.android.permission.USE_INSTALLER_V2" diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 65872f43f46e..afeceb775524 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1309,7 +1309,7 @@ <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Εντοπίστηκε αναλογικό αξεσουάρ ήχου"</string> <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Η συνδεδεμένη συσκευή δεν είναι συμβατή με αυτό το τηλέφωνο. Πατήστε για να μάθετε περισσότερα."</string> <string name="adb_active_notification_title" msgid="408390247354560331">"Συνδέθηκε ο εντοπισμός σφαλμάτων USB"</string> - <string name="adb_active_notification_message" msgid="5617264033476778211">"Απενεργοποιήστε τον εντοπισμό/διόρθ. σφαλμάτων USB"</string> + <string name="adb_active_notification_message" msgid="5617264033476778211">"Πατήστε για απενεργοποίηση εντοπισμού/διόρθ. σφαλμάτων USB"</string> <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Επιλογή για απενεργοποίηση του εντοπισμού σφαλμάτων USB."</string> <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Συνδέθηκε ο ασύρματος εντοπισμός σφαλμάτων"</string> <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Πατήστε, για να απενεργοποιήσετε τον ασύρματο εντοπισμό σφαλμάτων"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 36380b822864..31abd8235bf4 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -202,10 +202,8 @@ <string name="printing_disabled_by" msgid="3517499806528864633">"Printing disabled by <xliff:g id="OWNER_APP">%s</xliff:g>."</string> <string name="personal_apps_suspension_title" msgid="7561416677884286600">"Turn on your work profile"</string> <string name="personal_apps_suspension_text" msgid="6115455688932935597">"Your personal apps are blocked until you turn on your work profile"</string> - <!-- no translation found for personal_apps_suspension_soon_text (8123898693479590) --> - <skip /> - <!-- no translation found for personal_apps_suspended_turn_profile_on (2758012869627513689) --> - <skip /> + <string name="personal_apps_suspension_soon_text" msgid="8123898693479590">"Personal apps will be blocked on <xliff:g id="DATE">%1$s</xliff:g> at <xliff:g id="TIME">%2$s</xliff:g>. Your IT admin doesn’t allow your work profile to stay off for more than <xliff:g id="NUMBER">%3$d</xliff:g> days."</string> + <string name="personal_apps_suspended_turn_profile_on" msgid="2758012869627513689">"Turn on"</string> <string name="me" msgid="6207584824693813140">"Me"</string> <string name="power_dialog" product="tablet" msgid="8333207765671417261">"Tablet options"</string> <string name="power_dialog" product="tv" msgid="7792839006640933763">"Android TV options"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 91a8d0585709..bb06173918d7 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -2032,7 +2032,7 @@ <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> ficheiros</item> <item quantity="one"><xliff:g id="FILE_NAME_0">%s</xliff:g> + <xliff:g id="COUNT_1">%d</xliff:g> ficheiro</item> </plurals> - <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"Non hai persoas recomendadas coas que compartir contido"</string> + <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"Non hai recomendacións de persoas coas que compartir contido"</string> <string name="chooser_all_apps_button_label" msgid="3230427756238666328">"Lista de aplicacións"</string> <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Esta aplicación non está autorizada a realizar gravacións, pero pode capturar audio a través deste dispositivo USB."</string> <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Inicio"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 3fe076695642..5161fdc65446 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -1230,7 +1230,7 @@ <string name="volume_unknown" msgid="4041914008166576293">"កម្រិតសំឡេង"</string> <string name="volume_icon_description_bluetooth" msgid="7540388479345558400">"កម្រិតសំឡេងប៊្លូធូស"</string> <string name="volume_icon_description_ringer" msgid="2187800636867423459">"កម្រិតសំឡេងរោទ៍"</string> - <string name="volume_icon_description_incall" msgid="4491255105381227919">"កម្រិតសំឡេងហៅ"</string> + <string name="volume_icon_description_incall" msgid="4491255105381227919">"កម្រិតសំឡេងហៅទូរសព្ទ"</string> <string name="volume_icon_description_media" msgid="4997633254078171233">"កម្រិតសំឡេងមេឌៀ"</string> <string name="volume_icon_description_notification" msgid="579091344110747279">"កម្រិតសំឡេងការជូនដំណឹង"</string> <string name="ringtone_default" msgid="9118299121288174597">"សំឡេងរោទ៍លំនាំដើម"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index bc896d61a43b..25aa3b5eaf94 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -1308,7 +1308,7 @@ <string name="usb_power_notification_message" msgid="7284765627437897702">"ಸಂಪರ್ಕಗೊಂಡಿರುವ ಸಾಧನವನ್ನು ಚಾರ್ಜ್ ಮಾಡಲಾಗುತ್ತಿದೆ. ಹೆಚ್ಚಿನ ಆಯ್ಕೆಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string> <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"ಅನ್ಲಾಗ್ ಆಡಿಯೋ ಪರಿಕರ ಪತ್ತೆಯಾಗಿದೆ"</string> <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"ಲಗತ್ತಿಸಲಾದ ಸಾಧನವು ಈ ಫೋನಿನೊಂದಿಗೆ ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. ಇನ್ನಷ್ಟು ತಿಳಿಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string> - <string name="adb_active_notification_title" msgid="408390247354560331">"USB ಡೀಬಗಿಂಗ್ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string> + <string name="adb_active_notification_title" msgid="408390247354560331">"USB ಡೀಬಗ್ ಮಾಡುವಿಕೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string> <string name="adb_active_notification_message" msgid="5617264033476778211">"USB ಡೀಬಗ್ ಮಾಡುವಿಕೆಯನ್ನು ಆಫ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"USB ಡೀಬಗ್ ಮಾಡುವಿಕೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ಆಯ್ಕೆ ಮಾಡಿ."</string> <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"ವೈರ್ಲೆಸ್ ಡೀಬಗ್ ಮಾಡುವಿಕೆಯನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string> @@ -2032,7 +2032,7 @@ <item quantity="one"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> ಫೈಲ್ಗಳು</item> <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> ಫೈಲ್ಗಳು</item> </plurals> - <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"ಹಂಚಿಕೊಳ್ಳಲು, ಯಾವುದೇ ಶಿಫಾರಸು ಮಾಡಲಾದ ಜನರಿಲ್ಲ"</string> + <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"ಹಂಚಿಕೊಳ್ಳಲು ಶಿಫಾರಸು ಮಾಡಲಾದವರು ಯಾರೂ ಇಲ್ಲ"</string> <string name="chooser_all_apps_button_label" msgid="3230427756238666328">"ಆ್ಯಪ್ಗಳ ಪಟ್ಟಿ"</string> <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"ಈ ಆ್ಯಪ್ಗೆ ರೆಕಾರ್ಡ್ ಅನುಮತಿಯನ್ನು ನೀಡಲಾಗಿಲ್ಲ, ಆದರೆ ಈ USB ಸಾಧನದ ಮೂಲಕ ಆಡಿಯೊವನ್ನು ಸೆರೆಹಿಡಿಯಬಲ್ಲದು."</string> <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"ಹೋಮ್"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index a7e6f99ba8fd..a5a0f9e587ec 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -2032,7 +2032,7 @@ <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> फाइल</item> <item quantity="one"><xliff:g id="FILE_NAME_0">%s</xliff:g> + <xliff:g id="COUNT_1">%d</xliff:g> फाइल</item> </plurals> - <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"शेअर करण्यासाठी कोणतीही शिफारस केलेले लोक नाहीत"</string> + <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"शेअर करण्यासाठी शिफारस केलेल्या कोणत्याही व्यक्ती नाहीत"</string> <string name="chooser_all_apps_button_label" msgid="3230427756238666328">"अॅप्स सूची"</string> <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"या अॅपला रेकॉर्ड करण्याची परवानगी दिली गेली नाही पण हे USB डिव्हाइस वापरून ऑडिओ कॅप्चर केला जाऊ शकतो."</string> <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"होम"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index cc8137cc244c..64302dab7ce9 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1912,7 +1912,7 @@ <string name="demo_starting_message" msgid="6577581216125805905">"Demo starten…"</string> <string name="demo_restarting_message" msgid="1160053183701746766">"Apparaat resetten…"</string> <string name="suspended_widget_accessibility" msgid="6331451091851326101">"<xliff:g id="LABEL">%1$s</xliff:g> uitgeschakeld"</string> - <string name="conference_call" msgid="5731633152336490471">"Telefonische vergadering"</string> + <string name="conference_call" msgid="5731633152336490471">"Conferencecall"</string> <string name="tooltip_popup_title" msgid="7863719020269945722">"Knopinfo"</string> <string name="app_category_game" msgid="4534216074910244790">"Games"</string> <string name="app_category_audio" msgid="8296029904794676222">"Muziek en audio"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index f803f02e92bc..4becdaa16ef3 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -60,7 +60,7 @@ <string name="ClirMmi" msgid="4702929460236547156">"ଆଉଟଗୋଇଙ୍ଗ୍ କଲର୍ ଆଇଡି"</string> <string name="ColpMmi" msgid="4736462893284419302">"ସଂଯୁକ୍ତ ଲାଇନ୍ ID"</string> <string name="ColrMmi" msgid="5889782479745764278">"ସଂଯୁକ୍ତ ଲାଇନ୍ ID କଟକଣା"</string> - <string name="CfMmi" msgid="8390012691099787178">"କଲ୍ ଫରୱାର୍ଡିଙ୍ଗ"</string> + <string name="CfMmi" msgid="8390012691099787178">"କଲ୍ ଫରୱାର୍ଡିଂ"</string> <string name="CwMmi" msgid="3164609577675404761">"କଲ୍ ଅପେକ୍ଷାରତ"</string> <string name="BaMmi" msgid="7205614070543372167">"କଲ୍ ବ୍ୟାରିଙ୍ଗ୍"</string> <string name="PwdMmi" msgid="3360991257288638281">"ପାସ୍ୱର୍ଡ ପରିବର୍ତ୍ତନ"</string> @@ -88,7 +88,7 @@ <string name="EmergencyCallWarningTitle" msgid="1615688002899152860">"ଜରୁରୀକାଳୀନ କଲ୍ ଉପଲବ୍ଧ ନାହିଁ"</string> <string name="EmergencyCallWarningSummary" msgid="1194185880092805497">"ୱାଇ-ଫାଇ ସାହାଯ୍ୟରେ ଜରୁରୀକାଳୀନ କଲ୍ କରାଯାଇପାରିବ ନାହିଁ"</string> <string name="notification_channel_network_alert" msgid="4788053066033851841">"ଆଲର୍ଟ"</string> - <string name="notification_channel_call_forward" msgid="8230490317314272406">"କଲ୍ ଫରୱାର୍ଡିଙ୍ଗ"</string> + <string name="notification_channel_call_forward" msgid="8230490317314272406">"କଲ୍ ଫରୱାର୍ଡିଂ"</string> <string name="notification_channel_emergency_callback" msgid="54074839059123159">"ଜରୁରୀକାଳୀନ କଲବ୍ୟାକ୍ ମୋଡ୍"</string> <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ମୋବାଇଲ୍ ଡାଟା ଷ୍ଟାଟସ୍"</string> <string name="notification_channel_sms" msgid="1243384981025535724">"SMS ମେସେଜ୍"</string> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index c962256e477c..f42b248f4670 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1830,30 +1830,13 @@ <!-- @hide no longer used, kept to preserve padding --> <attr name="allowAutoRevokePermissionsExemption" format="boolean" /> - <!-- Declare the app's tolerance to having its permissions automatically revoked when unused for an extended - period of time --> + <!-- No longer used. Declaring this does nothing --> <attr name="autoRevokePermissions"> - <!-- App supports re-requesting its permissions if revoked. - Revoking app's permissions doesn't cause user experience issues, aside from a repeated permission request. - - Permissions may be automatically revoked from an app if unused. The app must check and possibly request the - necessary permission on each permission-gated call--> + <!-- No longer used --> <enum name="allowed" value="0" /> - <!-- App may experience degraded functionality when its previously-granted permissions are revoked. - Revoking app's permissions may cause user experience issues, that are not critical to the user. - - Apps with this declaration can choose to request an exemption from auto revoke from user by starting - an activity with {@code Intent.ACTION_AUTO_REVOKE_PERMISSIONS}. --> + <!-- No longer used --> <enum name="discouraged" value="1" /> - <!-- User may experience severe consequences if this app's permissions are revoked unexpectedly. - - E.g. app may fail to do a user-critical background job that may likely impact user's - safety/security/device accessibility. - - This declaration may cause an additional review when publishing your app. - - Apps with this declaration are exempt from auto revoke by default, though the user has the final say - in both revoking the permissions as well as the app's auto revoke exemption status. --> + <!-- No longer used --> <enum name="disallowed" value="2" /> </attr> </declare-styleable> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index dc21e878d132..a1c2450be153 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1613,15 +1613,15 @@ <string name="face_error_no_space">Can\u2019t store new face data. Delete an old one first.</string> <!-- Generic error message shown when the face operation (e.g. enrollment or authentication) is canceled. Generally not shown to the user. [CHAR LIMIT=50] --> <string name="face_error_canceled">Face operation canceled.</string> - <!-- Generic error message shown when the face unlock operation is canceled due to user input. Generally not shown to the user [CHAR LIMIT=54] --> + <!-- Generic error message shown when the face unlock operation is canceled due to user input. Generally not shown to the user [CHAR LIMIT=68] --> <string name="face_error_user_canceled">Face unlock canceled by user.</string> <!-- Generic error message shown when the face operation fails because too many attempts have been made. [CHAR LIMIT=50] --> <string name="face_error_lockout">Too many attempts. Try again later.</string> - <!-- Generic error message shown when the face operation fails because strong authentication is required. [CHAR LIMIT=71] --> + <!-- Generic error message shown when the face operation fails because strong authentication is required. [CHAR LIMIT=77] --> <string name="face_error_lockout_permanent">Too many attempts. Face unlock disabled.</string> <!-- Generic error message shown when the face hardware can't recognize the face. [CHAR LIMIT=50] --> <string name="face_error_unable_to_process">Can\u2019t verify face. Try again.</string> - <!-- Generic error message shown when the user has no enrolled face. [CHAR LIMIT=52] --> + <!-- Generic error message shown when the user has no enrolled face. [CHAR LIMIT=59] --> <string name="face_error_not_enrolled">You haven\u2019t set up face unlock.</string> <!-- Generic error message shown when the app requests face unlock on a device without a sensor. [CHAR LIMIT=61] --> <string name="face_error_hw_not_present">Face unlock is not supported on this device.</string> diff --git a/core/tests/PackageInstallerSessions/Android.bp b/core/tests/PackageInstallerSessions/Android.bp new file mode 100644 index 000000000000..e74f30ee10a4 --- /dev/null +++ b/core/tests/PackageInstallerSessions/Android.bp @@ -0,0 +1,42 @@ +// +// Copyright 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test { + name: "FrameworksCorePackageInstallerSessionsTests", + + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "androidx.test.rules", + "compatibility-device-util-axt", + "frameworks-base-testutils", + "platform-test-annotations", + "testng", + "truth-prebuilt", + ], + + libs: [ + "android.test.runner", + "android.test.base", + "framework", + "framework-res", + ], + + platform_apis: true, + sdk_version: "core_platform", + test_suites: ["device-tests"], +} diff --git a/core/tests/PackageInstallerSessions/AndroidManifest.xml b/core/tests/PackageInstallerSessions/AndroidManifest.xml new file mode 100644 index 000000000000..5b22d2b4f3e3 --- /dev/null +++ b/core/tests/PackageInstallerSessions/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> + +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.package_installer_sessions" + > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.coretests.package_installer_sessions"/> +</manifest> diff --git a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt new file mode 100644 index 000000000000..494c92a8aa3f --- /dev/null +++ b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm + +import android.content.Context +import android.content.pm.PackageInstaller.SessionParams +import android.platform.test.annotations.Presubmit +import androidx.test.InstrumentationRegistry +import androidx.test.filters.LargeTest +import com.android.compatibility.common.util.ShellIdentityUtils +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.testng.Assert.assertThrows +import kotlin.random.Random + +/** + * For verifying public [PackageInstaller] session APIs. This differs from + * [com.android.server.pm.PackageInstallerSessionTest] in services because that mocks the session, + * whereas this test uses the installer on device. + */ +@Presubmit +class PackageSessionTests { + + companion object { + /** + * Permissions marked "hardRestricted" or "softRestricted" in core/res/AndroidManifest.xml. + */ + private val RESTRICTED_PERMISSIONS = listOf( + "android.permission.SEND_SMS", + "android.permission.RECEIVE_SMS", + "android.permission.READ_SMS", + "android.permission.RECEIVE_WAP_PUSH", + "android.permission.RECEIVE_MMS", + "android.permission.READ_CELL_BROADCASTS", + "android.permission.ACCESS_BACKGROUND_LOCATION", + "android.permission.READ_CALL_LOG", + "android.permission.WRITE_CALL_LOG", + "android.permission.PROCESS_OUTGOING_CALLS" + ) + } + + private val context: Context = InstrumentationRegistry.getContext() + + private val installer = context.packageManager.packageInstaller + + @Before + @After + fun abandonAllSessions() { + installer.mySessions.asSequence() + .map { it.sessionId } + .forEach { + try { + installer.abandonSession(it) + } catch (ignored: Exception) { + // Querying for sessions checks by calling package name, but abandoning + // checks by UID, which won't match if this test failed to clean up + // on a previous install + run + uninstall, so ignore these failures. + } + } + } + + @Test + fun truncateAppLabel() { + val longLabel = invalidAppLabel() + val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply { + setAppLabel(longLabel) + } + + createSession(params) { + assertThat(installer.getSessionInfo(it)?.appLabel) + .isEqualTo(longLabel.take(PackageItemInfo.MAX_SAFE_LABEL_LENGTH)) + } + } + + @Test + fun removeInvalidAppPackageName() { + val longName = invalidPackageName() + val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply { + setAppPackageName(longName) + } + + createSession(params) { + assertThat(installer.getSessionInfo(it)?.appPackageName) + .isEqualTo(null) + } + } + + @Test + fun removeInvalidInstallerPackageName() { + val longName = invalidPackageName() + val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply { + setInstallerPackageName(longName) + } + + createSession(params) { + // If a custom installer name is dropped, it defaults to the caller + assertThat(installer.getSessionInfo(it)?.installerPackageName) + .isEqualTo(context.packageName) + } + } + + @Test + fun truncateWhitelistPermissions() { + val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply { + setWhitelistedRestrictedPermissions(invalidPermissions()) + } + + createSession(params) { + assertThat(installer.getSessionInfo(it)?.whitelistedRestrictedPermissions!!) + .containsExactlyElementsIn(RESTRICTED_PERMISSIONS) + } + } + + @LargeTest + @Test + fun allocateMaxSessionsWithPermission() { + ShellIdentityUtils.invokeWithShellPermissions { + repeat(1024) { createDummySession() } + assertThrows(IllegalStateException::class.java) { createDummySession() } + } + } + + @LargeTest + @Test + fun allocateMaxSessionsNoPermission() { + repeat(50) { createDummySession() } + assertThrows(IllegalStateException::class.java) { createDummySession() } + } + + private fun createDummySession() { + installer.createSession(SessionParams(SessionParams.MODE_FULL_INSTALL) + .apply { + setAppPackageName(invalidPackageName()) + setAppLabel(invalidAppLabel()) + setWhitelistedRestrictedPermissions(invalidPermissions()) + }) + } + + private fun invalidPackageName(maxLength: Int = SessionParams.MAX_PACKAGE_NAME_LENGTH): String { + return (0 until (maxLength + 10)) + .asSequence() + .mapIndexed { index, _ -> + // A package name needs at least one separator + if (index == 2) { + '.' + } else { + Random.nextInt('z' - 'a').toChar() + 'a'.toInt() + } + } + .joinToString(separator = "") + } + + private fun invalidAppLabel() = (0 until PackageItemInfo.MAX_SAFE_LABEL_LENGTH + 10) + .asSequence() + .map { Random.nextInt(Char.MAX_VALUE.toInt()).toChar() } + .joinToString(separator = "") + + private fun invalidPermissions() = RESTRICTED_PERMISSIONS.toMutableSet() + .apply { + // Add some invalid permission names + repeat(10) { add(invalidPackageName(300)) } + } + + private fun createSession(params: SessionParams, block: (Int) -> Unit = {}) { + val sessionId = installer.createSession(params) + try { + block(sessionId) + } finally { + installer.abandonSession(sessionId) + } + } +} diff --git a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java index a8ca6f048a11..b329e55b569f 100644 --- a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java +++ b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java @@ -126,6 +126,32 @@ public class AutofillIdTest { } @Test + public void testVirtual_Long_withoutSession() { + final AutofillId id = new AutofillId(new AutofillId(42), 108L, 666); + final AutofillId idWithoutSession = AutofillId.withoutSession(id); + assertThat(idWithoutSession.getViewId()).isEqualTo(42); + assertThat(idWithoutSession.isVirtualLong()).isTrue(); + assertThat(idWithoutSession.isVirtualInt()).isFalse(); + assertThat(idWithoutSession.isNonVirtual()).isFalse(); + assertThat(idWithoutSession.getVirtualChildLongId()).isEqualTo(108L); + assertThat(idWithoutSession.getVirtualChildIntId()).isEqualTo(View.NO_ID); + assertThat(idWithoutSession.getSessionId()).isEqualTo(NO_SESSION); + } + + @Test + public void testVirtual_Int_withoutSession() { + final AutofillId id = new AutofillId(42, 108); + final AutofillId idWithoutSession = AutofillId.withoutSession(id); + assertThat(idWithoutSession.getViewId()).isEqualTo(42); + assertThat(idWithoutSession.isVirtualLong()).isFalse(); + assertThat(idWithoutSession.isVirtualInt()).isTrue(); + assertThat(idWithoutSession.isNonVirtual()).isFalse(); + assertThat(idWithoutSession.getVirtualChildIntId()).isEqualTo(108); + assertThat(idWithoutSession.getVirtualChildLongId()).isEqualTo(View.NO_ID); + assertThat(idWithoutSession.getSessionId()).isEqualTo(NO_SESSION); + } + + @Test public void testSetResetSession() { final AutofillId id = new AutofillId(42); assertNonVirtual(id, 42, NO_SESSION); diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java index 7cd2f3b4c2ab..a4f206586625 100644 --- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java +++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java @@ -363,6 +363,16 @@ public class HdmiAudioSystemClientTest { public boolean isHdmiCecVolumeControlEnabled() { return true; } + + @Override + public void addHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) { + } + + @Override + public void removeHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) { + } } } diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java index fe33cd80f735..4b8173732b4d 100644 --- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java +++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java @@ -29,11 +29,12 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; -import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.view.WindowManager; @@ -91,8 +92,7 @@ public final class ScreenshotHelperTest { @Test public void testProvidedImageScreenshot() { mScreenshotHelper.provideScreenshot( - Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), new Rect(), - Insets.of(0, 0, 0, 0), 1, + new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""), WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null); } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index e00813ce2aaf..9b503eba5c68 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -378,6 +378,9 @@ applications that come with the platform <permission name="android.permission.SET_WALLPAPER" /> <permission name="android.permission.SET_WALLPAPER_COMPONENT" /> <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> + <!-- Permissions required for Incremental CTS tests --> + <permission name="com.android.permission.USE_INSTALLER_V2"/> + <permission name="android.permission.LOADER_USAGE_STATS"/> <!-- Permission required to test system only camera devices. --> <permission name="android.permission.SYSTEM_CAMERA" /> <!-- Permission required to test ExplicitHealthCheckServiceImpl. --> @@ -418,6 +421,8 @@ applications that come with the platform <permission name="android.permission.TV_INPUT_HARDWARE" /> <!-- Permission required for CTS test - PrivilegedLocationPermissionTest --> <permission name="android.permission.LOCATION_HARDWARE" /> + <!-- Permissions required for GTS test - GtsDialerAudioTestCases --> + <permission name="android.permission.CAPTURE_AUDIO_OUTPUT" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/data/keyboards/Vendor_045e_Product_0b12.kl b/data/keyboards/Vendor_045e_Product_0b12.kl new file mode 100644 index 000000000000..0b44c7434af2 --- /dev/null +++ b/data/keyboards/Vendor_045e_Product_0b12.kl @@ -0,0 +1,59 @@ +# 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. + +# +# XBox USB Controller +# + +key 304 BUTTON_A +key 305 BUTTON_B +key 307 BUTTON_X +key 308 BUTTON_Y +key 310 BUTTON_L1 +key 311 BUTTON_R1 + +key 317 BUTTON_THUMBL +key 318 BUTTON_THUMBR + +# Left and right stick. +# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd. +# This confuses applications that rely on the flat value because the joystick actually +# settles in a flat range of +/- 4096 or so. +axis 0x00 X flat 4096 +axis 0x01 Y flat 4096 +axis 0x03 Z flat 4096 +axis 0x04 RZ flat 4096 + +# Triggers. +axis 0x02 LTRIGGER +axis 0x05 RTRIGGER + +# Hat. +axis 0x10 HAT_X +axis 0x11 HAT_Y + +# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt + +# Two overlapping rectangles +key 314 BUTTON_SELECT + +# The branded "X" button in the center of the controller +key 316 BUTTON_MODE + +# Three parallel horizontal lines (hamburger menu) +key 315 BUTTON_START + +#Button below the "X" button +key 167 MEDIA_RECORD + diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java index c652628eb425..590def4d4ced 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.ActivityManager; import android.content.Context; import android.hardware.cas.V1_0.HidlCasPluginDescriptor; import android.hardware.cas.V1_0.ICas; @@ -43,6 +44,8 @@ import android.os.RemoteException; import android.util.Log; import android.util.Singleton; +import com.android.internal.util.FrameworkStatsLog; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -122,6 +125,7 @@ public final class MediaCas implements AutoCloseable { private String mTvInputServiceSessionId; private int mClientId; private int mCasSystemId; + private int mUserId; private TunerResourceManager mTunerResourceManager = null; private final Map<Session, Integer> mSessionMap = new HashMap<>(); @@ -673,6 +677,8 @@ public final class MediaCas implements AutoCloseable { */ public MediaCas(int CA_system_id) throws UnsupportedCasException { try { + mCasSystemId = CA_system_id; + mUserId = ActivityManager.getCurrentUser(); IMediaCasService service = getService(); android.hardware.cas.V1_2.IMediaCasService serviceV12 = android.hardware.cas.V1_2.IMediaCasService.castFrom(service); @@ -721,7 +727,6 @@ public final class MediaCas implements AutoCloseable { this(casSystemId); Objects.requireNonNull(context, "context must not be null"); - mCasSystemId = casSystemId; mTunerResourceManager = (TunerResourceManager) context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE); if (mTunerResourceManager != null) { @@ -925,10 +930,18 @@ public final class MediaCas implements AutoCloseable { mICas.openSession(cb); MediaCasException.throwExceptionIfNeeded(cb.mStatus); addSessionToResourceMap(cb.mSession, sessionResourceHandle); + Log.d(TAG, "Write Stats Log for succeed to Open Session."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, + FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED); return cb.mSession; } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } + Log.d(TAG, "Write Stats Log for fail to Open Session."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, + FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED); return null; } @@ -964,10 +977,18 @@ public final class MediaCas implements AutoCloseable { mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb); MediaCasException.throwExceptionIfNeeded(cb.mStatus); addSessionToResourceMap(cb.mSession, sessionResourceHandle); + Log.d(TAG, "Write Stats Log for succeed to Open Session."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, + FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED); return cb.mSession; } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } + Log.d(TAG, "Write Stats Log for fail to Open Session."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, + FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED); return null; } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 981bf7af9f25..05c6e3ad9392 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -137,7 +137,7 @@ public abstract class MediaRoute2ProviderService extends Service { private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false); private MediaRoute2ProviderServiceStub mStub; private IMediaRoute2ProviderServiceCallback mRemoteCallback; - private MediaRoute2ProviderInfo mProviderInfo; + private volatile MediaRoute2ProviderInfo mProviderInfo; @GuardedBy("mSessionLock") private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>(); @@ -167,8 +167,8 @@ public abstract class MediaRoute2ProviderService extends Service { /** * Called when a volume setting is requested on a route of the provider * - * @param requestId the id of this request - * @param routeId the id of the route + * @param requestId the ID of this request + * @param routeId the ID of the route * @param volume the target volume * @see MediaRoute2Info.Builder#setVolume(int) */ @@ -178,8 +178,8 @@ public abstract class MediaRoute2ProviderService extends Service { * Called when {@link MediaRouter2.RoutingController#setVolume(int)} is called on * a routing session of the provider * - * @param requestId the id of this request - * @param sessionId the id of the routing session + * @param requestId the ID of this request + * @param sessionId the ID of the routing session * @param volume the target volume * @see RoutingSessionInfo.Builder#setVolume(int) */ @@ -188,7 +188,7 @@ public abstract class MediaRoute2ProviderService extends Service { /** * Gets information of the session with the given id. * - * @param sessionId id of the session + * @param sessionId the ID of the session * @return information of the session with the given id. * null if the session is released or ID is not valid. */ @@ -218,7 +218,7 @@ public abstract class MediaRoute2ProviderService extends Service { * If this session is created without any creation request, use {@link #REQUEST_ID_NONE} * as the request ID. * - * @param requestId id of the previous request to create this session provided in + * @param requestId the ID of the previous request to create this session provided in * {@link #onCreateSession(long, String, String, Bundle)}. Can be * {@link #REQUEST_ID_NONE} if this session is created without any request. * @param sessionInfo information of the new session. @@ -237,18 +237,15 @@ public abstract class MediaRoute2ProviderService extends Service { return; } mSessionInfo.put(sessionInfo.getId(), sessionInfo); - } - if (mRemoteCallback == null) { - return; - } - try { - // TODO(b/157873487): Calling binder calls in multiple thread may cause timing issue. - // Consider to change implementations to avoid the problems. - // For example, post binder calls, always send all sessions at once, etc. - mRemoteCallback.notifySessionCreated(requestId, sessionInfo); - } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session created."); + if (mRemoteCallback == null) { + return; + } + try { + mRemoteCallback.notifySessionCreated(requestId, sessionInfo); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify session created."); + } } } @@ -267,22 +264,22 @@ public abstract class MediaRoute2ProviderService extends Service { Log.w(TAG, "Ignoring unknown session info."); return; } - } - if (mRemoteCallback == null) { - return; - } - try { - mRemoteCallback.notifySessionUpdated(sessionInfo); - } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session info changed."); + if (mRemoteCallback == null) { + return; + } + try { + mRemoteCallback.notifySessionUpdated(sessionInfo); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify session info changed."); + } } } /** * Notifies that the session is released. * - * @param sessionId id of the released session. + * @param sessionId the ID of the released session. * @see #onReleaseSession(long, String) */ public final void notifySessionReleased(@NonNull String sessionId) { @@ -292,20 +289,20 @@ public abstract class MediaRoute2ProviderService extends Service { RoutingSessionInfo sessionInfo; synchronized (mSessionLock) { sessionInfo = mSessionInfo.remove(sessionId); - } - if (sessionInfo == null) { - Log.w(TAG, "Ignoring unknown session info."); - return; - } + if (sessionInfo == null) { + Log.w(TAG, "Ignoring unknown session info."); + return; + } - if (mRemoteCallback == null) { - return; - } - try { - mRemoteCallback.notifySessionReleased(sessionInfo); - } catch (RemoteException ex) { - Log.w(TAG, "Failed to notify session info changed."); + if (mRemoteCallback == null) { + return; + } + try { + mRemoteCallback.notifySessionReleased(sessionInfo); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify session info changed."); + } } } @@ -348,9 +345,9 @@ public abstract class MediaRoute2ProviderService extends Service { * If you can't create the session or want to reject the request, call * {@link #notifyRequestFailed(long, int)} with the given {@code requestId}. * - * @param requestId the id of this request + * @param requestId the ID of this request * @param packageName the package name of the application that selected the route - * @param routeId the id of the route initially being connected + * @param routeId the ID of the route initially being connected * @param sessionHints an optional bundle of app-specific arguments sent by * {@link MediaRouter2}, or null if none. The contents of this bundle * may affect the result of session creation. @@ -372,8 +369,8 @@ public abstract class MediaRoute2ProviderService extends Service { * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger * this method to be called. * - * @param requestId the id of this request - * @param sessionId id of the session being released. + * @param requestId the ID of this request + * @param sessionId the ID of the session being released. * @see #notifySessionReleased(String) * @see #getSessionInfo(String) */ @@ -384,9 +381,9 @@ public abstract class MediaRoute2ProviderService extends Service { * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)} * to update session info. * - * @param requestId the id of this request - * @param sessionId id of the session - * @param routeId id of the route + * @param requestId the ID of this request + * @param sessionId the ID of the session + * @param routeId the ID of the route */ public abstract void onSelectRoute(long requestId, @NonNull String sessionId, @NonNull String routeId); @@ -396,9 +393,9 @@ public abstract class MediaRoute2ProviderService extends Service { * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)} * to update session info. * - * @param requestId the id of this request - * @param sessionId id of the session - * @param routeId id of the route + * @param requestId the ID of this request + * @param sessionId the ID of the session + * @param routeId the ID of the route */ public abstract void onDeselectRoute(long requestId, @NonNull String sessionId, @NonNull String routeId); @@ -408,9 +405,9 @@ public abstract class MediaRoute2ProviderService extends Service { * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)} * to update session info. * - * @param requestId the id of this request - * @param sessionId id of the session - * @param routeId id of the route + * @param requestId the ID of this request + * @param sessionId the ID of the session + * @param routeId the ID of the route */ public abstract void onTransferToRoute(long requestId, @NonNull String sessionId, @NonNull String routeId); @@ -475,13 +472,39 @@ public abstract class MediaRoute2ProviderService extends Service { final class MediaRoute2ProviderServiceStub extends IMediaRoute2ProviderService.Stub { MediaRoute2ProviderServiceStub() { } - boolean checkCallerisSystem() { + private boolean checkCallerIsSystem() { return Binder.getCallingUid() == Process.SYSTEM_UID; } + private boolean checkSessionIdIsValid(String sessionId, String description) { + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, description + ": Ignoring empty sessionId from system service."); + return false; + } + if (getSessionInfo(sessionId) == null) { + Log.w(TAG, description + ": Ignoring unknown session from system service. " + + "sessionId=" + sessionId); + return false; + } + return true; + } + + private boolean checkRouteIdIsValid(String routeId, String description) { + if (TextUtils.isEmpty(routeId)) { + Log.w(TAG, description + ": Ignoring empty routeId from system service."); + return false; + } + if (mProviderInfo == null || mProviderInfo.getRoute(routeId) == null) { + Log.w(TAG, description + ": Ignoring unknown route from system service. " + + "routeId=" + routeId); + return false; + } + return true; + } + @Override public void setCallback(IMediaRoute2ProviderServiceCallback callback) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::setCallback, @@ -490,7 +513,7 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } mHandler.sendMessage(obtainMessage( @@ -500,7 +523,10 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void setRouteVolume(long requestId, String routeId, int volume) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { + return; + } + if (!checkRouteIdIsValid(routeId, "setRouteVolume")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume, @@ -510,7 +536,10 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void requestCreateSession(long requestId, String packageName, String routeId, @Nullable Bundle requestCreateSession) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { + return; + } + if (!checkRouteIdIsValid(routeId, "requestCreateSession")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession, @@ -518,14 +547,13 @@ public abstract class MediaRoute2ProviderService extends Service { requestCreateSession)); } - //TODO(b/157873546): Ignore requests with unknown session ID. -> For all similar commands. @Override public void selectRoute(long requestId, String sessionId, String routeId) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } - if (TextUtils.isEmpty(sessionId)) { - Log.w(TAG, "selectRoute: Ignoring empty sessionId from system service."); + if (!checkSessionIdIsValid(sessionId, "selectRoute") + || !checkRouteIdIsValid(routeId, "selectRoute")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute, @@ -534,11 +562,11 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void deselectRoute(long requestId, String sessionId, String routeId) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } - if (TextUtils.isEmpty(sessionId)) { - Log.w(TAG, "deselectRoute: Ignoring empty sessionId from system service."); + if (!checkSessionIdIsValid(sessionId, "deselectRoute") + || !checkRouteIdIsValid(routeId, "deselectRoute")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute, @@ -547,11 +575,11 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void transferToRoute(long requestId, String sessionId, String routeId) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } - if (TextUtils.isEmpty(sessionId)) { - Log.w(TAG, "transferToRoute: Ignoring empty sessionId from system service."); + if (!checkSessionIdIsValid(sessionId, "transferToRoute") + || !checkRouteIdIsValid(routeId, "transferToRoute")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute, @@ -560,7 +588,10 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void setSessionVolume(long requestId, String sessionId, int volume) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { + return; + } + if (!checkSessionIdIsValid(sessionId, "setSessionVolume")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume, @@ -569,11 +600,10 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void releaseSession(long requestId, String sessionId) { - if (!checkCallerisSystem()) { + if (!checkCallerIsSystem()) { return; } - if (TextUtils.isEmpty(sessionId)) { - Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service."); + if (!checkSessionIdIsValid(sessionId, "releaseSession")) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession, diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java index c4d27eca02f8..e719b2a04720 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -90,6 +90,17 @@ public final class MediaProjectionManager { * projection is stopped. This allows for user controls to be displayed on top of the screen * being captured. * + * <p> + * Apps targeting SDK version {@link android.os.Build.VERSION_CODES#Q} or later should specify + * the foreground service type using the attribute {@link android.R.attr#foregroundServiceType} + * in the service element of the app's manifest file. + * The {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION} attribute + * should be specified. + * </p> + * + * @see <a href="https://developer.android.com/preview/privacy/foreground-service-types"> + * Foregroud Service Types</a> + * * @param resultCode The result code from {@link android.app.Activity#onActivityResult(int, * int, android.content.Intent)} * @param resultData The resulting data from {@link android.app.Activity#onActivityResult(int, diff --git a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java index 68071b0b0fe3..bb00bb3b8d56 100644 --- a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java +++ b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java @@ -20,12 +20,16 @@ import android.annotation.BytesLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.app.ActivityManager; import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.Tuner; import android.media.tv.tuner.Tuner.Result; import android.media.tv.tuner.TunerUtils; import android.media.tv.tuner.filter.Filter; import android.os.ParcelFileDescriptor; +import android.util.Log; + +import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -72,9 +76,15 @@ public class DvrPlayback implements AutoCloseable { */ public static final int PLAYBACK_STATUS_FULL = Constants.PlaybackStatus.SPACE_FULL; + private static final String TAG = "TvTunerPlayback"; + private long mNativeContext; private OnPlaybackStatusChangedListener mListener; private Executor mExecutor; + private int mUserId; + private static int sInstantId = 0; + private int mSegmentId = 0; + private int mUnderflow; private native int nativeAttachFilter(Filter filter); private native int nativeDetachFilter(Filter filter); @@ -88,6 +98,9 @@ public class DvrPlayback implements AutoCloseable { private native long nativeRead(byte[] bytes, long offset, long size); private DvrPlayback() { + mUserId = ActivityManager.getCurrentUser(); + mSegmentId = (sInstantId & 0x0000ffff) << 16; + sInstantId++; } /** @hide */ @@ -98,6 +111,9 @@ public class DvrPlayback implements AutoCloseable { } private void onPlaybackStatusChanged(int status) { + if (status == PLAYBACK_STATUS_EMPTY) { + mUnderflow++; + } if (mExecutor != null && mListener != null) { mExecutor.execute(() -> mListener.onPlaybackStatusChanged(status)); } @@ -154,6 +170,13 @@ public class DvrPlayback implements AutoCloseable { */ @Result public int start() { + mSegmentId = (mSegmentId & 0xffff0000) | (((mSegmentId & 0x0000ffff) + 1) & 0x0000ffff); + mUnderflow = 0; + Log.d(TAG, "Write Stats Log for Playback."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__PLAYBACK, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0); return nativeStartDvr(); } @@ -167,6 +190,11 @@ public class DvrPlayback implements AutoCloseable { */ @Result public int stop() { + Log.d(TAG, "Write Stats Log for Playback."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__PLAYBACK, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mUnderflow); return nativeStopDvr(); } diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java index 198bd0f4e78e..887116725961 100644 --- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java +++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java @@ -19,14 +19,19 @@ package android.media.tv.tuner.dvr; import android.annotation.BytesLong; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.app.ActivityManager; import android.media.tv.tuner.Tuner; import android.media.tv.tuner.Tuner.Result; import android.media.tv.tuner.TunerUtils; import android.media.tv.tuner.filter.Filter; import android.os.ParcelFileDescriptor; +import android.util.Log; + +import com.android.internal.util.FrameworkStatsLog; import java.util.concurrent.Executor; + /** * Digital Video Record (DVR) recorder class which provides record control on Demux's output buffer. * @@ -34,9 +39,14 @@ import java.util.concurrent.Executor; */ @SystemApi public class DvrRecorder implements AutoCloseable { + private static final String TAG = "TvTunerRecord"; private long mNativeContext; private OnRecordStatusChangedListener mListener; private Executor mExecutor; + private int mUserId; + private static int sInstantId = 0; + private int mSegmentId = 0; + private int mOverflow; private native int nativeAttachFilter(Filter filter); private native int nativeDetachFilter(Filter filter); @@ -50,6 +60,9 @@ public class DvrRecorder implements AutoCloseable { private native long nativeWrite(byte[] bytes, long offset, long size); private DvrRecorder() { + mUserId = ActivityManager.getCurrentUser(); + mSegmentId = (sInstantId & 0x0000ffff) << 16; + sInstantId++; } /** @hide */ @@ -60,6 +73,9 @@ public class DvrRecorder implements AutoCloseable { } private void onRecordStatusChanged(int status) { + if (status == Filter.STATUS_OVERFLOW) { + mOverflow++; + } if (mExecutor != null && mListener != null) { mExecutor.execute(() -> mListener.onRecordStatusChanged(status)); } @@ -112,6 +128,13 @@ public class DvrRecorder implements AutoCloseable { */ @Result public int start() { + mSegmentId = (mSegmentId & 0xffff0000) | (((mSegmentId & 0x0000ffff) + 1) & 0x0000ffff); + mOverflow = 0; + Log.d(TAG, "Write Stats Log for Record."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0); return nativeStartDvr(); } @@ -124,6 +147,11 @@ public class DvrRecorder implements AutoCloseable { */ @Result public int stop() { + Log.d(TAG, "Write Stats Log for Playback."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD, + FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mOverflow); return nativeStopDvr(); } diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp index 2a8a39a1fe1a..32b33a758535 100644 --- a/packages/CarSystemUI/Android.bp +++ b/packages/CarSystemUI/Android.bp @@ -32,6 +32,7 @@ android_library { "SystemUIPluginLib", "SystemUISharedLib", "SettingsLib", + "car-ui-lib", "android.car.userlib", "androidx.legacy_legacy-support-v4", "androidx.recyclerview_recyclerview", @@ -95,6 +96,7 @@ android_library { "androidx.slice_slice-builders", "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", + "car-ui-lib", "SystemUI-tags", "SystemUI-proto", "metrics-helper-lib", diff --git a/packages/CarSystemUI/res/drawable/nav_button_background.xml b/packages/CarSystemUI/res/drawable/nav_button_background.xml deleted file mode 100644 index 376347cdf4a9..000000000000 --- a/packages/CarSystemUI/res/drawable/nav_button_background.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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 - --> - -<ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/nav_bar_ripple_background_color"> - <item android:id="@android:id/mask"> - <shape android:shape="rectangle"> - <solid android:color="?android:colorAccent"/> - <corners android:radius="6dp"/> - </shape> - </item> -</ripple> diff --git a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml index a8c70989253e..94816f81a4c5 100644 --- a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml @@ -79,7 +79,7 @@ android:gravity="bottom" android:orientation="vertical"> - <com.android.keyguard.AlphaOptimizedImageButton + <com.android.systemui.statusbar.AlphaOptimizedImageView android:id="@+id/note" android:layout_height="wrap_content" android:layout_width="match_parent" diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml index 2a715d0c3494..93174983b116 100644 --- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml @@ -29,9 +29,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" - android:paddingStart="20dp" + android:gravity="center" + android:layoutDirection="ltr" android:paddingEnd="20dp" - android:gravity="center"> + android:paddingStart="20dp"> <com.android.systemui.car.navigationbar.CarNavigationButton android:id="@+id/home" @@ -135,9 +136,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" - android:paddingStart="@dimen/car_keyline_1" - android:paddingEnd="@dimen/car_keyline_1" android:gravity="center" + android:layoutDirection="ltr" + android:paddingEnd="@dimen/car_keyline_1" + android:paddingStart="@dimen/car_keyline_1" android:visibility="gone" /> diff --git a/packages/CarSystemUI/res/layout/car_navigation_button.xml b/packages/CarSystemUI/res/layout/car_navigation_button.xml index ca4e76ee104b..a8f115742023 100644 --- a/packages/CarSystemUI/res/layout/car_navigation_button.xml +++ b/packages/CarSystemUI/res/layout/car_navigation_button.xml @@ -27,7 +27,7 @@ android:animateLayoutChanges="true" android:orientation="vertical"> - <com.android.keyguard.AlphaOptimizedImageButton + <com.android.systemui.statusbar.AlphaOptimizedImageView android:id="@+id/car_nav_button_icon_image" android:layout_height="@dimen/car_navigation_button_icon_height" android:layout_width="match_parent" @@ -40,7 +40,7 @@ android:clickable="false" /> - <com.android.keyguard.AlphaOptimizedImageButton + <com.android.systemui.statusbar.AlphaOptimizedImageView android:id="@+id/car_nav_button_more_icon" android:layout_height="wrap_content" android:layout_width="match_parent" diff --git a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml index fd75570e759c..dc9583382921 100644 --- a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml @@ -82,7 +82,7 @@ android:gravity="bottom" android:orientation="vertical"> - <com.android.keyguard.AlphaOptimizedImageButton + <com.android.systemui.statusbar.AlphaOptimizedImageView android:id="@+id/note" android:layout_height="wrap_content" android:layout_width="match_parent" diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml index 60e0d7e430df..cdc29eec21cd 100644 --- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml @@ -27,7 +27,8 @@ <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1"> + android:layout_weight="1" + android:layoutDirection="ltr"> <FrameLayout android:id="@+id/left_hvac_container" diff --git a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml index caf1677234d0..1782d2536035 100644 --- a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml +++ b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml @@ -29,6 +29,15 @@ android:orientation="horizontal" app:layout_constraintGuide_begin="@dimen/headsup_scrim_height"/> + <!-- Include a FocusParkingView at the beginning or end. The rotary controller "parks" the + focus here when the user navigates to another window. This is also used to prevent + wrap-around which is why it must be first or last in Tab order. --> + <com.android.car.ui.FocusParkingView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + <View android:id="@+id/scrim" android:layout_width="match_parent" diff --git a/packages/CarSystemUI/res/values/styles.xml b/packages/CarSystemUI/res/values/styles.xml index 371bebdebc86..7fc69e6d5d8f 100644 --- a/packages/CarSystemUI/res/values/styles.xml +++ b/packages/CarSystemUI/res/values/styles.xml @@ -44,6 +44,6 @@ <style name="NavigationBarButton"> <item name="android:layout_height">96dp</item> <item name="android:layout_width">96dp</item> - <item name="android:background">@drawable/nav_button_background</item> + <item name="android:background">@*android:drawable/item_background_material</item> </style> </resources>
\ No newline at end of file diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java index ab61b443df97..2dad5f872e73 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java @@ -219,6 +219,14 @@ public class CarKeyguardViewController extends OverlayViewController implements } @Override + public void setOccluded(boolean occluded, boolean animate) { + getOverlayViewGlobalStateController().setOccluded(occluded); + if (!occluded) { + reset(/* hideBouncerWhenShowing= */ false); + } + } + + @Override public void onCancelClicked() { if (mBouncer == null) return; @@ -315,11 +323,6 @@ public class CarKeyguardViewController extends OverlayViewController implements } @Override - public void setOccluded(boolean occluded, boolean animate) { - // no-op - } - - @Override public boolean shouldDisableWindowAnimationsForUnlock() { return false; } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java index 20fc1bcd6013..0ced4021ce38 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java @@ -74,8 +74,10 @@ public class CarNavigationBarView extends LinearLayout { mDarkIconManager.setShouldLog(true); Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager); } - // needs to be clickable so that it will receive ACTION_MOVE events + // Needs to be clickable so that it will receive ACTION_MOVE events. setClickable(true); + // Needs to not be focusable so rotary won't highlight the entire nav bar. + setFocusable(false); } @Override diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java index 5e113d6366a1..e7e33a5439f9 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java @@ -32,8 +32,8 @@ import android.widget.ImageView; import android.widget.LinearLayout; import com.android.internal.annotations.VisibleForTesting; -import com.android.keyguard.AlphaOptimizedImageButton; import com.android.systemui.R; +import com.android.systemui.statusbar.AlphaOptimizedImageView; import java.net.URISyntaxException; @@ -53,8 +53,8 @@ public class CarNavigationButton extends LinearLayout { private static final String EXTRA_BUTTON_PACKAGES = "packages"; private Context mContext; - private AlphaOptimizedImageButton mIcon; - private AlphaOptimizedImageButton mMoreIcon; + private AlphaOptimizedImageView mIcon; + private AlphaOptimizedImageView mMoreIcon; private ImageView mUnseenIcon; private String mIntent; private String mLongIntent; diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java index 3b7b48a77186..d60bc418ece2 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java @@ -24,6 +24,7 @@ import android.view.ViewGroup; import androidx.annotation.LayoutRes; +import com.android.car.ui.FocusParkingView; import com.android.systemui.R; import javax.inject.Inject; @@ -146,6 +147,12 @@ public class NavigationBarViewFactory { CarNavigationBarView view = (CarNavigationBarView) View.inflate(mContext, barLayout, /* root= */ null); + + // Include a FocusParkingView at the end. The rotary controller "parks" the focus here when + // the user navigates to another window. This is also used to prevent wrap-around which is + // why it must be first or last in Tab order. + view.addView(new FocusParkingView(mContext)); + mCachedViewMap.put(type, view); return mCachedViewMap.get(type); } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java index aeb1d39599db..d4f720715a69 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java @@ -24,7 +24,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.widget.FrameLayout; import com.android.car.notification.R; import com.android.car.notification.headsup.CarHeadsUpNotificationContainer; @@ -44,7 +43,7 @@ public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotifica private final OverlayViewGlobalStateController mOverlayViewGlobalStateController; private final ViewGroup mWindow; - private final FrameLayout mHeadsUpContentFrame; + private final ViewGroup mHeadsUpContentFrame; @Inject CarHeadsUpNotificationSystemContainer(Context context, diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java index 30e26578bd73..3969f92c690a 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java @@ -138,4 +138,11 @@ public class OverlayViewController { protected boolean shouldShowNavigationBar() { return false; } + + /** + * Returns {@code true} if this view should be hidden during the occluded state. + */ + protected boolean shouldShowWhenOccluded() { + return false; + } } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java index 70260b0d4cef..8e9410964313 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java @@ -24,7 +24,9 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.car.navigationbar.CarNavigationBarController; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; @@ -47,11 +49,16 @@ public class OverlayViewGlobalStateController { private static final int UNKNOWN_Z_ORDER = -1; private final SystemUIOverlayWindowController mSystemUIOverlayWindowController; private final CarNavigationBarController mCarNavigationBarController; + + private boolean mIsOccluded; + @VisibleForTesting Map<OverlayViewController, Integer> mZOrderMap; @VisibleForTesting SortedMap<Integer, OverlayViewController> mZOrderVisibleSortedMap; @VisibleForTesting + Set<OverlayViewController> mViewsHiddenForOcclusion; + @VisibleForTesting OverlayViewController mHighestZOrder; @Inject @@ -63,6 +70,7 @@ public class OverlayViewGlobalStateController { mCarNavigationBarController = carNavigationBarController; mZOrderMap = new HashMap<>(); mZOrderVisibleSortedMap = new TreeMap<>(); + mViewsHiddenForOcclusion = new HashSet<>(); } /** @@ -91,6 +99,10 @@ public class OverlayViewGlobalStateController { */ public void showView(OverlayViewController viewController, @Nullable Runnable show) { debugLog(); + if (mIsOccluded && !viewController.shouldShowWhenOccluded()) { + mViewsHiddenForOcclusion.add(viewController); + return; + } if (mZOrderVisibleSortedMap.isEmpty()) { setWindowVisible(true); } @@ -147,6 +159,10 @@ public class OverlayViewGlobalStateController { */ public void hideView(OverlayViewController viewController, @Nullable Runnable hide) { debugLog(); + if (mIsOccluded && mViewsHiddenForOcclusion.contains(viewController)) { + mViewsHiddenForOcclusion.remove(viewController); + return; + } if (!viewController.isInflated()) { Log.d(TAG, "Content cannot be hidden since it isn't inflated: " + viewController.getClass().getName()); @@ -240,6 +256,43 @@ public class OverlayViewGlobalStateController { return mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowHUN(); } + /** + * Set the OverlayViewWindow to be in occluded or unoccluded state. When OverlayViewWindow is + * occluded, all views mounted to it that are not configured to be shown during occlusion will + * be hidden. + */ + public void setOccluded(boolean occluded) { + if (occluded) { + // Hide views before setting mIsOccluded to true so the regular hideView logic is used, + // not the one used during occlusion. + hideViewsForOcclusion(); + mIsOccluded = true; + } else { + mIsOccluded = false; + // show views after setting mIsOccluded to false so the regular showView logic is used, + // not the one used during occlusion. + showViewsHiddenForOcclusion(); + } + } + + private void hideViewsForOcclusion() { + HashSet<OverlayViewController> viewsCurrentlyShowing = new HashSet<>( + mZOrderVisibleSortedMap.values()); + viewsCurrentlyShowing.forEach(overlayController -> { + if (!overlayController.shouldShowWhenOccluded()) { + hideView(overlayController, overlayController::hideInternal); + mViewsHiddenForOcclusion.add(overlayController); + } + }); + } + + private void showViewsHiddenForOcclusion() { + mViewsHiddenForOcclusion.forEach(overlayViewController -> { + showView(overlayViewController, overlayViewController::showInternal); + }); + mViewsHiddenForOcclusion.clear(); + } + private void debugLog() { if (!DEBUG) { return; @@ -250,5 +303,8 @@ public class OverlayViewGlobalStateController { Log.d(TAG, "mZOrderVisibleSortedMap: " + mZOrderVisibleSortedMap); Log.d(TAG, "mZOrderMap.size(): " + mZOrderMap.size()); Log.d(TAG, "mZOrderMap: " + mZOrderMap); + Log.d(TAG, "mIsOccluded: " + mIsOccluded); + Log.d(TAG, "mViewsHiddenForOcclusion: " + mViewsHiddenForOcclusion); + Log.d(TAG, "mViewsHiddenForOcclusion.size(): " + mViewsHiddenForOcclusion.size()); } } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java index 38836d85e8d4..189e240169c3 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -169,6 +170,18 @@ public class CarKeyguardViewControllerTest extends SysuiTestCase { } @Test + public void setOccludedFalse_currentlyOccluded_bouncerReset() { + when(mBouncer.isSecure()).thenReturn(true); + mCarKeyguardViewController.show(/* options= */ null); + mCarKeyguardViewController.setOccluded(/* occluded= */ true, /* animate= */ false); + reset(mBouncer); + + mCarKeyguardViewController.setOccluded(/* occluded= */ false, /* animate= */ false); + + verify(mBouncer).show(/* resetSecuritySelection= */ true); + } + + @Test public void onCancelClicked_callsCancelClickedListener() { when(mBouncer.isSecure()).thenReturn(true); mCarKeyguardViewController.show(/* options= */ null); diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java index 54282d39998b..bcaa5e9a03ee 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java @@ -36,8 +36,8 @@ import android.widget.LinearLayout; import androidx.test.filters.SmallTest; -import com.android.keyguard.AlphaOptimizedImageButton; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.AlphaOptimizedImageView; import com.android.systemui.tests.R; import org.junit.Before; @@ -74,7 +74,7 @@ public class CarNavigationButtonTest extends SysuiTestCase { @Test public void onCreate_iconIsVisible() { - AlphaOptimizedImageButton icon = mDefaultButton.findViewById( + AlphaOptimizedImageView icon = mDefaultButton.findViewById( R.id.car_nav_button_icon_image); assertThat(icon.getDrawable()).isNotNull(); @@ -83,12 +83,12 @@ public class CarNavigationButtonTest extends SysuiTestCase { @Test public void onSelected_selectedIconDefined_togglesIcon() { mDefaultButton.setSelected(true); - Drawable selectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById( + Drawable selectedIconDrawable = ((AlphaOptimizedImageView) mDefaultButton.findViewById( R.id.car_nav_button_icon_image)).getDrawable(); mDefaultButton.setSelected(false); - Drawable unselectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById( + Drawable unselectedIconDrawable = ((AlphaOptimizedImageView) mDefaultButton.findViewById( R.id.car_nav_button_icon_image)).getDrawable(); assertThat(selectedIconDrawable).isNotEqualTo(unselectedIconDrawable); @@ -100,12 +100,12 @@ public class CarNavigationButtonTest extends SysuiTestCase { R.id.selected_icon_undefined); selectedIconUndefinedButton.setSelected(true); - Drawable selectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById( + Drawable selectedIconDrawable = ((AlphaOptimizedImageView) mDefaultButton.findViewById( R.id.car_nav_button_icon_image)).getDrawable(); selectedIconUndefinedButton.setSelected(false); - Drawable unselectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById( + Drawable unselectedIconDrawable = ((AlphaOptimizedImageView) mDefaultButton.findViewById( R.id.car_nav_button_icon_image)).getDrawable(); assertThat(selectedIconDrawable).isEqualTo(unselectedIconDrawable); @@ -150,7 +150,7 @@ public class CarNavigationButtonTest extends SysuiTestCase { @Test public void onSelected_doesNotShowMoreWhenSelected_doesNotShowMoreIcon() { mDefaultButton.setSelected(true); - AlphaOptimizedImageButton moreIcon = mDefaultButton.findViewById( + AlphaOptimizedImageView moreIcon = mDefaultButton.findViewById( R.id.car_nav_button_more_icon); assertThat(moreIcon.getVisibility()).isEqualTo(View.GONE); @@ -161,7 +161,7 @@ public class CarNavigationButtonTest extends SysuiTestCase { CarNavigationButton showMoreWhenSelected = mTestView.findViewById( R.id.not_highlightable_more_button); showMoreWhenSelected.setSelected(true); - AlphaOptimizedImageButton moreIcon = showMoreWhenSelected.findViewById( + AlphaOptimizedImageView moreIcon = showMoreWhenSelected.findViewById( R.id.car_nav_button_more_icon); assertThat(moreIcon.getVisibility()).isEqualTo(View.VISIBLE); @@ -173,7 +173,7 @@ public class CarNavigationButtonTest extends SysuiTestCase { R.id.highlightable_no_more_button); showMoreWhenSelected.setSelected(true); showMoreWhenSelected.setSelected(false); - AlphaOptimizedImageButton moreIcon = showMoreWhenSelected.findViewById( + AlphaOptimizedImageView moreIcon = showMoreWhenSelected.findViewById( R.id.car_nav_button_more_icon); assertThat(moreIcon.getVisibility()).isEqualTo(View.GONE); @@ -187,7 +187,7 @@ public class CarNavigationButtonTest extends SysuiTestCase { roleBasedButton.setSelected(false); roleBasedButton.setAppIcon(appIcon); - Drawable currentDrawable = ((AlphaOptimizedImageButton) roleBasedButton.findViewById( + Drawable currentDrawable = ((AlphaOptimizedImageView) roleBasedButton.findViewById( R.id.car_nav_button_icon_image)).getDrawable(); assertThat(currentDrawable).isEqualTo(appIcon); @@ -212,7 +212,7 @@ public class CarNavigationButtonTest extends SysuiTestCase { roleBasedButton.setSelected(true); roleBasedButton.setAppIcon(appIcon); - Drawable currentDrawable = ((AlphaOptimizedImageButton) roleBasedButton.findViewById( + Drawable currentDrawable = ((AlphaOptimizedImageView) roleBasedButton.findViewById( R.id.car_nav_button_icon_image)).getDrawable(); assertThat(currentDrawable).isEqualTo(appIcon); diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java index 9e6e616e3ccf..cba42e5a9be4 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java @@ -491,6 +491,81 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { } @Test + public void setOccludedTrue_viewToHideWhenOccludedVisible_viewHidden() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(false); + + mOverlayViewGlobalStateController.setOccluded(true); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue( + mOverlayViewController1)).isFalse(); + } + + @Test + public void setOccludedTrue_viewToNotHideWhenOccludedVisible_viewShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(true); + + mOverlayViewGlobalStateController.setOccluded(true); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue( + mOverlayViewController1)).isTrue(); + } + + @Test + public void hideViewAndThenSetOccludedTrue_viewHiddenForOcclusion_viewHiddenAfterOcclusion() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(false); + mOverlayViewGlobalStateController.setOccluded(true); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, /* runnable= */ null); + mOverlayViewGlobalStateController.setOccluded(false); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue( + mOverlayViewController1)).isFalse(); + } + + @Test + public void setOccludedTrueAndThenShowView_viewToNotHideForOcclusion_viewShown() { + setupOverlayViewController1(); + when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(true); + + mOverlayViewGlobalStateController.setOccluded(true); + setOverlayViewControllerAsShowing(mOverlayViewController1); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue( + mOverlayViewController1)).isTrue(); + } + + @Test + public void setOccludedTrueAndThenShowView_viewToHideForOcclusion_viewHidden() { + setupOverlayViewController1(); + when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(false); + + mOverlayViewGlobalStateController.setOccluded(true); + setOverlayViewControllerAsShowing(mOverlayViewController1); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue( + mOverlayViewController1)).isFalse(); + } + + @Test + public void setOccludedFalse_viewShownAfterSetOccludedTrue_viewToHideForOcclusion_viewShown() { + setupOverlayViewController1(); + when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(false); + mOverlayViewGlobalStateController.setOccluded(true); + setOverlayViewControllerAsShowing(mOverlayViewController1); + + mOverlayViewGlobalStateController.setOccluded(false); + + assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue( + mOverlayViewController1)).isTrue(); + } + + @Test public void inflateView_notInflated_inflates() { when(mOverlayViewController2.isInflated()).thenReturn(false); diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index d039c9f646df..1b5062efa23e 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -140,8 +140,8 @@ <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Адкрытая сетка"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Бяспечная сетка"</string> <string name="process_kernel_label" msgid="950292573930336765">"АС Android"</string> - <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Выдаленыя прыкладанні"</string> - <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Выдаленыя прыкладанні і карыстальнiкi"</string> + <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Выдаленыя праграмы"</string> + <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Выдаленыя праграмы і карыстальнiкi"</string> <string name="data_usage_ota" msgid="7984667793701597001">"Абнаўленні сістэмы"</string> <string name="tether_settings_title_usb" msgid="3728686573430917722">"USB-мадэм"</string> <string name="tether_settings_title_wifi" msgid="4803402057533895526">"Партатыўны хот-спот"</string> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index 65f456e20e4e..d003ef0b9c71 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -449,7 +449,7 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täislaadimiseni"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string> - <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Kiiresti laadimine"</string> + <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Kiirlaadimine"</string> <string name="battery_info_status_charging_slow" msgid="3190803837168962319">"Aeglaselt laadimine"</string> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei lae"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Vooluvõrgus, praegu ei saa laadida"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java index a6202956efa5..38eeda245616 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -19,10 +19,13 @@ package com.android.settingslib.applications; import android.app.Application; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.hardware.usb.IUsbManager; +import android.net.Uri; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; @@ -44,6 +47,15 @@ public class AppUtils { */ private static InstantAppDataProvider sInstantAppDataProvider = null; + private static final Intent sBrowserIntent; + + static { + sBrowserIntent = new Intent() + .setAction(Intent.ACTION_VIEW) + .addCategory(Intent.CATEGORY_BROWSABLE) + .setData(Uri.parse("http:")); + } + public static CharSequence getLaunchByDefaultSummary(ApplicationsState.AppEntry appEntry, IUsbManager usbManager, PackageManager pm, Context context) { String packageName = appEntry.info.packageName; @@ -153,4 +165,22 @@ public class AppUtils { return com.android.settingslib.utils.applications.AppUtils.getAppContentDescription(context, packageName, userId); } + + /** + * Returns a boolean indicating whether a given package is a browser app. + * + * An app is a "browser" if it has an activity resolution that wound up + * marked with the 'handleAllWebDataURI' flag. + */ + public static boolean isBrowserApp(Context context, String packageName, int userId) { + sBrowserIntent.setPackage(packageName); + final List<ResolveInfo> list = context.getPackageManager().queryIntentActivitiesAsUser( + sBrowserIntent, PackageManager.MATCH_ALL, userId); + for (ResolveInfo info : list) { + if (info.activityInfo != null && info.handleAllWebDataURI) { + return true; + } + } + return false; + } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index fa87b62bd73a..eb7ad72eb1ba 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -316,7 +316,6 @@ public class SettingsBackupTest { Settings.Global.KERNEL_CPU_THREAD_READER, Settings.Global.LANG_ID_UPDATE_CONTENT_URL, Settings.Global.LANG_ID_UPDATE_METADATA_URL, - Settings.Global.LAST_ACTIVE_USER_ID, Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS, Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index ae8e8e82241d..44349c5f5ab4 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -306,6 +306,9 @@ <!-- Permission needed for CTS test - PrivilegedLocationPermissionTest --> <uses-permission android:name="android.permission.LOCATION_HARDWARE" /> + <!-- Permissions required for GTS test - GtsDialerAudioTestCases --> + <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/res/drawable/dismiss_circle_background.xml b/packages/SystemUI/res/drawable/dismiss_circle_background.xml index e311c520d3d6..7809c8398c2d 100644 --- a/packages/SystemUI/res/drawable/dismiss_circle_background.xml +++ b/packages/SystemUI/res/drawable/dismiss_circle_background.xml @@ -21,8 +21,8 @@ <stroke android:width="1dp" - android:color="#66FFFFFF" /> + android:color="#AAFFFFFF" /> - <solid android:color="#B3000000" /> + <solid android:color="#77000000" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/floating_dismiss_gradient.xml b/packages/SystemUI/res/drawable/floating_dismiss_gradient.xml new file mode 100644 index 000000000000..8f7fb1011cf4 --- /dev/null +++ b/packages/SystemUI/res/drawable/floating_dismiss_gradient.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <gradient + android:angle="270" + android:startColor="#00000000" + android:endColor="#77000000" + android:type="linear" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/floating_dismiss_gradient_transition.xml b/packages/SystemUI/res/drawable/floating_dismiss_gradient_transition.xml new file mode 100644 index 000000000000..6a0695e817c7 --- /dev/null +++ b/packages/SystemUI/res/drawable/floating_dismiss_gradient_transition.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<transition xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@color/transparent" /> + <item android:drawable="@drawable/floating_dismiss_gradient" /> +</transition>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/dismiss_target_x.xml b/packages/SystemUI/res/drawable/ic_music_note.xml index 3672efffe139..30959a870a02 100644 --- a/packages/SystemUI/res/drawable/dismiss_target_x.xml +++ b/packages/SystemUI/res/drawable/ic_music_note.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2020 The Android Open Source Project ~ @@ -14,15 +13,12 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<!-- 'X' icon. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24.0dp" - android:height="24.0dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> <path - android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z" - android:fillColor="#FFFFFFFF" - android:strokeColor="#FF000000"/> -</vector>
\ No newline at end of file + android:fillColor="#FF000000" + android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z"/> +</vector> diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml index b34822291815..803b0c61e6da 100644 --- a/packages/SystemUI/res/layout/partial_conversation_info.xml +++ b/packages/SystemUI/res/layout/partial_conversation_info.xml @@ -52,39 +52,20 @@ android:gravity="center_vertical" android:layout_alignEnd="@id/conversation_icon" android:layout_toEndOf="@id/conversation_icon"> - <LinearLayout + <TextView + android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="start" - android:orientation="horizontal"> - <TextView - android:id="@+id/name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@style/TextAppearance.NotificationImportanceChannel"/> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - style="@style/TextAppearance.NotificationImportanceHeader" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:text="@*android:string/notification_header_divider_symbol" /> - <TextView - android:id="@+id/parent_channel_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@style/TextAppearance.NotificationImportanceChannel"/> - - </LinearLayout> + android:ellipsize="end" + android:textDirection="locale" + style="@style/TextAppearance.NotificationImportanceChannel"/> <TextView - android:id="@+id/pkg_name" + android:id="@+id/parent_channel_name" android:layout_width="match_parent" android:layout_height="wrap_content" - style="@style/TextAppearance.NotificationImportanceChannelGroup" android:ellipsize="end" android:textDirection="locale" - android:maxLines="1"/> + style="@style/TextAppearance.NotificationImportanceChannel"/> <TextView android:id="@+id/group_name" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml index c27b3a9b3bf4..bf2eac3c8ff3 100644 --- a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml +++ b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml @@ -38,157 +38,67 @@ android:background="@drawable/rounded_bg_full" > - <!-- We have a known number of rows that can be shown; just design them all here --> - <LinearLayout - android:id="@+id/show_at_top_tip" + <ImageView + android:id="@+id/conversation_icon" + android:layout_width="@dimen/notification_guts_conversation_icon_size" + android:layout_height="@dimen/notification_guts_conversation_icon_size" + android:layout_gravity="center_horizontal" /> + + <TextView + android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="8dp" - android:paddingBottom="8dp" - android:paddingStart="4dp" - android:paddingEnd="4dp" - android:orientation="horizontal" - > - <ImageView - android:id="@+id/bell_icon" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_gravity="center_vertical" - android:src="@drawable/ic_notifications_alert" - android:tint="?android:attr/colorControlNormal" /> - - <TextView - android:id="@+id/show_at_top_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingStart="16dp" - android:paddingEnd="16dp" - android:gravity="center_vertical|start" - android:textSize="15sp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/priority_onboarding_show_at_top_text" - style="@style/TextAppearance.NotificationInfo" - /> - - </LinearLayout> - - <LinearLayout - android:id="@+id/show_avatar_tip" + android:gravity="center_horizontal" + android:layout_marginTop="16dp" + android:text="@string/priority_onboarding_title" + style="@style/TextAppearance.NotificationImportanceChannel" + /> + + <View + android:id="@+id/divider" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - android:paddingBottom="8dp" - android:paddingStart="4dp" - android:paddingEnd="4dp" - android:orientation="horizontal" - > - <ImageView - android:id="@+id/avatar_icon" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_gravity="center_vertical" - android:src="@drawable/ic_person" - android:tint="?android:attr/colorControlNormal" /> - - <TextView - android:id="@+id/avatar_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingStart="16dp" - android:paddingEnd="16dp" - android:gravity="center_vertical|start" - android:textSize="15sp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/priority_onboarding_show_avatar_text" - style="@style/TextAppearance.NotificationInfo" - /> + android:layout_height="0.5dp" + android:layout_marginTop="20dp" + android:layout_marginBottom="20dp" + android:background="@color/material_grey_300" /> - </LinearLayout> - - <!-- These rows show optionally --> - - <LinearLayout - android:id="@+id/floating_bubble_tip" + <TextView android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="8dp" - android:paddingBottom="8dp" - android:paddingStart="4dp" - android:paddingEnd="4dp" - android:orientation="horizontal" - > - - <ImageView - android:id="@+id/bubble_icon" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_gravity="center_vertical" - android:src="@drawable/ic_create_bubble" - android:tint="?android:attr/colorControlNormal" /> + android:gravity="start" + android:text="@string/priority_onboarding_behavior" + style="@style/TextAppearance.NotificationImportanceChannelGroup" + /> - <TextView - android:id="@+id/bubble_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingStart="16dp" - android:paddingEnd="16dp" - android:gravity="center_vertical|start" - android:textSize="15sp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/priority_onboarding_appear_as_bubble_text" - style="@style/TextAppearance.NotificationInfo" - /> - - </LinearLayout> - - <LinearLayout - android:id="@+id/ignore_dnd_tip" + <TextView + android:id="@+id/behaviors" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="8dp" - android:paddingBottom="8dp" - android:paddingStart="4dp" - android:paddingEnd="4dp" - android:orientation="horizontal" - > - - <ImageView - android:id="@+id/dnd_icon" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_gravity="center_vertical" - android:src="@drawable/moon" - android:tint="?android:attr/colorControlNormal" /> - - <TextView - android:id="@+id/dnd_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingStart="16dp" - android:paddingEnd="16dp" - android:gravity="center_vertical|start" - android:textSize="15sp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/priority_onboarding_ignores_dnd_text" - style="@style/TextAppearance.NotificationInfo" - /> - - </LinearLayout> + android:gravity="start" + android:layout_marginTop="8dp" + style="@style/TextAppearance.NotificationImportanceChannelGroup" + /> <!-- Bottom button container --> <RelativeLayout android:id="@+id/button_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="4dp" - android:paddingEnd="4dp" + android:layout_marginTop="32dp" android:orientation="horizontal" > <TextView + android:id="@+id/settings_button" + android:text="@string/priority_onboarding_settings_button_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:gravity="start|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="125dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView android:id="@+id/done_button" android:text="@string/priority_onboarding_done_button_title" android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml index 5b7e7e7d59a3..44f52efd175e 100644 --- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml +++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml @@ -21,8 +21,6 @@ android:layout_height="wrap_content" android:paddingTop="@dimen/qs_header_top_padding" android:paddingBottom="@dimen/qs_header_bottom_padding" - android:paddingStart="@dimen/status_bar_padding_start" - android:paddingEnd="@dimen/status_bar_padding_end" android:layout_below="@id/quick_status_bar_system_icons" android:clipChildren="false" android:clipToPadding="false" diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml index 28672241b1b2..12127f529054 100644 --- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml +++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml @@ -18,8 +18,6 @@ android:layout_height="wrap_content" android:layout_width="match_parent" android:layout_gravity="center_vertical" - android:paddingLeft="16dp" - android:paddingRight="16dp" style="@style/BrightnessDialogContainer"> <com.android.systemui.settings.ToggleSliderView diff --git a/packages/SystemUI/res/layout/quick_settings_footer.xml b/packages/SystemUI/res/layout/quick_settings_footer.xml index 846c5386dd06..15f398aa52e6 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer.xml @@ -23,7 +23,7 @@ android:paddingStart="@dimen/qs_footer_padding_start" android:paddingEnd="@dimen/qs_footer_padding_end" android:gravity="center_vertical" - android:background="?android:attr/colorPrimary" > + android:background="@android:color/transparent"> <TextView android:id="@+id/footer_text" @@ -32,7 +32,7 @@ android:gravity="start" android:layout_weight="1" android:textAppearance="@style/TextAppearance.QS.TileLabel" - android:textColor="?android:attr/textColorSecondary"/> + style="@style/qs_security_footer"/> <ImageView android:id="@+id/footer_icon" @@ -40,6 +40,6 @@ android:layout_height="@dimen/qs_footer_icon_size" android:contentDescription="@null" android:src="@drawable/ic_info_outline" - android:tint="?android:attr/textColorSecondary"/> + style="@style/qs_security_footer"/> </LinearLayout> diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml index 683e86728e91..e6ef9b4b902c 100644 --- a/packages/SystemUI/res/layout/quick_settings_header_info.xml +++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml @@ -19,8 +19,6 @@ android:layout_width="match_parent" android:layout_height="@dimen/qs_header_tooltip_height" android:layout_below="@id/quick_status_bar_system_icons" - android:paddingStart="@dimen/status_bar_padding_start" - android:paddingEnd="@dimen/status_bar_padding_end" android:visibility="invisible" android:theme="@style/QSHeaderTheme"> diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index 9a7c344baf20..abeb33111c40 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -33,6 +33,7 @@ android:paddingStart="0dp" android:elevation="4dp" > + <!-- The clock --> <include layout="@layout/quick_status_bar_header_system_icons" /> <!-- Status icons within the panel itself (and not in the top-most status bar) --> diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml index b27d096c12b5..be86e5f5abc5 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml @@ -25,8 +25,6 @@ android:gravity="center" android:orientation="horizontal" android:clickable="true" - android:paddingStart="@dimen/status_bar_padding_start" - android:paddingEnd="@dimen/status_bar_padding_end" android:paddingTop="@dimen/status_bar_padding_top" > <com.android.systemui.statusbar.policy.Clock diff --git a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml index af6f9bb827f6..0c4d5a26f95b 100644 --- a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml +++ b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml @@ -16,21 +16,20 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" + android:layout_width="250dp" android:layout_height="48dp" android:orientation="vertical" - android:padding="10dp" - android:layout_weight="1"> + android:padding="13dp"> <TextView android:id="@+id/screen_recording_dialog_source_text" - android:layout_width="match_parent" + android:layout_width="250dp" android:layout_height="match_parent" android:layout_gravity="center_vertical" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorPrimary"/> <TextView android:id="@+id/screen_recording_dialog_source_description" - android:layout_width="wrap_content" + android:layout_width="250dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:textColor="?android:attr/textColorSecondary"/> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 4fdeb6fa4a92..50261e1b2139 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -29,4 +29,9 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> </style> + <style name="qs_security_footer" parent="@style/qs_theme"> + <item name="android:textColor">#B3FFFFFF</item> <!-- 70% white --> + <item name="android:tint">#FFFFFFFF</item> + </style> + </resources> diff --git a/packages/SystemUI/res/values-sw320dp/dimens.xml b/packages/SystemUI/res/values-sw320dp/dimens.xml deleted file mode 100644 index c110113e91f4..000000000000 --- a/packages/SystemUI/res/values-sw320dp/dimens.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2019 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> -<resources> - <!-- Global actions grid --> - <dimen name="global_actions_grid_vertical_padding">3dp</dimen> - <dimen name="global_actions_grid_horizontal_padding">3dp</dimen> - - <dimen name="global_actions_grid_item_side_margin">5dp</dimen> - <dimen name="global_actions_grid_item_vertical_margin">4dp</dimen> - <dimen name="global_actions_grid_item_width">64dp</dimen> - <dimen name="global_actions_grid_item_height">64dp</dimen> - - <dimen name="global_actions_grid_item_icon_width">20dp</dimen> - <dimen name="global_actions_grid_item_icon_height">20dp</dimen> - <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen> - <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen> - <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen> - - <!-- Home Controls --> - <dimen name="global_actions_side_margin">10dp</dimen> -</resources> - diff --git a/packages/SystemUI/res/values-sw372dp/dimens.xml b/packages/SystemUI/res/values-sw372dp/dimens.xml index e64662ec7bea..d3fff0c059f9 100644 --- a/packages/SystemUI/res/values-sw372dp/dimens.xml +++ b/packages/SystemUI/res/values-sw372dp/dimens.xml @@ -17,5 +17,4 @@ --> <resources> <dimen name="nav_content_padding">8dp</dimen> - <dimen name="qs_header_tile_margin_horizontal">13dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a7d176443c55..73d8e9a0d8a7 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -224,6 +224,7 @@ <dimen name="notification_guts_conversation_icon_size">56dp</dimen> <dimen name="notification_guts_conversation_action_height">56dp</dimen> <dimen name="notification_guts_conversation_action_text_padding_start">32dp</dimen> + <dimen name="conversation_onboarding_bullet_gap_width">6dp</dimen> <!-- The height of the header in inline settings --> <dimen name="notification_guts_header_height">24dp</dimen> @@ -482,7 +483,8 @@ <dimen name="pull_span_min">25dp</dimen> <dimen name="qs_tile_height">106dp</dimen> - <dimen name="qs_tile_layout_margin_side">6dp</dimen> + <!--notification_side_paddings + notification_content_margin_start - (qs_quick_tile_size - qs_tile_background_size) / 2 --> + <dimen name="qs_tile_layout_margin_side">18dp</dimen> <dimen name="qs_tile_margin_horizontal">18dp</dimen> <dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen> <dimen name="qs_tile_margin_vertical">24dp</dimen> @@ -498,7 +500,6 @@ <dimen name="qs_quick_tile_size">48dp</dimen> <dimen name="qs_quick_tile_padding">12dp</dimen> <dimen name="qs_header_gear_translation">16dp</dimen> - <dimen name="qs_header_tile_margin_horizontal">4dp</dimen> <dimen name="qs_header_tile_margin_bottom">18dp</dimen> <dimen name="qs_page_indicator_width">16dp</dimen> <dimen name="qs_page_indicator_height">8dp</dimen> @@ -973,7 +974,9 @@ <dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen> <!-- The height of the gradient indicating the dismiss edge when moving a PIP. --> - <dimen name="floating_dismiss_gradient_height">176dp</dimen> + <dimen name="floating_dismiss_gradient_height">250dp</dimen> + + <dimen name="floating_dismiss_bottom_margin">50dp</dimen> <!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. --> <dimen name="pip_dismiss_text_bottom_margin">24dp</dimen> @@ -1031,8 +1034,23 @@ <dimen name="global_actions_grid_container_shadow_offset">20dp</dimen> <dimen name="global_actions_grid_container_negative_shadow_offset">-20dp</dimen> + <!-- Global actions grid --> + <dimen name="global_actions_grid_vertical_padding">3dp</dimen> + <dimen name="global_actions_grid_horizontal_padding">3dp</dimen> + + <dimen name="global_actions_grid_item_side_margin">5dp</dimen> + <dimen name="global_actions_grid_item_vertical_margin">4dp</dimen> + <dimen name="global_actions_grid_item_width">64dp</dimen> + <dimen name="global_actions_grid_item_height">64dp</dimen> + + <dimen name="global_actions_grid_item_icon_width">20dp</dimen> + <dimen name="global_actions_grid_item_icon_height">20dp</dimen> + <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen> + <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen> + <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen> + <!-- Margins at the left and right of the power menu and home controls widgets. --> - <dimen name="global_actions_side_margin">16dp</dimen> + <dimen name="global_actions_side_margin">10dp</dimen> <!-- Amount to shift the layout when exiting/entering for controls activities --> <dimen name="global_actions_controls_y_translation">20dp</dimen> @@ -1057,9 +1075,8 @@ <dimen name="edge_margin">8dp</dimen> <!-- The absolute side margins of quick settings --> - <dimen name="quick_settings_side_margins">16dp</dimen> <dimen name="quick_settings_expanded_bottom_margin">16dp</dimen> - <dimen name="quick_settings_media_extra_bottom_margin">4dp</dimen> + <dimen name="quick_settings_media_extra_bottom_margin">6dp</dimen> <dimen name="rounded_corner_content_padding">0dp</dimen> <dimen name="nav_content_padding">0dp</dimen> <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 97a0d032c4a7..0314fc89d33a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2667,6 +2667,11 @@ <string name="inattentive_sleep_warning_title">Standby</string> <!-- Priority conversation onboarding screen --> + <!-- title of priority onboarding [CHAR LIMIT=75] --> + <string name="priority_onboarding_title">Conversation set to priority</string> + <!-- Text explaining that the following actions are the behaviors of priority conversations. + E.g. priority conversations will show at the top of the conversation section [CHAR LIMIT=75] --> + <string name="priority_onboarding_behavior">Priority conversations will:</string> <!-- Text explaining that priority conversations show at the top of the conversation section [CHAR LIMIT=75] --> <string name="priority_onboarding_show_at_top_text">Show at top of conversation section</string> <!-- Text explaining that priority conversations show an avatar on the lock screen [CHAR LIMIT=75] --> @@ -2677,6 +2682,8 @@ <string name="priority_onboarding_ignores_dnd_text">Interrupt Do Not Disturb</string> <!-- Title for the affirmative button [CHAR LIMIT=50] --> <string name="priority_onboarding_done_button_title">Got it</string> + <!-- Title for the settings button button [CHAR LIMIT=50] --> + <string name="priority_onboarding_settings_button_title">Settings</string> <!-- Window Magnification strings --> <!-- Title for Magnification Overlay Window [CHAR LIMIT=NONE] --> @@ -2774,6 +2781,8 @@ <!-- Close the controls associated with a specific media session [CHAR_LIMIT=NONE] --> <string name="controls_media_close_session">Close this media session</string> + <!-- Label for button to resume media playback [CHAR_LIMIT=NONE] --> + <string name="controls_media_resume">Resume</string> <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] --> <string name="controls_error_timeout">Inactive, check app</string> @@ -2781,7 +2790,13 @@ a retry will be attempted [CHAR LIMIT=30] --> <string name="controls_error_retryable">Error, retrying\u2026</string> <!-- Error message indicating that the control is no longer available in the application [CHAR LIMIT=30] --> - <string name="controls_error_removed">Device removed</string> + <string name="controls_error_removed">Not found</string> + <!-- Title for dialog indicating that the control is no longer available in the application [CHAR LIMIT=30] --> + <string name="controls_error_removed_title">Control is unavailable</string> + <!-- Message body for dialog indicating that the control is no longer available in the application [CHAR LIMIT=NONE] --> + <string name="controls_error_removed_message">Couldn\u2019t access <xliff:g id="device" example="Backdoor lock">%1$s</xliff:g>. Check the <xliff:g id="application" example="Google Home">%2$s</xliff:g> app to make sure the control is still available and that the app settings haven\u2019t changed.</string> + <!-- Text for button to open the corresponding application [CHAR_LIMIT=20] --> + <string name="controls_open_app">Open app</string> <!-- Error message indicating that an unspecified error occurred while getting the status [CHAR LIMIT=30] --> <string name="controls_error_generic">Can\u2019t load status</string> <!-- Error message indicating that a control action failed [CHAR_LIMIT=30] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index ed36bdbe1e7e..39f78bf46028 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -387,6 +387,11 @@ <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item> </style> + <style name="qs_security_footer" parent="@style/qs_theme"> + <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:tint">?android:attr/textColorSecondary</item> + </style> + <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault.Light"> <item name="android:colorAccent">@color/remote_input_accent</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 35ad422c56b8..655008b7745a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -23,10 +23,11 @@ import android.os.Bundle; import android.view.MotionEvent; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; +import com.android.systemui.shared.recents.model.Task; /** * Temporary callbacks into SystemUI. - * Next id = 26 + * Next id = 27 */ interface ISystemUiProxy { @@ -122,6 +123,9 @@ interface ISystemUiProxy { /** * Handle the provided image as if it was a screenshot. + * + * Deprecated, use handleImageBundleAsScreenshot with image bundle and UserTask + * @deprecated */ void handleImageAsScreenshot(in Bitmap screenImage, in Rect locationInScreen, in Insets visibleInsets, int taskId) = 21; @@ -146,4 +150,10 @@ interface ISystemUiProxy { * @param rotation indicates which Surface.Rotation the gesture was started in */ void onQuickSwitchToNewTask(int rotation) = 25; + + /** + * Handle the provided image as if it was a screenshot. + */ + void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen, + in Insets visibleInsets, in Task.TaskKey task) = 26; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.aidl new file mode 100644 index 000000000000..e7cad2acc645 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.aidl @@ -0,0 +1,19 @@ +/* + * 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.shared.recents.model; + +parcelable Task.TaskKey;
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index dcb134ec933e..186379af4b1d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -26,6 +26,8 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; import android.view.ViewDebug; import com.android.systemui.shared.recents.utilities.Utilities; @@ -52,8 +54,10 @@ public class Task { void onTaskWindowingModeChanged(); } - /* The Task Key represents the unique primary key for the task */ - public static class TaskKey { + /** + * The Task Key represents the unique primary key for the task + */ + public static class TaskKey implements Parcelable { @ViewDebug.ExportedProperty(category="recents") public final int id; @ViewDebug.ExportedProperty(category="recents") @@ -157,6 +161,48 @@ public class Task { private void updateHashCode() { mHashCode = Objects.hash(id, windowingMode, userId); } + + public static final Parcelable.Creator<TaskKey> CREATOR = + new Parcelable.Creator<TaskKey>() { + @Override + public TaskKey createFromParcel(Parcel source) { + return TaskKey.readFromParcel(source); + } + + @Override + public TaskKey[] newArray(int size) { + return new TaskKey[size]; + } + }; + + @Override + public final void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(id); + parcel.writeInt(windowingMode); + parcel.writeTypedObject(baseIntent, flags); + parcel.writeInt(userId); + parcel.writeLong(lastActiveTime); + parcel.writeInt(displayId); + parcel.writeTypedObject(sourceComponent, flags); + } + + private static TaskKey readFromParcel(Parcel parcel) { + int id = parcel.readInt(); + int windowingMode = parcel.readInt(); + Intent baseIntent = parcel.readTypedObject(Intent.CREATOR); + int userId = parcel.readInt(); + long lastActiveTime = parcel.readLong(); + int displayId = parcel.readInt(); + ComponentName sourceComponent = parcel.readTypedObject(ComponentName.CREATOR); + + return new TaskKey(id, windowingMode, baseIntent, sourceComponent, userId, + lastActiveTime, displayId); + } + + @Override + public int describeContents() { + return 0; + } } @ViewDebug.ExportedProperty(deepExport=true, prefix="key_") diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java new file mode 100644 index 000000000000..b79fcbd43972 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/BitmapUtil.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.recents.utilities; + +import android.graphics.Bitmap; +import android.graphics.ColorSpace; +import android.graphics.ParcelableColorSpace; +import android.hardware.HardwareBuffer; +import android.os.Bundle; + +import java.util.Objects; + +/** + * Utils for working with Bitmaps. + */ +public final class BitmapUtil { + private static final String KEY_BUFFER = "bitmap_util_buffer"; + private static final String KEY_COLOR_SPACE = "bitmap_util_color_space"; + + private BitmapUtil(){ } + + /** + * Creates a Bundle that represents the given Bitmap. + * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid + * copies when passing across processes, only pass to processes you trust. + * + * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the + * returned Bundle should be treated as a standalone object. + * + * @param bitmap to convert to bundle + * @return a Bundle representing the bitmap, should only be parsed by + * {@link #bundleToHardwareBitmap(Bundle)} + */ + public static Bundle hardwareBitmapToBundle(Bitmap bitmap) { + if (bitmap.getConfig() != Bitmap.Config.HARDWARE) { + throw new IllegalArgumentException( + "Passed bitmap must have hardware config, found: " + bitmap.getConfig()); + } + + // Bitmap assumes SRGB for null color space + ParcelableColorSpace colorSpace = + bitmap.getColorSpace() == null + ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)) + : new ParcelableColorSpace(bitmap.getColorSpace()); + + Bundle bundle = new Bundle(); + bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer()); + bundle.putParcelable(KEY_COLOR_SPACE, colorSpace); + + return bundle; + } + + /** + * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .} + * + * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing this + * Bitmap on to any other source. + * + * @param bundle containing the bitmap + * @return a hardware Bitmap + */ + public static Bitmap bundleToHardwareBitmap(Bundle bundle) { + if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) { + throw new IllegalArgumentException("Bundle does not contain a hardware bitmap"); + } + + HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER); + ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE); + + return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer), colorSpace); + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java index dd5cc7c9bbd4..796aaeefb62f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java @@ -114,4 +114,7 @@ public abstract class TaskStackChangeListener { /** @see ITaskStackListener#onRecentTaskListFrozenChanged(boolean) */ public void onRecentTaskListFrozenChanged(boolean frozen) { } + + /** @see ITaskStackListener#onActivityRotation()*/ + public void onActivityRotation() { } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java index a76a901c5c81..13f7993f57d4 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -237,6 +237,11 @@ public class TaskStackChangeListeners extends TaskStackListener { mHandler.obtainMessage(H.ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget(); } + @Override + public void onActivityRotation() { + mHandler.obtainMessage(H.ON_ACTIVITY_ROTATION).sendToTarget(); + } + private final class H extends Handler { private static final int ON_TASK_STACK_CHANGED = 1; private static final int ON_TASK_SNAPSHOT_CHANGED = 2; @@ -260,6 +265,7 @@ public class TaskStackChangeListeners extends TaskStackListener { private static final int ON_SINGLE_TASK_DISPLAY_EMPTY = 22; private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 23; private static final int ON_TASK_DESCRIPTION_CHANGED = 24; + private static final int ON_ACTIVITY_ROTATION = 25; public H(Looper looper) { @@ -427,6 +433,12 @@ public class TaskStackChangeListeners extends TaskStackListener { } break; } + case ON_ACTIVITY_ROTATION: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityRotation(); + } + break; + } } } if (msg.obj instanceof SomeArgs) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java index dd613263e5c2..73783ae7ece2 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ViewRootImplCompat.java @@ -15,6 +15,7 @@ */ package com.android.systemui.shared.system; +import android.graphics.HardwareRenderer; import android.view.SurfaceControl; import android.view.View; import android.view.ViewRootImpl; @@ -50,7 +51,13 @@ public class ViewRootImplCompat { public void registerRtFrameCallback(LongConsumer callback) { if (mViewRoot != null) { - mViewRoot.registerRtFrameCallback(callback::accept); + mViewRoot.registerRtFrameCallback( + new HardwareRenderer.FrameDrawingCallback() { + @Override + public void onFrameDraw(long l) { + callback.accept(l); + } + }); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt new file mode 100644 index 000000000000..8cd68ef8acbc --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt @@ -0,0 +1,26 @@ +package com.android.keyguard + +import android.annotation.CurrentTimeMillisLong + +/** + * Data class for tracking information associated with [KeyguardUpdateMonitor.shouldListenForFace] + * method calls. + */ +data class KeyguardFaceListenModel( + @CurrentTimeMillisLong val timeMillis: Long, + val userId: Int, + val isListeningForFace: Boolean, + val isBouncer: Boolean, + val isAuthInterruptActive: Boolean, + val isKeyguardAwake: Boolean, + val isListeningForFaceAssistant: Boolean, + val isSwitchingUser: Boolean, + val isFaceDisabled: Boolean, + val isBecauseCannotSkipBouncer: Boolean, + val isKeyguardGoingAway: Boolean, + val isFaceSettingEnabledForUser: Boolean, + val isLockIconPressed: Boolean, + val isScanningAllowedByStrongAuth: Boolean, + val isPrimaryUser: Boolean, + val isSecureCameraLaunched: Boolean +) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 0db713e9c276..ee31706c0b94 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -58,6 +58,7 @@ import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback; import android.hardware.fingerprint.FingerprintManager.AuthenticationResult; +import android.os.Build; import android.os.CancellationSignal; import android.os.Handler; import android.os.IRemoteCallback; @@ -108,9 +109,13 @@ import com.google.android.collect.Lists; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; +import java.text.SimpleDateFormat; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.TimeZone; @@ -131,7 +136,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final String TAG = "KeyguardUpdateMonitor"; private static final boolean DEBUG = KeyguardConstants.DEBUG; private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES; - private static final boolean DEBUG_FACE = true; + private static final boolean DEBUG_FACE = Build.IS_DEBUGGABLE; private static final boolean DEBUG_SPEW = false; private static final int LOW_BATTERY_THRESHOLD = 20; @@ -362,6 +367,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting SparseArray<BiometricAuthenticated> mUserFaceAuthenticated = new SparseArray<>(); + // Keep track of recent calls to shouldListenForFace() for debugging. + private static final int FACE_LISTEN_CALLS_QUEUE_SIZE = 20; + private ArrayDeque<KeyguardFaceListenModel> mFaceListenModels; + private static int sCurrentUser; private Runnable mUpdateBiometricListeningState = this::updateBiometricListeningState; @@ -1945,25 +1954,48 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && strongAuthAllowsScanning && mIsPrimaryUser && !mSecureCameraLaunched; + // Aggregate relevant fields for debug logging. + if (DEBUG_FACE || DEBUG_SPEW) { + final KeyguardFaceListenModel model = new KeyguardFaceListenModel( + System.currentTimeMillis(), + user, + shouldListen, + mBouncer, + mAuthInterruptActive, + awakeKeyguard, + shouldListenForFaceAssistant(), + mSwitchingUser, + isFaceDisabled(user), + becauseCannotSkipBouncer, + mKeyguardGoingAway, + mFaceSettingEnabledForUser.get(user), + mLockIconPressed, + strongAuthAllowsScanning, + mIsPrimaryUser, + mSecureCameraLaunched); + maybeLogFaceListenerModelData(model); + } + + return shouldListen; + } + + private void maybeLogFaceListenerModelData(KeyguardFaceListenModel model) { // Too chatty, but very useful when debugging issues. if (DEBUG_SPEW) { - Log.v(TAG, "shouldListenForFace(" + user + ")=" + shouldListen + "... " - + ", mBouncer: " + mBouncer - + ", mAuthInterruptActive: " + mAuthInterruptActive - + ", awakeKeyguard: " + awakeKeyguard - + ", shouldListenForFaceAssistant: " + shouldListenForFaceAssistant() - + ", mSwitchingUser: " + mSwitchingUser - + ", isFaceDisabled(" + user + "): " + isFaceDisabled(user) - + ", becauseCannotSkipBouncer: " + becauseCannotSkipBouncer - + ", mKeyguardGoingAway: " + mKeyguardGoingAway - + ", mFaceSettingEnabledForUser(" + user + "): " - + mFaceSettingEnabledForUser.get(user) - + ", mLockIconPressed: " + mLockIconPressed - + ", strongAuthAllowsScanning: " + strongAuthAllowsScanning - + ", isPrimaryUser: " + mIsPrimaryUser - + ", mSecureCameraLaunched: " + mSecureCameraLaunched); + Log.v(TAG, model.toString()); + } + + // Add model data to the historical buffer. + if (DEBUG_FACE && mFaceRunningState != BIOMETRIC_STATE_RUNNING + && model.isListeningForFace()) { + if (mFaceListenModels == null) { + mFaceListenModels = new ArrayDeque<>(FACE_LISTEN_CALLS_QUEUE_SIZE); + } + if (mFaceListenModels.size() >= FACE_LISTEN_CALLS_QUEUE_SIZE) { + mFaceListenModels.remove(); + } + mFaceListenModels.add(model); } - return shouldListen; } /** @@ -2919,5 +2951,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" enabledByUser=" + mFaceSettingEnabledForUser.get(userId)); pw.println(" mSecureCameraLaunched=" + mSecureCameraLaunched); } + if (mFaceListenModels != null && !mFaceListenModels.isEmpty()) { + final SimpleDateFormat dateFormat = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); + pw.println(" Face listen results (last " + FACE_LISTEN_CALLS_QUEUE_SIZE + " calls):"); + for (final KeyguardFaceListenModel model : mFaceListenModels) { + final String time = dateFormat.format(new Date(model.getTimeMillis())); + pw.println(" " + time + " " + model.toString()); + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 87990cd3bffa..ccb506de6d8b 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -124,7 +124,7 @@ public final class Prefs { String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding"; String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount"; /** Tracks whether the user has seen the onboarding screen for priority conversations */ - String HAS_SEEN_PRIORITY_ONBOARDING = "HasSeenPriorityOnboarding"; + String HAS_SEEN_PRIORITY_ONBOARDING = "HaveShownPriorityOnboarding"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 0690907e8433..8a2c101f7057 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -798,6 +798,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleIconFactory = new BubbleIconFactory(mContext); mStackView.onDisplaySizeChanged(); } + + mStackView.onLayoutDirectionChanged(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index fee7847ee220..790d6a2070a2 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -316,6 +316,10 @@ public class BubbleExpandedView extends LinearLayout { return false; }); + + // BubbleStackView is forced LTR, but we want to respect the locale for expanded view layout + // so the Manage button appears on the right. + setLayoutDirection(LAYOUT_DIRECTION_LOCALE); } private String getBubbleKey() { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java index e12b325f5d05..8c76cda3290f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java @@ -186,6 +186,9 @@ public class BubbleFlyoutView extends FrameLayout { } }); + // Use locale direction so the text is aligned correctly. + setLayoutDirection(LAYOUT_DIRECTION_LOCALE); + mBgPaint.setColor(mFloatingBackgroundColor); mLeftTriangleShape = diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index 8fd20517782f..3e694b9f5d2a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -285,7 +285,10 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V } }); - ShortcutInfo info = b.getEntry().getRanking().getShortcutInfo(); + // If the bubble was persisted, the entry is null but it should have shortcut info + ShortcutInfo info = b.getEntry() == null + ? b.getShortcutInfo() + : b.getEntry().getRanking().getShortcutInfo(); if (info == null) { Log.d(TAG, "ShortcutInfo required to bubble but none found for " + b); } else { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index fb081e2bf904..8a80c4d75e84 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -47,6 +47,7 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; +import android.graphics.drawable.TransitionDrawable; import android.os.Bundle; import android.provider.Settings; import android.util.Log; @@ -133,6 +134,9 @@ public class BubbleStackView extends FrameLayout /** Percent to darken the bubbles when they're in the dismiss target. */ private static final float DARKEN_PERCENT = 0.3f; + /** Duration of the dismiss scrim fading in/out. */ + private static final int DISMISS_TRANSITION_DURATION_MS = 200; + /** How long to wait, in milliseconds, before hiding the flyout. */ @VisibleForTesting static final int FLYOUT_HIDE_AFTER = 5000; @@ -723,6 +727,12 @@ public class BubbleStackView extends FrameLayout setUpUserEducation(); + // Force LTR by default since most of the Bubbles UI is positioned manually by the user, or + // is centered. It greatly simplifies translation positioning/animations. Views that will + // actually lay out differently in RTL, such as the flyout and expanded view, will set their + // layout direction to LOCALE. + setLayoutDirection(LAYOUT_DIRECTION_LTR); + mBubbleContainer = new PhysicsAnimationLayout(context); mBubbleContainer.setActiveController(mStackAnimationController); mBubbleContainer.setElevation(elevation); @@ -746,7 +756,7 @@ public class BubbleStackView extends FrameLayout final View targetView = new DismissCircleView(context); final FrameLayout.LayoutParams newParams = new FrameLayout.LayoutParams(targetSize, targetSize); - newParams.gravity = Gravity.CENTER; + newParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; targetView.setLayoutParams(newParams); mDismissTargetAnimator = PhysicsAnimator.getInstance(targetView); @@ -755,9 +765,16 @@ public class BubbleStackView extends FrameLayout MATCH_PARENT, getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), Gravity.BOTTOM)); + + final int bottomMargin = + getResources().getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin); + mDismissTargetContainer.setPadding(0, 0, 0, bottomMargin); + mDismissTargetContainer.setClipToPadding(false); mDismissTargetContainer.setClipChildren(false); mDismissTargetContainer.addView(targetView); mDismissTargetContainer.setVisibility(View.INVISIBLE); + mDismissTargetContainer.setBackgroundResource( + R.drawable.floating_dismiss_gradient_transition); addView(mDismissTargetContainer); // Start translated down so the target springs up. @@ -961,6 +978,9 @@ public class BubbleStackView extends FrameLayout mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon); mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name); + + // The menu itself should respect locale direction so the icons are on the correct side. + mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE); addView(mManageMenu); } @@ -1084,6 +1104,16 @@ public class BubbleStackView extends FrameLayout mShowingManage = false; } + /** Tells the views with locale-dependent layout direction to resolve the new direction. */ + public void onLayoutDirectionChanged() { + mManageMenu.resolveLayoutDirection(); + mFlyout.resolveLayoutDirection(); + + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView().resolveLayoutDirection(); + } + } + /** Respond to the display size change by recalculating view size and location. */ public void onDisplaySizeChanged() { setUpOverflow(); @@ -1865,6 +1895,9 @@ public class BubbleStackView extends FrameLayout mDismissTargetContainer.setZ(Short.MAX_VALUE - 1); mDismissTargetContainer.setVisibility(VISIBLE); + ((TransitionDrawable) mDismissTargetContainer.getBackground()).startTransition( + DISMISS_TRANSITION_DURATION_MS); + mDismissTargetAnimator.cancel(); mDismissTargetAnimator .spring(DynamicAnimation.TRANSLATION_Y, 0f, mDismissTargetSpring) @@ -1882,6 +1915,9 @@ public class BubbleStackView extends FrameLayout mShowingDismiss = false; + ((TransitionDrawable) mDismissTargetContainer.getBackground()).reverseTransition( + DISMISS_TRANSITION_DURATION_MS); + mDismissTargetAnimator .spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(), mDismissTargetSpring) @@ -2095,16 +2131,21 @@ public class BubbleStackView extends FrameLayout mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect); - // When the menu is open, it should be at these coordinates. This will make the menu's - // bottom left corner match up with the button's bottom left corner. - final float targetX = mTempRect.left; + final boolean isLtr = + getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_LTR; + + // When the menu is open, it should be at these coordinates. The menu pops out to the right + // in LTR and to the left in RTL. + final float targetX = isLtr ? mTempRect.left : mTempRect.right - mManageMenu.getWidth(); final float targetY = mTempRect.bottom - mManageMenu.getHeight(); + final float xOffsetForAnimation = (isLtr ? 1 : -1) * mManageMenu.getWidth() / 4f; + if (show) { mManageMenu.setScaleX(0.5f); mManageMenu.setScaleY(0.5f); - mManageMenu.setTranslationX(targetX - mManageMenu.getWidth() / 4); - mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4); + mManageMenu.setTranslationX(targetX - xOffsetForAnimation); + mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4f); mManageMenu.setAlpha(0f); PhysicsAnimator.getInstance(mManageMenu) @@ -2121,8 +2162,8 @@ public class BubbleStackView extends FrameLayout .spring(DynamicAnimation.ALPHA, 0f) .spring(DynamicAnimation.SCALE_X, 0.5f) .spring(DynamicAnimation.SCALE_Y, 0.5f) - .spring(DynamicAnimation.TRANSLATION_X, targetX - mManageMenu.getWidth() / 4) - .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4) + .spring(DynamicAnimation.TRANSLATION_X, targetX - xOffsetForAnimation) + .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4f) .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE)) .start(); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index fd9fda3662a3..93f0c7f41ce3 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -44,6 +44,7 @@ import com.android.systemui.controls.management.ControlsListingController import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager +import com.android.systemui.globalactions.GlobalActionsDialog import com.android.systemui.util.concurrency.DelayableExecutor import java.io.FileDescriptor import java.io.PrintWriter @@ -79,6 +80,7 @@ class ControlsControllerImpl @Inject constructor ( } private var userChanging: Boolean = true + private var userStructure: UserStructure private var seedingInProgress = false private val seedingCallbacks = mutableListOf<Consumer<Boolean>>() @@ -97,7 +99,7 @@ class ControlsControllerImpl @Inject constructor ( internal var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper init { - val userStructure = UserStructure(context, currentUser) + userStructure = UserStructure(context, currentUser) persistenceWrapper = optionalWrapper.orElseGet { ControlsFavoritePersistenceWrapper( @@ -116,7 +118,7 @@ class ControlsControllerImpl @Inject constructor ( private fun setValuesForUser(newUser: UserHandle) { Log.d(TAG, "Changing to user: $newUser") currentUser = newUser - val userStructure = UserStructure(context, currentUser) + userStructure = UserStructure(context, currentUser) persistenceWrapper.changeFileAndBackupManager( userStructure.file, BackupManager(userStructure.userContext) @@ -192,6 +194,16 @@ class ControlsControllerImpl @Inject constructor ( it.componentName }.toSet() + // When a component is uninstalled, allow seeding to happen again if the user + // reinstalls the app + val prefs = userStructure.userContext.getSharedPreferences( + GlobalActionsDialog.PREFS_CONTROLS_FILE, Context.MODE_PRIVATE) + val completedSeedingPackageSet = prefs.getStringSet( + GlobalActionsDialog.PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>()) + val favoritePackageSet = favoriteComponentSet.map { it.packageName } + prefs.edit().putStringSet(GlobalActionsDialog.PREFS_CONTROLS_SEEDING_COMPLETED, + completedSeedingPackageSet.intersect(favoritePackageSet)).apply() + var changed = false favoriteComponentSet.subtract(serviceInfoSet).forEach { changed = true diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index 10e913743224..2a40b76eb326 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -58,16 +58,14 @@ class ControlActionCoordinatorImpl @Inject constructor( override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) { bouncerOrRun { - val effect = if (!isChecked) Vibrations.toggleOnEffect else Vibrations.toggleOffEffect - vibrate(effect) + cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) cvh.action(BooleanAction(templateId, !isChecked)) } } override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) { - vibrate(Vibrations.toggleOnEffect) - bouncerOrRun { + cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) if (cvh.usePanel()) { showDialog(cvh, control.getAppIntent().getIntent()) } else { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index f07f3168d246..994557cf696b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -118,6 +118,7 @@ class ControlViewHolder( var behavior: Behavior? = null var lastAction: ControlAction? = null var isLoading = false + var visibleDialog: Dialog? = null private var lastChallengeDialog: Dialog? = null private val onDialogCancel: () -> Unit = { lastChallengeDialog = null } @@ -197,18 +198,24 @@ class ControlViewHolder( fun dismiss() { lastChallengeDialog?.dismiss() lastChallengeDialog = null + visibleDialog?.dismiss() + visibleDialog = null } fun setTransientStatus(tempStatus: String) { val previousText = status.getText() cancelUpdate = uiExecutor.executeDelayed({ - setStatusText(previousText) - updateContentDescription() + animateStatusChange(/* animated */ true, { + setStatusText(previousText, /* immediately */ true) + updateContentDescription() + }) }, UPDATE_DELAY_IN_MILLIS) - setStatusText(tempStatus) - updateContentDescription() + animateStatusChange(/* animated */ true, { + setStatusText(tempStatus, /* immediately */ true) + updateContentDescription() + }) } private fun updateContentDescription() = diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 35ebac5b1ed5..d31b6eb9d857 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -129,7 +129,10 @@ class ControlsUiControllerImpl @Inject constructor ( SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName) } uiExecutor.execute { - onResult(lastItems) + parent.removeAllViews() + if (lastItems.size > 0) { + onResult(lastItems) + } } } } @@ -189,8 +192,6 @@ class ControlsUiControllerImpl @Inject constructor ( } private fun showSeedingView(items: List<SelectionItem>) { - parent.removeAllViews() - val inflater = LayoutInflater.from(context) inflater.inflate(R.layout.controls_no_favorites, parent, true) val subtitle = parent.requireViewById<TextView>(R.id.controls_subtitle) @@ -198,8 +199,6 @@ class ControlsUiControllerImpl @Inject constructor ( } private fun showInitialSetupView(items: List<SelectionItem>) { - parent.removeAllViews() - val inflater = LayoutInflater.from(context) inflater.inflate(R.layout.controls_no_favorites, parent, true) @@ -263,7 +262,6 @@ class ControlsUiControllerImpl @Inject constructor ( } private fun showControlsView(items: List<SelectionItem>) { - parent.removeAllViews() controlViewsById.clear() createListView() diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index f97015282222..9ec14523a809 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -64,6 +64,10 @@ class DetailDialog( } override fun onActivityViewDestroyed(view: ActivityView) {} + + override fun onTaskRemovalStarted(taskId: Int) { + dismiss() + } } init { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt index bf3835dba4fd..6bf189744033 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt @@ -16,7 +16,13 @@ package com.android.systemui.controls.ui +import android.app.AlertDialog +import android.app.PendingIntent +import android.content.DialogInterface +import android.content.pm.PackageManager import android.service.controls.Control +import android.view.View +import android.view.WindowManager import com.android.systemui.R @@ -31,7 +37,17 @@ class StatusBehavior : Behavior { val status = cws.control?.status ?: Control.STATUS_UNKNOWN val msg = when (status) { Control.STATUS_ERROR -> R.string.controls_error_generic - Control.STATUS_NOT_FOUND -> R.string.controls_error_removed + Control.STATUS_DISABLED -> R.string.controls_error_timeout + Control.STATUS_NOT_FOUND -> { + cvh.layout.setOnClickListener(View.OnClickListener() { + showNotFoundDialog(cvh, cws) + }) + cvh.layout.setOnLongClickListener(View.OnLongClickListener() { + showNotFoundDialog(cvh, cws) + true + }) + R.string.controls_error_removed + } else -> { cvh.isLoading = true com.android.internal.R.string.loading @@ -40,4 +56,42 @@ class StatusBehavior : Behavior { cvh.setStatusText(cvh.context.getString(msg)) cvh.applyRenderInfo(false, colorOffset) } + + private fun showNotFoundDialog(cvh: ControlViewHolder, cws: ControlWithState) { + val pm = cvh.context.getPackageManager() + val ai = pm.getApplicationInfo(cws.componentName.packageName, PackageManager.GET_META_DATA) + val appLabel = pm.getApplicationLabel(ai) + val builder = AlertDialog.Builder( + cvh.context, + android.R.style.Theme_DeviceDefault_Dialog_Alert + ).apply { + val res = cvh.context.resources + setTitle(res.getString(R.string.controls_error_removed_title)) + setMessage(res.getString( + R.string.controls_error_removed_message, cvh.title.getText(), appLabel)) + setPositiveButton( + R.string.controls_open_app, + DialogInterface.OnClickListener { dialog, _ -> + try { + cws.control?.getAppIntent()?.send() + } catch (e: PendingIntent.CanceledException) { + cvh.setTransientStatus( + cvh.context.resources.getString(R.string.controls_error_failed)) + } + dialog.dismiss() + }) + setNegativeButton( + android.R.string.cancel, + DialogInterface.OnClickListener { dialog, _ -> + dialog.cancel() + } + ) + } + cvh.visibleDialog = builder.create().apply { + getWindow().apply { + setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) + show() + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt index 1f0ca9be2ecc..4003f41b33dc 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt @@ -231,9 +231,11 @@ class ToggleRangeBehavior : Behavior { rangeAnimator?.cancel() if (isDragging) { - clipLayer.level = newLevel val isEdge = newLevel == MIN_LEVEL || newLevel == MAX_LEVEL - cvh.controlActionCoordinator.drag(isEdge) + if (clipLayer.level != newLevel) { + cvh.controlActionCoordinator.drag(isEdge) + clipLayer.level = newLevel + } } else if (newLevel != clipLayer.level) { rangeAnimator = ValueAnimator.ofInt(cvh.clipLayer.level, newLevel).apply { addUpdateListener { @@ -266,7 +268,7 @@ class ToggleRangeBehavior : Behavior { private fun format(primaryFormat: String, backupFormat: String, value: Float): String { return try { - String.format(primaryFormat, value) + String.format(primaryFormat, findNearestStep(value)) } catch (e: IllegalFormatException) { Log.w(ControlsUiController.TAG, "Illegal format in range template", e) if (backupFormat == "") { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt index c0f6aabe2427..29b7e985fabc 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt @@ -20,35 +20,9 @@ import android.os.VibrationEffect import android.os.VibrationEffect.Composition.PRIMITIVE_TICK object Vibrations { - private const val TOGGLE_TICK_COUNT = 40 - - val toggleOnEffect = initToggleOnEffect() - val toggleOffEffect = initToggleOffEffect() val rangeEdgeEffect = initRangeEdgeEffect() val rangeMiddleEffect = initRangeMiddleEffect() - private fun initToggleOnEffect(): VibrationEffect { - val composition = VibrationEffect.startComposition() - composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 200) - var i = 0 - while (i++ < TOGGLE_TICK_COUNT) { - composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 0) - } - composition.addPrimitive(PRIMITIVE_TICK, 0.5f, 100) - return composition.compose() - } - - private fun initToggleOffEffect(): VibrationEffect { - val composition = VibrationEffect.startComposition() - composition.addPrimitive(PRIMITIVE_TICK, 0.5f, 0) - composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 100) - var i = 0 - while (i++ < TOGGLE_TICK_COUNT) { - composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 0) - } - return composition.compose() - } - private fun initRangeEdgeEffect(): VibrationEffect { val composition = VibrationEffect.startComposition() composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 95a9006c854a..951dc9936e1c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -71,7 +71,7 @@ public class DozeFactory { DockManager dockManager, @Nullable IWallpaperManager wallpaperManager, ProximitySensor proximitySensor, DelayedWakeLock.Builder delayedWakeLockBuilder, @Main Handler handler, - DelayableExecutor delayableExecutor, + @Main DelayableExecutor delayableExecutor, BiometricUnlockController biometricUnlockController, BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) { mFalsingManager = falsingManager; diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 1b13d4a49fec..7e009b459ede 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -183,8 +183,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency"; static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; - private static final String PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"; - private static final String PREFS_CONTROLS_FILE = "controls_prefs"; + 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; @@ -391,7 +391,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (controlsComponent.getControlsListingController().isPresent()) { controlsComponent.getControlsListingController().get() - .addCallback(list -> mControlsServiceInfos = list); + .addCallback(list -> { + mControlsServiceInfos = list; + // This callback may occur after the dialog has been shown. + // If so, add controls into the already visible space + if (mDialog != null && !mDialog.isShowingControls() + && shouldShowControls()) { + mDialog.showControls(mControlsUiControllerOptional.get()); + } + }); } // Need to be user-specific with the context to make sure we read the correct prefs diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 5595201a670f..b8c1842a1780 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -17,12 +17,8 @@ package com.android.systemui.media; import android.app.PendingIntent; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -35,7 +31,6 @@ import android.graphics.drawable.RippleDrawable; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; -import android.service.media.MediaBrowserService; import android.util.Log; import android.view.View; import android.widget.ImageButton; @@ -55,7 +50,6 @@ import com.android.settingslib.media.MediaOutputSliceConstants; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.qs.QSMediaBrowser; import com.android.systemui.util.animation.TransitionLayout; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -81,7 +75,6 @@ public class MediaControlPanel { private final SeekBarViewModel mSeekBarViewModel; private SeekBarObserver mSeekBarObserver; - private final Executor mForegroundExecutor; protected final Executor mBackgroundExecutor; private final ActivityStarter mActivityStarter; @@ -91,48 +84,18 @@ public class MediaControlPanel { private MediaSession.Token mToken; private MediaController mController; private int mBackgroundColor; - protected ComponentName mServiceComponent; - private boolean mIsRegistered = false; - private String mKey; private int mAlbumArtSize; private int mAlbumArtRadius; - private int mViewWidth; - - public static final String MEDIA_PREFERENCES = "media_control_prefs"; - public static final String MEDIA_PREFERENCE_KEY = "browser_components"; - private SharedPreferences mSharedPrefs; - private boolean mCheckedForResumption = false; - private QSMediaBrowser mQSMediaBrowser; - - private final MediaController.Callback mSessionCallback = new MediaController.Callback() { - @Override - public void onSessionDestroyed() { - Log.d(TAG, "session destroyed"); - mController.unregisterCallback(mSessionCallback); - clearControls(); - } - @Override - public void onPlaybackStateChanged(PlaybackState state) { - final int s = state != null ? state.getState() : PlaybackState.STATE_NONE; - if (s == PlaybackState.STATE_NONE) { - Log.d(TAG, "playback state change will trigger resumption, state=" + state); - clearControls(); - } - } - }; /** * Initialize a new control panel * @param context - * @param foregroundExecutor foreground executor * @param backgroundExecutor background executor, used for processing artwork * @param activityStarter activity starter */ - public MediaControlPanel(Context context, Executor foregroundExecutor, - DelayableExecutor backgroundExecutor, ActivityStarter activityStarter, - MediaHostStatesManager mediaHostStatesManager) { + public MediaControlPanel(Context context, DelayableExecutor backgroundExecutor, + ActivityStarter activityStarter, MediaHostStatesManager mediaHostStatesManager) { mContext = context; - mForegroundExecutor = foregroundExecutor; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor); @@ -214,45 +177,18 @@ public class MediaControlPanel { MediaSession.Token token = data.getToken(); mBackgroundColor = data.getBackgroundColor(); if (mToken == null || !mToken.equals(token)) { - if (mQSMediaBrowser != null) { - Log.d(TAG, "Disconnecting old media browser"); - mQSMediaBrowser.disconnect(); - mQSMediaBrowser = null; - } mToken = token; - mServiceComponent = null; - mCheckedForResumption = false; } - mController = new MediaController(mContext, mToken); + if (mToken != null) { + mController = new MediaController(mContext, mToken); + } else { + mController = null; + } ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); - // Try to find a browser service component for this app - // TODO also check for a media button receiver intended for restarting (b/154127084) - // Only check if we haven't tried yet or the session token changed - final String pkgName = data.getPackageName(); - if (mServiceComponent == null && !mCheckedForResumption) { - Log.d(TAG, "Checking for service component"); - PackageManager pm = mContext.getPackageManager(); - Intent resumeIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE); - List<ResolveInfo> resumeInfo = pm.queryIntentServices(resumeIntent, 0); - // TODO: look into this resumption - if (resumeInfo != null) { - for (ResolveInfo inf : resumeInfo) { - if (inf.serviceInfo.packageName.equals(mController.getPackageName())) { - mBackgroundExecutor.execute(() -> - tryUpdateResumptionList(inf.getComponentInfo().getComponentName())); - break; - } - } - } - mCheckedForResumption = true; - } - - mController.registerCallback(mSessionCallback); - mViewHolder.getPlayer().setBackgroundTintList( ColorStateList.valueOf(mBackgroundColor)); @@ -267,12 +203,22 @@ public class MediaControlPanel { ImageView albumView = mViewHolder.getAlbumView(); // TODO: migrate this to a view with rounded corners instead of baking the rounding // into the bitmap - Drawable artwork = createRoundedBitmap(data.getArtwork()); - albumView.setImageDrawable(artwork); + boolean hasArtwork = data.getArtwork() != null; + if (hasArtwork) { + Drawable artwork = createRoundedBitmap(data.getArtwork()); + albumView.setImageDrawable(artwork); + } + setVisibleAndAlpha(collapsedSet, R.id.album_art, hasArtwork); + setVisibleAndAlpha(expandedSet, R.id.album_art, hasArtwork); // App icon ImageView appIcon = mViewHolder.getAppIcon(); - appIcon.setImageDrawable(data.getAppIcon()); + if (data.getAppIcon() != null) { + appIcon.setImageDrawable(data.getAppIcon()); + } else { + Drawable iconDrawable = mContext.getDrawable(R.drawable.ic_music_note); + appIcon.setImageDrawable(iconDrawable); + } // Song name TextView titleText = mViewHolder.getTitleText(); @@ -294,7 +240,7 @@ public class MediaControlPanel { final Intent intent = new Intent() .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, - mController.getPackageName()) + data.getPackageName()) .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken); mActivityStarter.startActivity(intent, false, true /* dismissShade */, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -350,15 +296,11 @@ public class MediaControlPanel { MediaAction mediaAction = actionIcons.get(i); button.setImageDrawable(mediaAction.getDrawable()); button.setContentDescription(mediaAction.getContentDescription()); - PendingIntent actionIntent = mediaAction.getIntent(); + Runnable action = mediaAction.getAction(); button.setOnClickListener(v -> { - if (actionIntent != null) { - try { - actionIntent.send(); - } catch (PendingIntent.CanceledException e) { - e.printStackTrace(); - } + if (action != null) { + action.run(); } }); boolean visibleInCompat = actionsWhenCollapsed.contains(i); @@ -444,14 +386,6 @@ public class MediaControlPanel { } /** - * Return the original notification's key - * @return The notification key - */ - public String getKey() { - return mKey; - } - - /** * Check whether this player has an attached media session. * @return whether there is a controller with a current media session. */ @@ -485,150 +419,8 @@ public class MediaControlPanel { return (state.getState() == PlaybackState.STATE_PLAYING); } - /** - * Puts controls into a resumption state if possible, or calls removePlayer if no component was - * found that could resume playback - */ - public void clearControls() { - Log.d(TAG, "clearControls to resumption state package=" + getMediaPlayerPackage()); - if (mServiceComponent == null) { - // If we don't have a way to resume, just remove the player altogether - Log.d(TAG, "Removing unresumable controls"); - removePlayer(); - return; - } - resetButtons(); - } - - /** - * Hide the media buttons and show only a restart button - */ - protected void resetButtons() { - if (mViewHolder == null) { - return; - } - // Hide all the old buttons - - ConstraintSet expandedSet = mMediaViewController.getExpandedLayout(); - ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout(); - for (int i = 1; i < ACTION_IDS.length; i++) { - setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /*visible */); - setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */); - } - - // Add a restart button - ImageButton btn = mViewHolder.getAction0(); - btn.setOnClickListener(v -> { - Log.d(TAG, "Attempting to restart session"); - if (mQSMediaBrowser != null) { - mQSMediaBrowser.disconnect(); - } - mQSMediaBrowser = new QSMediaBrowser(mContext, new QSMediaBrowser.Callback(){ - @Override - public void onConnected() { - Log.d(TAG, "Successfully restarted"); - } - @Override - public void onError() { - Log.e(TAG, "Error restarting"); - mQSMediaBrowser.disconnect(); - mQSMediaBrowser = null; - } - }, mServiceComponent); - mQSMediaBrowser.restart(); - }); - btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play)); - setVisibleAndAlpha(expandedSet, ACTION_IDS[0], true /*visible */); - setVisibleAndAlpha(collapsedSet, ACTION_IDS[0], true /*visible */); - - mSeekBarViewModel.clearController(); - // TODO: fix guts - // View guts = mMediaNotifView.findViewById(R.id.media_guts); - View options = mViewHolder.getOptions(); - - mViewHolder.getPlayer().setOnLongClickListener(v -> { - // Replace player view with close/cancel view -// guts.setVisibility(View.GONE); - options.setVisibility(View.VISIBLE); - return true; // consumed click - }); - mMediaViewController.refreshState(); - } - private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) { set.setVisibility(actionId, visible? ConstraintSet.VISIBLE : ConstraintSet.GONE); set.setAlpha(actionId, visible ? 1.0f : 0.0f); } - - /** - * Verify that we can connect to the given component with a MediaBrowser, and if so, add that - * component to the list of resumption components - */ - private void tryUpdateResumptionList(ComponentName componentName) { - Log.d(TAG, "Testing if we can connect to " + componentName); - if (mQSMediaBrowser != null) { - mQSMediaBrowser.disconnect(); - } - mQSMediaBrowser = new QSMediaBrowser(mContext, - new QSMediaBrowser.Callback() { - @Override - public void onConnected() { - Log.d(TAG, "yes we can resume with " + componentName); - mServiceComponent = componentName; - updateResumptionList(componentName); - mQSMediaBrowser.disconnect(); - mQSMediaBrowser = null; - } - - @Override - public void onError() { - Log.d(TAG, "Cannot resume with " + componentName); - mServiceComponent = null; - if (!hasMediaSession()) { - // If it's not active and we can't resume, remove - removePlayer(); - } - mQSMediaBrowser.disconnect(); - mQSMediaBrowser = null; - } - }, - componentName); - mQSMediaBrowser.testConnection(); - } - - /** - * Add the component to the saved list of media browser services, checking for duplicates and - * removing older components that exceed the maximum limit - * @param componentName - */ - private synchronized void updateResumptionList(ComponentName componentName) { - // Add to front of saved list - if (mSharedPrefs == null) { - mSharedPrefs = mContext.getSharedPreferences(MEDIA_PREFERENCES, 0); - } - String componentString = componentName.flattenToString(); - String listString = mSharedPrefs.getString(MEDIA_PREFERENCE_KEY, null); - if (listString == null) { - listString = componentString; - } else { - String[] components = listString.split(QSMediaBrowser.DELIMITER); - StringBuilder updated = new StringBuilder(componentString); - int nBrowsers = 1; - for (int i = 0; i < components.length - && nBrowsers < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) { - if (componentString.equals(components[i])) { - continue; - } - updated.append(QSMediaBrowser.DELIMITER).append(components[i]); - nBrowsers++; - } - listString = updated.toString(); - } - mSharedPrefs.edit().putString(MEDIA_PREFERENCE_KEY, listString).apply(); - } - - /** - * Called when a player can't be resumed to give it an opportunity to hide or remove itself - */ - protected void removePlayer() { } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index a94f6a87d58a..5d28178a3b1b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -32,17 +32,19 @@ data class MediaData( val artwork: Icon?, val actions: List<MediaAction>, val actionsToShowInCompact: List<Int>, - val packageName: String?, + val packageName: String, val token: MediaSession.Token?, val clickIntent: PendingIntent?, val device: MediaDeviceData?, - val notificationKey: String = "INVALID" + var resumeAction: Runnable?, + val notificationKey: String = "INVALID", + var hasCheckedForResume: Boolean = false ) /** State of a media action. */ data class MediaAction( val drawable: Drawable?, - val intent: PendingIntent?, + val action: Runnable?, val contentDescription: CharSequence? ) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt index cce9838bb8e2..11cbc482459a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt @@ -32,9 +32,15 @@ class MediaDataCombineLatest @Inject constructor( init { dataSource.addListener(object : MediaDataManager.Listener { - override fun onMediaDataLoaded(key: String, data: MediaData) { - entries[key] = data to entries[key]?.second - update(key) + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + if (oldKey != null && !oldKey.equals(key)) { + val s = entries[oldKey]?.second + entries[key] = data to entries[oldKey]?.second + entries.remove(oldKey) + } else { + entries[key] = data to entries[key]?.second + } + update(key, oldKey) } override fun onMediaDataRemoved(key: String) { remove(key) @@ -43,7 +49,7 @@ class MediaDataCombineLatest @Inject constructor( deviceSource.addListener(object : MediaDeviceManager.Listener { override fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) { entries[key] = entries[key]?.first to data - update(key) + update(key, key) } override fun onKeyRemoved(key: String) { remove(key) @@ -61,19 +67,21 @@ class MediaDataCombineLatest @Inject constructor( */ fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener) - private fun update(key: String) { + private fun update(key: String, oldKey: String?) { val (entry, device) = entries[key] ?: null to null if (entry != null && device != null) { val data = entry.copy(device = device) - listeners.forEach { - it.onMediaDataLoaded(key, data) + val listenersCopy = listeners.toSet() + listenersCopy.forEach { + it.onMediaDataLoaded(key, oldKey, data) } } } private fun remove(key: String) { entries.remove(key)?.let { - listeners.forEach { + val listenersCopy = listeners.toSet() + listenersCopy.forEach { it.onMediaDataRemoved(key) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index d94985703083..094c5bef3c18 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -17,6 +17,7 @@ package com.android.systemui.media import android.app.Notification +import android.app.PendingIntent import android.content.ContentResolver import android.content.Context import android.graphics.Bitmap @@ -25,6 +26,7 @@ import android.graphics.Color import android.graphics.ImageDecoder import android.graphics.drawable.Drawable import android.graphics.drawable.Icon +import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaSession import android.net.Uri @@ -32,8 +34,10 @@ import android.service.notification.StatusBarNotification import android.text.TextUtils import android.util.Log import com.android.internal.graphics.ColorUtils +import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.MediaNotificationProcessor import com.android.systemui.statusbar.notification.NotificationEntryManager import com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON @@ -58,7 +62,7 @@ private const val LUMINOSITY_THRESHOLD = 0.05f private const val SATURATION_MULTIPLIER = 0.8f private val LOADING = MediaData(false, 0, null, null, null, null, null, - emptyList(), emptyList(), null, null, null, null) + emptyList(), emptyList(), "INVALID", null, null, null, null) fun isMediaNotification(sbn: StatusBarNotification): Boolean { if (!sbn.notification.hasMediaSession()) { @@ -81,34 +85,92 @@ class MediaDataManager @Inject constructor( private val mediaControllerFactory: MediaControllerFactory, private val mediaTimeoutListener: MediaTimeoutListener, private val notificationEntryManager: NotificationEntryManager, + private val mediaResumeListener: MediaResumeListener, @Background private val backgroundExecutor: Executor, @Main private val foregroundExecutor: Executor ) { private val listeners: MutableSet<Listener> = mutableSetOf() private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() + private val useMediaResumption: Boolean = Utils.useMediaResumption(context) init { mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean -> setTimedOut(token, timedOut) } addListener(mediaTimeoutListener) + + if (useMediaResumption) { + mediaResumeListener.addTrackToResumeCallback = { desc: MediaDescription, + resumeAction: Runnable, token: MediaSession.Token, appName: String, + appIntent: PendingIntent, packageName: String -> + addResumptionControls(desc, resumeAction, token, appName, appIntent, packageName) + } + mediaResumeListener.resumeComponentFoundCallback = { key: String, action: Runnable? -> + mediaEntries.get(key)?.resumeAction = action + mediaEntries.get(key)?.hasCheckedForResume = true + } + addListener(mediaResumeListener) + } } fun onNotificationAdded(key: String, sbn: StatusBarNotification) { if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) { Assert.isMainThread() - if (!mediaEntries.containsKey(key)) { - mediaEntries.put(key, LOADING) + val oldKey = findExistingEntry(key, sbn.packageName) + if (oldKey == null) { + val temp = LOADING.copy(packageName = sbn.packageName) + mediaEntries.put(key, temp) + } else if (oldKey != key) { + // Move to new key + val oldData = mediaEntries.remove(oldKey)!! + mediaEntries.put(key, oldData) } - loadMediaData(key, sbn) + loadMediaData(key, sbn, oldKey) } else { onNotificationRemoved(key) } } - private fun loadMediaData(key: String, sbn: StatusBarNotification) { + private fun addResumptionControls( + desc: MediaDescription, + action: Runnable, + token: MediaSession.Token, + appName: String, + appIntent: PendingIntent, + packageName: String + ) { + // Resume controls don't have a notification key, so store by package name instead + if (!mediaEntries.containsKey(packageName)) { + val resumeData = LOADING.copy(packageName = packageName, resumeAction = action) + mediaEntries.put(packageName, resumeData) + } backgroundExecutor.execute { - loadMediaDataInBg(key, sbn) + loadMediaDataInBg(desc, action, token, appName, appIntent, packageName) + } + } + + /** + * Check if there is an existing entry that matches the key or package name. + * Returns the key that matches, or null if not found. + */ + private fun findExistingEntry(key: String, packageName: String): String? { + if (mediaEntries.containsKey(key)) { + return key + } + // Check if we already had a resume player + if (mediaEntries.containsKey(packageName)) { + return packageName + } + return null + } + + private fun loadMediaData( + key: String, + sbn: StatusBarNotification, + oldKey: String? + ) { + backgroundExecutor.execute { + loadMediaDataInBg(key, sbn, oldKey) } } @@ -132,7 +194,50 @@ class MediaDataManager @Inject constructor( } } - private fun loadMediaDataInBg(key: String, sbn: StatusBarNotification) { + private fun loadMediaDataInBg( + desc: MediaDescription, + resumeAction: Runnable, + token: MediaSession.Token, + appName: String, + appIntent: PendingIntent, + packageName: String + ) { + if (resumeAction == null) { + Log.e(TAG, "Resume action cannot be null") + return + } + + if (TextUtils.isEmpty(desc.title)) { + Log.e(TAG, "Description incomplete") + return + } + + Log.d(TAG, "adding track from browser: $desc") + + // Album art + var artworkBitmap = desc.iconBitmap + if (artworkBitmap == null && desc.iconUri != null) { + artworkBitmap = loadBitmapFromUri(desc.iconUri!!) + } + val artworkIcon = if (artworkBitmap != null) { + Icon.createWithBitmap(artworkBitmap) + } else { + null + } + + val mediaAction = getResumeMediaAction(resumeAction) + foregroundExecutor.execute { + onMediaDataLoaded(packageName, null, MediaData(true, Color.DKGRAY, appName, + null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), + packageName, token, appIntent, null, resumeAction, packageName)) + } + } + + private fun loadMediaDataInBg( + key: String, + sbn: StatusBarNotification, + oldKey: String? + ) { val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) as MediaSession.Token? val metadata = mediaControllerFactory.create(token).metadata @@ -234,16 +339,23 @@ class MediaDataManager @Inject constructor( } val mediaAction = MediaAction( action.getIcon().loadDrawable(packageContext), - action.actionIntent, + Runnable { + try { + action.actionIntent.send() + } catch (e: PendingIntent.CanceledException) { + Log.d(TAG, "Intent canceled", e) + } + }, action.title) actionIcons.add(mediaAction) } } + val resumeAction: Runnable? = mediaEntries.get(key)?.resumeAction foregroundExecutor.execute { - onMediaDataLoaded(key, MediaData(true, bgColor, app, smallIconDrawable, artist, song, - artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token, - notif.contentIntent, null, key)) + onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist, + song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token, + notif.contentIntent, null, resumeAction, key)) } } @@ -257,7 +369,7 @@ class MediaDataManager @Inject constructor( val albumArt = loadBitmapFromUri(Uri.parse(uriString)) if (albumArt != null) { Log.d(TAG, "loaded art from $uri") - break + return albumArt } } } @@ -283,27 +395,52 @@ class MediaDataManager @Inject constructor( val source = ImageDecoder.createSource(context.getContentResolver(), uri) return try { - ImageDecoder.decodeBitmap(source) + ImageDecoder.decodeBitmap(source) { + decoder, info, source -> decoder.isMutableRequired = true + } } catch (e: IOException) { e.printStackTrace() null } } - fun onMediaDataLoaded(key: String, data: MediaData) { + private fun getResumeMediaAction(action: Runnable): MediaAction { + return MediaAction( + context.getDrawable(R.drawable.lb_ic_play), + action, + context.getString(R.string.controls_media_resume) + ) + } + + fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { Assert.isMainThread() if (mediaEntries.containsKey(key)) { // Otherwise this was removed already mediaEntries.put(key, data) val listenersCopy = listeners.toSet() listenersCopy.forEach { - it.onMediaDataLoaded(key, data) + it.onMediaDataLoaded(key, oldKey, data) } } } fun onNotificationRemoved(key: String) { Assert.isMainThread() + if (useMediaResumption && mediaEntries.get(key)?.resumeAction != null) { + Log.d(TAG, "Not removing $key because resumable") + // Move to resume key aka package name + val data = mediaEntries.remove(key)!! + val resumeAction = getResumeMediaAction(data.resumeAction!!) + val updated = data.copy(token = null, actions = listOf(resumeAction), + actionsToShowInCompact = listOf(0)) + mediaEntries.put(data.packageName, updated) + // Notify listeners of "new" controls + val listenersCopy = listeners.toSet() + listenersCopy.forEach { + it.onMediaDataLoaded(data.packageName, key, updated) + } + return + } val removed = mediaEntries.remove(key) if (removed != null) { val listenersCopy = listeners.toSet() @@ -316,19 +453,32 @@ class MediaDataManager @Inject constructor( /** * Are there any media notifications active? */ - fun hasActiveMedia() = mediaEntries.isNotEmpty() + fun hasActiveMedia() = mediaEntries.any({ isActive(it.value) }) - fun hasAnyMedia(): Boolean { - // TODO: implement this when we implemented resumption - return hasActiveMedia() + fun isActive(data: MediaData): Boolean { + if (data.token == null) { + return false + } + val controller = mediaControllerFactory.create(data.token) + val state = controller?.playbackState?.state + return state != null && NotificationMediaManager.isActiveState(state) } + /** + * Are there any media entries, including resume controls? + */ + fun hasAnyMedia() = mediaEntries.isNotEmpty() + interface Listener { /** - * Called whenever there's new MediaData Loaded for the consumption in views + * Called whenever there's new MediaData Loaded for the consumption in views. + * + * oldKey is provided to check whether the view has changed keys, which can happen when a + * player has gone from resume state (key is package name) to active state (key is + * notification key) or vice versa. */ - fun onMediaDataLoaded(key: String, data: MediaData) {} + fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {} /** * Called whenever a previously existing Media notification was removed diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index 552fea63a278..2f521ea39242 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -16,11 +16,8 @@ package com.android.systemui.media -import android.app.Notification import android.content.Context -import android.service.notification.StatusBarNotification import android.media.MediaRouter2Manager -import android.media.session.MediaSession import android.media.session.MediaController import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice @@ -38,11 +35,16 @@ class MediaDeviceManager @Inject constructor( private val localMediaManagerFactory: LocalMediaManagerFactory, private val mr2manager: MediaRouter2Manager, private val featureFlag: MediaFeatureFlag, - @Main private val fgExecutor: Executor -) { + @Main private val fgExecutor: Executor, + private val mediaDataManager: MediaDataManager +) : MediaDataManager.Listener { private val listeners: MutableSet<Listener> = mutableSetOf() private val entries: MutableMap<String, Token> = mutableMapOf() + init { + mediaDataManager.addListener(this) + } + /** * Add a listener for changes to the media route (ie. device). */ @@ -53,23 +55,25 @@ class MediaDeviceManager @Inject constructor( */ fun removeListener(listener: Listener) = listeners.remove(listener) - fun onNotificationAdded(key: String, sbn: StatusBarNotification) { - if (featureFlag.enabled && isMediaNotification(sbn)) { + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + if (featureFlag.enabled) { + if (oldKey != null && oldKey != key) { + val oldToken = entries.remove(oldKey) + oldToken?.stop() + } var tok = entries[key] - if (tok == null) { - val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) - as MediaSession.Token? - val controller = MediaController(context, token) - tok = Token(key, controller, localMediaManagerFactory.create(sbn.packageName)) + if (tok == null && data.token != null) { + val controller = MediaController(context, data.token!!) + tok = Token(key, controller, localMediaManagerFactory.create(data.packageName)) entries[key] = tok tok.start() } } else { - onNotificationRemoved(key) + onMediaDataRemoved(key) } } - fun onNotificationRemoved(key: String) { + override fun onMediaDataRemoved(key: String) { val token = entries.remove(key) token?.stop() token?.let { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index e904e935b0e0..2bd8c0cbeab2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -50,7 +50,7 @@ class MediaHost @Inject constructor( } private val listener = object : MediaDataManager.Listener { - override fun onMediaDataLoaded(key: String, data: MediaData) { + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { updateViewVisibility() } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt new file mode 100644 index 000000000000..6bbe0d1651dd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -0,0 +1,252 @@ +/* + * 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.media + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageManager +import android.media.MediaDescription +import android.media.session.MediaController +import android.media.session.MediaSession +import android.os.UserHandle +import android.service.media.MediaBrowserService +import android.util.Log +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.Utils +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Singleton + +private const val TAG = "MediaResumeListener" + +private const val MEDIA_PREFERENCES = "media_control_prefs" +private const val MEDIA_PREFERENCE_KEY = "browser_components" + +@Singleton +class MediaResumeListener @Inject constructor( + private val context: Context, + private val broadcastDispatcher: BroadcastDispatcher, + @Background private val backgroundExecutor: Executor +) : MediaDataManager.Listener { + + private val useMediaResumption: Boolean = Utils.useMediaResumption(context) + private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue() + + lateinit var addTrackToResumeCallback: ( + MediaDescription, + Runnable, + MediaSession.Token, + String, + PendingIntent, + String + ) -> Unit + lateinit var resumeComponentFoundCallback: (String, Runnable?) -> Unit + + private var mediaBrowser: ResumeMediaBrowser? = null + + private val unlockReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (Intent.ACTION_USER_UNLOCKED == intent.action) { + loadMediaResumptionControls() + } + } + } + + private val mediaBrowserCallback = object : ResumeMediaBrowser.Callback() { + override fun addTrack( + desc: MediaDescription, + component: ComponentName, + browser: ResumeMediaBrowser + ) { + val token = browser.token + val appIntent = browser.appIntent + val pm = context.getPackageManager() + var appName: CharSequence = component.packageName + val resumeAction = getResumeAction(component) + try { + appName = pm.getApplicationLabel( + pm.getApplicationInfo(component.packageName, 0)) + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Error getting package information", e) + } + + Log.d(TAG, "Adding resume controls $desc") + addTrackToResumeCallback(desc, resumeAction, token, appName.toString(), appIntent, + component.packageName) + } + } + + init { + if (useMediaResumption) { + val unlockFilter = IntentFilter() + unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED) + broadcastDispatcher.registerReceiver(unlockReceiver, unlockFilter, null, UserHandle.ALL) + loadSavedComponents() + } + } + + private fun loadSavedComponents() { + val userContext = context.createContextAsUser(context.getUser(), 0) + val prefs = userContext.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE) + val listString = prefs.getString(MEDIA_PREFERENCE_KEY, null) + val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex()) + ?.dropLastWhile { it.isEmpty() } + components?.forEach { + val info = it.split("/") + val packageName = info[0] + val className = info[1] + val component = ComponentName(packageName, className) + resumeComponents.add(component) + } + Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}") + } + + /** + * Load controls for resuming media, if available + */ + private fun loadMediaResumptionControls() { + if (!useMediaResumption) { + return + } + + resumeComponents.forEach { + val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it) + browser.findRecentMedia() + } + broadcastDispatcher.unregisterReceiver(unlockReceiver) // only need to load once + } + + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + if (useMediaResumption) { + // If this had been started from a resume state, disconnect now that it's live + mediaBrowser?.disconnect() + // If we don't have a resume action, check if we haven't already + if (data.resumeAction == null && !data.hasCheckedForResume) { + // TODO also check for a media button receiver intended for restarting (b/154127084) + Log.d(TAG, "Checking for service component for " + data.packageName) + val pm = context.packageManager + val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE) + val resumeInfo = pm.queryIntentServices(serviceIntent, 0) + + val inf = resumeInfo?.filter { + it.serviceInfo.packageName == data.packageName + } + if (inf != null && inf.size > 0) { + backgroundExecutor.execute { + tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName) + } + } else { + // No service found + resumeComponentFoundCallback(key, null) + } + } + } + } + + /** + * Verify that we can connect to the given component with a MediaBrowser, and if so, add that + * component to the list of resumption components + */ + private fun tryUpdateResumptionList(key: String, componentName: ComponentName) { + Log.d(TAG, "Testing if we can connect to $componentName") + mediaBrowser?.disconnect() + mediaBrowser = ResumeMediaBrowser(context, + object : ResumeMediaBrowser.Callback() { + override fun onConnected() { + Log.d(TAG, "yes we can resume with $componentName") + resumeComponentFoundCallback(key, getResumeAction(componentName)) + updateResumptionList(componentName) + mediaBrowser?.disconnect() + mediaBrowser = null + } + + override fun onError() { + Log.e(TAG, "Cannot resume with $componentName") + resumeComponentFoundCallback(key, null) + mediaBrowser?.disconnect() + mediaBrowser = null + } + }, + componentName) + mediaBrowser?.testConnection() + } + + /** + * Add the component to the saved list of media browser services, checking for duplicates and + * removing older components that exceed the maximum limit + * @param componentName + */ + private fun updateResumptionList(componentName: ComponentName) { + // Remove if exists + resumeComponents.remove(componentName) + // Insert at front of queue + resumeComponents.add(componentName) + // Remove old components if over the limit + if (resumeComponents.size > ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS) { + resumeComponents.remove() + } + + // Save changes + val sb = StringBuilder() + resumeComponents.forEach { + sb.append(it.flattenToString()) + sb.append(ResumeMediaBrowser.DELIMITER) + } + val userContext = context.createContextAsUser(context.getUser(), 0) + val prefs = userContext.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE) + prefs.edit().putString(MEDIA_PREFERENCE_KEY, sb.toString()).apply() + } + + /** + * Get a runnable which will resume media playback + */ + private fun getResumeAction(componentName: ComponentName): Runnable { + return Runnable { + mediaBrowser?.disconnect() + mediaBrowser = ResumeMediaBrowser(context, + object : ResumeMediaBrowser.Callback() { + override fun onConnected() { + if (mediaBrowser?.token == null) { + Log.e(TAG, "Error after connect") + mediaBrowser?.disconnect() + mediaBrowser = null + return + } + Log.d(TAG, "Connected for restart $componentName") + val controller = MediaController(context, mediaBrowser!!.token) + val controls = controller.transportControls + controls.prepare() + controls.play() + } + + override fun onError() { + Log.e(TAG, "Resume failed for $componentName") + mediaBrowser?.disconnect() + mediaBrowser = null + } + }, + componentName) + mediaBrowser?.restart() + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 92a1ab1b1871..359c2f5e297c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -45,7 +45,7 @@ class MediaTimeoutListener @Inject constructor( lateinit var timeoutCallback: (String, Boolean) -> Unit - override fun onMediaDataLoaded(key: String, data: MediaData) { + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { if (mediaListeners.containsKey(key)) { return } @@ -67,15 +67,20 @@ class MediaTimeoutListener @Inject constructor( var timedOut = false - private val mediaController = mediaControllerFactory.create(data.token) + // Resume controls may have null token + private val mediaController = if (data.token != null) { + mediaControllerFactory.create(data.token) + } else { + null + } private var cancellation: Runnable? = null init { - mediaController.registerCallback(this) + mediaController?.registerCallback(this) } fun destroy() { - mediaController.unregisterCallback(this) + mediaController?.unregisterCallback(this) } override fun onPlaybackStateChanged(state: PlaybackState?) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt index 8ab30c75c7eb..3557b04a57bc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt @@ -12,14 +12,12 @@ import android.widget.LinearLayout import androidx.core.view.GestureDetectorCompat import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.PageIndicator import com.android.systemui.statusbar.notification.VisualStabilityManager import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.animation.requiresRemeasuring import com.android.systemui.util.concurrency.DelayableExecutor -import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton @@ -32,7 +30,6 @@ private const val FLING_SLOP = 1000000 @Singleton class MediaViewManager @Inject constructor( private val context: Context, - @Main private val foregroundExecutor: Executor, @Background private val backgroundExecutor: DelayableExecutor, private val visualStabilityManager: VisualStabilityManager, private val activityStarter: ActivityStarter, @@ -147,8 +144,8 @@ class MediaViewManager @Inject constructor( visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback, true /* persistent */) mediaManager.addListener(object : MediaDataManager.Listener { - override fun onMediaDataLoaded(key: String, data: MediaData) { - updateView(key, data) + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + updateView(key, oldKey, data) updatePlayerVisibilities() mediaCarousel.requiresRemeasuring = true } @@ -259,11 +256,17 @@ class MediaViewManager @Inject constructor( } } - private fun updateView(key: String, data: MediaData) { + private fun updateView(key: String, oldKey: String?, data: MediaData) { + // If the key was changed, update entry + val oldData = mediaPlayers[oldKey] + if (oldData != null) { + val oldData = mediaPlayers.remove(oldKey) + mediaPlayers.put(key, oldData!!) + } var existingPlayer = mediaPlayers[key] if (existingPlayer == null) { - existingPlayer = MediaControlPanel(context, foregroundExecutor, backgroundExecutor, - activityStarter, mediaHostStatesManager) + existingPlayer = MediaControlPanel(context, backgroundExecutor, activityStarter, + mediaHostStatesManager) existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent)) mediaPlayers[key] = existingPlayer diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java index a5b73dcbd289..1e9a30364607 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs; +package com.android.systemui.media; import android.app.PendingIntent; import android.content.ComponentName; @@ -27,14 +27,17 @@ import android.media.session.MediaController; import android.media.session.MediaSession; import android.os.Bundle; import android.service.media.MediaBrowserService; +import android.text.TextUtils; import android.util.Log; +import com.android.systemui.util.Utils; + import java.util.List; /** - * Media browser for managing resumption in QS media controls + * Media browser for managing resumption in media controls */ -public class QSMediaBrowser { +public class ResumeMediaBrowser { /** Maximum number of controls to show on boot */ public static final int MAX_RESUMPTION_CONTROLS = 5; @@ -42,7 +45,8 @@ public class QSMediaBrowser { /** Delimiter for saved component names */ public static final String DELIMITER = ":"; - private static final String TAG = "QSMediaBrowser"; + private static final String TAG = "ResumeMediaBrowser"; + private boolean mIsEnabled = false; private final Context mContext; private final Callback mCallback; private MediaBrowser mMediaBrowser; @@ -54,21 +58,25 @@ public class QSMediaBrowser { * @param callback used to report media items found * @param componentName Component name of the MediaBrowserService this browser will connect to */ - public QSMediaBrowser(Context context, Callback callback, ComponentName componentName) { + public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName) { + mIsEnabled = Utils.useMediaResumption(context); mContext = context; mCallback = callback; mComponentName = componentName; } /** - * Connects to the MediaBrowserService and looks for valid media. If a media item is returned - * by the service, QSMediaBrowser.Callback#addTrack will be called with its MediaDescription. - * QSMediaBrowser.Callback#onConnected and QSMediaBrowser.Callback#onError will also be called - * when the initial connection is successful, or an error occurs. Note that it is possible for - * the service to connect but for no playable tracks to be found later. - * QSMediaBrowser#disconnect will be called automatically with this function. + * Connects to the MediaBrowserService and looks for valid media. If a media item is returned, + * ResumeMediaBrowser.Callback#addTrack will be called with the MediaDescription. + * ResumeMediaBrowser.Callback#onConnected and ResumeMediaBrowser.Callback#onError will also be + * called when the initial connection is successful, or an error occurs. + * Note that it is possible for the service to connect but for no playable tracks to be found. + * ResumeMediaBrowser#disconnect will be called automatically with this function. */ public void findRecentMedia() { + if (!mIsEnabled) { + return; + } Log.d(TAG, "Connecting to " + mComponentName); disconnect(); Bundle rootHints = new Bundle(); @@ -86,7 +94,7 @@ public class QSMediaBrowser { public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) { if (children.size() == 0) { - Log.e(TAG, "No children found for " + mComponentName); + Log.d(TAG, "No children found for " + mComponentName); return; } // We ask apps to return a playable item as the first child when sending @@ -94,23 +102,24 @@ public class QSMediaBrowser { MediaBrowser.MediaItem child = children.get(0); MediaDescription desc = child.getDescription(); if (child.isPlayable()) { - mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), QSMediaBrowser.this); + mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), + ResumeMediaBrowser.this); } else { - Log.e(TAG, "Child found but not playable for " + mComponentName); + Log.d(TAG, "Child found but not playable for " + mComponentName); } disconnect(); } @Override public void onError(String parentId) { - Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId); + Log.d(TAG, "Subscribe error for " + mComponentName + ": " + parentId); mCallback.onError(); disconnect(); } @Override public void onError(String parentId, Bundle options) { - Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId + Log.d(TAG, "Subscribe error for " + mComponentName + ": " + parentId + ", options: " + options); mCallback.onError(); disconnect(); @@ -149,7 +158,7 @@ public class QSMediaBrowser { */ @Override public void onConnectionFailed() { - Log.e(TAG, "Connection failed for " + mComponentName); + Log.d(TAG, "Connection failed for " + mComponentName); mCallback.onError(); disconnect(); } @@ -167,11 +176,15 @@ public class QSMediaBrowser { } /** - * Connects to the MediaBrowserService and starts playback. QSMediaBrowser.Callback#onError or - * QSMediaBrowser.Callback#onConnected will be called depending on whether it was successful. - * QSMediaBrowser#disconnect should be called after this to ensure the connection is closed. + * Connects to the MediaBrowserService and starts playback. + * ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called + * depending on whether it was successful. + * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed. */ public void restart() { + if (!mIsEnabled) { + return; + } disconnect(); Bundle rootHints = new Bundle(); rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true); @@ -224,18 +237,21 @@ public class QSMediaBrowser { /** * Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser. - * QSMediaBrowser.Callback#onError or QSMediaBrowser.Callback#onConnected will be called + * ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called * depending on whether it was successful. - * QSMediaBrowser#disconnect should be called after this to ensure the connection is closed. + * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed. */ public void testConnection() { + if (!mIsEnabled) { + return; + } disconnect(); final MediaBrowser.ConnectionCallback connectionCallback = new MediaBrowser.ConnectionCallback() { @Override public void onConnected() { Log.d(TAG, "connected"); - if (mMediaBrowser.getRoot() == null) { + if (TextUtils.isEmpty(mMediaBrowser.getRoot())) { mCallback.onError(); } else { mCallback.onConnected(); @@ -264,7 +280,7 @@ public class QSMediaBrowser { } /** - * Interface to handle results from QSMediaBrowser + * Interface to handle results from ResumeMediaBrowser */ public static class Callback { /** @@ -286,7 +302,7 @@ public class QSMediaBrowser { * @param browser reference to the browser */ public void addTrack(MediaDescription track, ComponentName component, - QSMediaBrowser browser) { + ResumeMediaBrowser browser) { } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt index 06821cd615a5..75ad06962afe 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt @@ -91,8 +91,14 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { playbackState = state if (shouldPollPlaybackPosition()) { checkPlaybackPosition() + } else if (PlaybackState.STATE_NONE.equals(playbackState)) { + clearController() } } + + override fun onSessionDestroyed() { + clearController() + } } /** Listening state (QS open or closed) is used to control polling of progress. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 6b0775f6c2d7..1c3b6850afc1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -50,6 +50,8 @@ public class QSContainerImpl extends FrameLayout { private int mSideMargins; private boolean mQsDisabled; + private int mContentPaddingStart = -1; + private int mContentPaddingEnd = -1; public QSContainerImpl(Context context, AttributeSet attrs) { super(context, attrs); @@ -66,10 +68,9 @@ public class QSContainerImpl extends FrameLayout { mBackground = findViewById(R.id.quick_settings_background); mStatusBarBackground = findViewById(R.id.quick_settings_status_bar_background); mBackgroundGradient = findViewById(R.id.quick_settings_gradient_view); - mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); + updateResources(); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - setMargins(); } @Override @@ -103,10 +104,15 @@ public class QSContainerImpl extends FrameLayout { if (navBelow) { maxQs -= getResources().getDimensionPixelSize(R.dimen.navigation_bar_height); } + + int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin + + layoutParams.rightMargin; + final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding, + layoutParams.width); // Measure with EXACTLY. That way, PagedTileLayout will only use excess height and will be // measured last, after other views and padding is accounted for. - mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.EXACTLY)); - int width = mQSPanel.getMeasuredWidth(); + mQSPanel.measure(qsPanelWidthSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.EXACTLY)); + int width = mQSPanel.getMeasuredWidth() + padding; int height = layoutParams.topMargin + layoutParams.bottomMargin + mQSPanel.getMeasuredHeight() + getPaddingBottom(); super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), @@ -148,8 +154,18 @@ public class QSContainerImpl extends FrameLayout { LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams(); layoutParams.topMargin = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.quick_qs_offset_height); - mQSPanel.setLayoutParams(layoutParams); + + mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); + mContentPaddingStart = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin_start); + int newPaddingEnd = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin_end); + boolean marginsChanged = newPaddingEnd != mContentPaddingEnd; + mContentPaddingEnd = newPaddingEnd; + if (marginsChanged) { + updatePaddingsAndMargins(); + } } /** @@ -196,17 +212,32 @@ public class QSContainerImpl extends FrameLayout { updateExpansion(); } - private void setMargins() { - setMargins(mQSDetail); - setMargins(mBackground); - mQSPanel.setMargins(mSideMargins); - mHeader.setMargins(mSideMargins); - } - - private void setMargins(View view) { - FrameLayout.LayoutParams lp = (LayoutParams) view.getLayoutParams(); - lp.rightMargin = mSideMargins; - lp.leftMargin = mSideMargins; + private void updatePaddingsAndMargins() { + for (int i = 0; i < getChildCount(); i++) { + View view = getChildAt(i); + if (view == mStatusBarBackground || view == mBackgroundGradient + || view == mQSCustomizer) { + // Some views are always full width + continue; + } + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + lp.rightMargin = mSideMargins; + lp.leftMargin = mSideMargins; + if (view == mQSPanel) { + // QS panel lays out some of its content full width + mQSPanel.setContentMargins(mContentPaddingStart, mContentPaddingEnd); + } else if (view == mHeader) { + // The header contains the QQS panel which needs to have special padding, to + // visually align them. + mHeader.setContentMargins(mContentPaddingStart, mContentPaddingEnd); + } else { + view.setPaddingRelative( + mContentPaddingStart, + view.getPaddingTop(), + mContentPaddingEnd, + view.getPaddingBottom()); + } + } } private int getDisplayHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 48ba1b99d714..78448785fe2f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -88,6 +88,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private final H mHandler = new H(); private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private final QSTileRevealController mQsTileRevealController; + /** Whether or not the QS media player feature is enabled. */ + protected boolean mUsingMediaPlayer; protected boolean mExpanded; protected boolean mListening; @@ -102,6 +104,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected QSSecurityFooter mFooter; private PageIndicator mFooterPageIndicator; private boolean mGridContentVisible = true; + private int mContentMarginStart; + private int mContentMarginEnd; + private int mVisualTilePadding; protected QSTileLayout mTileLayout; @@ -122,6 +127,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne UiEventLogger uiEventLogger ) { super(context, attrs); + mUsingMediaPlayer = useQsMediaPlayer(context); mMediaHost = mediaHost; mContext = context; mQSLogger = qsLogger; @@ -170,8 +176,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mMediaHost.init(MediaHierarchyManager.LOCATION_QS); ViewGroup hostView = mMediaHost.getHostView(); addView(hostView); - int sidePaddings = getResources().getDimensionPixelSize( - R.dimen.quick_settings_side_margins); int bottomPadding = getResources().getDimensionPixelSize( R.dimen.quick_settings_expanded_bottom_margin); MarginLayoutParams layoutParams = (MarginLayoutParams) hostView.getLayoutParams(); @@ -179,8 +183,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; layoutParams.bottomMargin = bottomPadding; hostView.setLayoutParams(layoutParams); - hostView.setPadding(sidePaddings, hostView.getPaddingTop(), sidePaddings, - hostView.getPaddingBottom()); + updateMediaHostContentMargins(); } protected void addDivider() { @@ -359,8 +362,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } public void updateResources() { - final Resources res = mContext.getResources(); - setPadding(0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_top), 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom)); + int tileSize = getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); + int tileBg = getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size); + mVisualTilePadding = (int) ((tileSize - tileBg) / 2.0f); + updatePadding(); updatePageIndicator(); @@ -372,6 +377,14 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } } + protected void updatePadding() { + final Resources res = mContext.getResources(); + setPaddingRelative(getPaddingStart(), + res.getDimensionPixelSize(R.dimen.qs_panel_padding_top), + getPaddingEnd(), + res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom)); + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -723,17 +736,51 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mFooter.showDeviceMonitoringDialog(); } - public void setMargins(int sideMargins) { - for (int i = 0; i < getChildCount(); i++) { - View view = getChildAt(i); - if (view != mTileLayout) { - LayoutParams lp = (LayoutParams) view.getLayoutParams(); - lp.leftMargin = sideMargins; - lp.rightMargin = sideMargins; - } + public void setContentMargins(int startMargin, int endMargin) { + // Only some views actually want this content padding, others want to go all the way + // to the edge like the brightness slider + mContentMarginStart = startMargin; + mContentMarginEnd = endMargin; + updateTileLayoutMargins(mContentMarginStart - mVisualTilePadding, + mContentMarginEnd - mVisualTilePadding); + updateMediaHostContentMargins(); + } + + /** + * Update the margins of all tile Layouts. + * + * @param visualMarginStart the visual start margin of the tile, adjusted for local insets + * to the tile. This can be set on a tileLayout + * @param visualMarginEnd the visual end margin of the tile, adjusted for local insets + * to the tile. This can be set on a tileLayout + */ + protected void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { + updateMargins((View) mTileLayout, visualMarginStart, visualMarginEnd); + } + + /** + * Update the margins of the media hosts + */ + protected void updateMediaHostContentMargins() { + if (mUsingMediaPlayer && mMediaHost != null) { + updateMargins(mMediaHost.getHostView(), mContentMarginStart, mContentMarginEnd); } } + /** + * Update the margins of a view. + * + * @param view the view to adjust + * @param start the start margin to set + * @param end the end margin to set + */ + protected void updateMargins(View view, int start, int end) { + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + lp.setMarginStart(start); + lp.setMarginEnd(end); + view.setLayoutParams(lp); + } + public MediaHost getMediaHost() { return mMediaHost; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 75507beba7ae..94b4cee92965 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -41,7 +41,6 @@ import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; -import com.android.systemui.util.Utils; import java.util.ArrayList; import java.util.Collection; @@ -62,8 +61,6 @@ public class QuickQSPanel extends QSPanel { private boolean mDisabledByPolicy; private int mMaxTiles; protected QSPanel mFullPanel; - /** Whether or not the QS media player feature is enabled. */ - private boolean mUsingMediaPlayer; /** Whether or not the QuickQSPanel currently contains a media player. */ private boolean mShowHorizontalTileLayout; private LinearLayout mHorizontalLinearLayout; @@ -84,8 +81,7 @@ public class QuickQSPanel extends QSPanel { MediaHost mediaHost, UiEventLogger uiEventLogger ) { - super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, - uiEventLogger); + super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger); if (mFooter != null) { removeView(mFooter.getView()); } @@ -97,8 +93,6 @@ public class QuickQSPanel extends QSPanel { } mMediaBottomMargin = getResources().getDimensionPixelSize( R.dimen.quick_settings_media_extra_bottom_margin); - - mUsingMediaPlayer = Utils.useQsMediaPlayer(context); if (mUsingMediaPlayer) { mHorizontalLinearLayout = new LinearLayout(mContext); mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL); @@ -132,7 +126,6 @@ public class QuickQSPanel extends QSPanel { mHorizontalLinearLayout.setVisibility(useHorizontal ? View.VISIBLE : View.GONE); addView((View) mRegularTileLayout, 0); super.setPadding(0, 0, 0, 0); - applySideMargins(mHorizontalLinearLayout); applyBottomMargin((View) mRegularTileLayout); } else { sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); @@ -151,14 +144,6 @@ public class QuickQSPanel extends QSPanel { view.setLayoutParams(layoutParams); } - private void applySideMargins(View view) { - int margin = getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_horizontal); - MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams(); - layoutParams.setMarginStart(margin); - layoutParams.setMarginEnd(margin); - view.setLayoutParams(layoutParams); - } - private void reAttachMediaHost() { if (mMediaHost == null) { return; @@ -177,10 +162,6 @@ public class QuickQSPanel extends QSPanel { layoutParams.width = horizontal ? 0 : ViewGroup.LayoutParams.MATCH_PARENT; layoutParams.weight = horizontal ? 1.5f : 0; layoutParams.bottomMargin = mMediaBottomMargin; - int marginStart = horizontal - ? getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_horizontal) - : 0; - layoutParams.setMarginStart(marginStart); } } @@ -194,11 +175,22 @@ public class QuickQSPanel extends QSPanel { mMediaHost.setShowsOnlyActiveMedia(true); mMediaHost.init(MediaHierarchyManager.LOCATION_QQS); reAttachMediaHost(); + updateMediaHostContentMargins(); + } + + @Override + protected void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) { + if (mUsingMediaPlayer) { + updateMargins((View) mRegularTileLayout, visualMarginStart, visualMarginEnd); + updateMargins((View) mHorizontalTileLayout, visualMarginStart, 0); + } else { + updateMargins((View) mTileLayout, visualMarginStart, visualMarginEnd); + } } @Override - public void setPadding(int left, int top, int right, int bottom) { - // Always have no padding. + protected void updatePadding() { + // QS Panel is setting a top padding by default, which we don't need. } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 29b3436c0b72..20e47b2f2fa9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -42,6 +42,7 @@ import android.view.DisplayCutout; import android.view.View; import android.view.WindowInsets; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; @@ -143,6 +144,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements private boolean mHasTopCutout = false; private int mRoundedCornerPadding = 0; + private int mContentMarginStart; + private int mContentMarginEnd; + private int mWaterfallTopInset; + private int mCutOutPaddingLeft; + private int mCutOutPaddingRight; @Inject public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, @@ -422,33 +428,42 @@ public class QuickStatusBarHeader extends RelativeLayout implements @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - // Handle padding of QuickStatusBarHeader - setPadding(mRoundedCornerPadding, getPaddingTop(), mRoundedCornerPadding, - getPaddingBottom()); - - // Handle padding of SystemIconsView + // Handle padding of the clock DisplayCutout cutout = insets.getDisplayCutout(); Pair<Integer, Integer> cornerCutoutPadding = StatusBarWindowView.cornerCutoutMargins( cutout, getDisplay()); Pair<Integer, Integer> padding = StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner( - cutout, cornerCutoutPadding, mRoundedCornerPadding); - final int waterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top; - int statusBarPaddingLeft = isLayoutRtl() - ? getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end) - : getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start); - int statusBarPaddingRight = isLayoutRtl() - ? getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start) - : getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end); - mSystemIconsView.setPadding( - Math.max(padding.first + statusBarPaddingLeft - mRoundedCornerPadding, 0), - waterfallTopInset, - Math.max(padding.second + statusBarPaddingRight - mRoundedCornerPadding, 0), - 0); - + cutout, cornerCutoutPadding, -1); + mCutOutPaddingLeft = padding.first; + mCutOutPaddingRight = padding.second; + mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top; + updateClockPadding(); return super.onApplyWindowInsets(insets); } + private void updateClockPadding() { + int clockPaddingLeft = 0; + int clockPaddingRight = 0; + // The clock might collide with cutouts, let's shift it out of the way. + // We only do that if the inset is bigger than our own padding, since it's nicer to + // align with + if (mCutOutPaddingLeft > 0) { + // if there's a cutout, let's use at least the rounded corner inset + int cutoutPadding = Math.max(mCutOutPaddingLeft, mRoundedCornerPadding); + int contentMarginLeft = isLayoutRtl() ? mContentMarginEnd : mContentMarginStart; + clockPaddingLeft = Math.max(cutoutPadding - contentMarginLeft, 0); + } + if (mCutOutPaddingRight > 0) { + // if there's a cutout, let's use at least the rounded corner inset + int cutoutPadding = Math.max(mCutOutPaddingRight, mRoundedCornerPadding); + int contentMarginRight = isLayoutRtl() ? mContentMarginStart : mContentMarginEnd; + clockPaddingRight = Math.max(cutoutPadding - contentMarginRight, 0); + } + + mSystemIconsView.setPadding(clockPaddingLeft, mWaterfallTopInset, clockPaddingRight, 0); + } + @Override @VisibleForTesting public void onDetachedFromWindow() { @@ -558,24 +573,27 @@ public class QuickStatusBarHeader extends RelativeLayout implements return color == Color.WHITE ? 0 : 1; } - public void setMargins(int sideMargins) { - for (int i = 0; i < getChildCount(); i++) { - View v = getChildAt(i); - // Prevents these views from getting set a margin. - // The Icon views all have the same padding set in XML to be aligned. - if (v == mSystemIconsView || v == mQuickQsStatusIcons || v == mHeaderQsPanel - || v == mHeaderTextContainerView) { - continue; - } - RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) v.getLayoutParams(); - lp.leftMargin = sideMargins; - lp.rightMargin = sideMargins; - } - } - @NonNull @Override public Lifecycle getLifecycle() { return mLifecycle; } + + public void setContentMargins(int marginStart, int marginEnd) { + mContentMarginStart = marginStart; + mContentMarginEnd = marginEnd; + for (int i = 0; i < getChildCount(); i++) { + View view = getChildAt(i); + if (view == mHeaderQsPanel) { + // QS panel doesn't lays out some of its content full width + mHeaderQsPanel.setContentMargins(marginStart, marginEnd); + } else { + MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams(); + lp.setMarginStart(marginStart); + lp.setMarginEnd(marginEnd); + view.setLayoutParams(lp); + } + } + updateClockPadding(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index 098431658e6a..383c29d90a22 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -96,7 +96,6 @@ public class TileLayout extends ViewGroup implements QSTileLayout { mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top); - mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side); mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows)); if (mLessRows) mMaxAllowedRows = Math.max(1, mMaxAllowedRows - 1); if (mColumns != columns) { @@ -120,7 +119,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { mRows = (numTiles + mColumns - 1) / mColumns; } mCellWidth = - (availableWidth - mSidePadding * 2 - (mCellMarginHorizontal * mColumns)) / mColumns; + (availableWidth - (mCellMarginHorizontal * mColumns)) / mColumns; // Measure each QS tile. View previousView = this; @@ -204,7 +203,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { } protected int getColumnStart(int column) { - return getPaddingStart() + mSidePadding + mCellMarginHorizontal / 2 + + return getPaddingStart() + mCellMarginHorizontal / 2 + column * (mCellWidth + mCellMarginHorizontal); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index b272b60f3593..baa2dfdcebf3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -64,13 +64,16 @@ import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActiv import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.util.ScreenshotHelper; import com.android.systemui.Dumpable; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.model.SysUiState; import com.android.systemui.pip.PipAnimationController; import com.android.systemui.pip.PipUI; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; +import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.recents.ISystemUiProxy; +import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.stackdivider.Divider; @@ -83,8 +86,6 @@ import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -101,8 +102,9 @@ import dagger.Lazy; * Class to send information from overview to launcher with a binder. */ @Singleton -public class OverviewProxyService implements CallbackController<OverviewProxyListener>, - NavigationModeController.ModeChangedListener, Dumpable { +public class OverviewProxyService extends CurrentUserTracker implements + CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener, + Dumpable { private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE"; @@ -123,7 +125,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final NotificationShadeWindowController mStatusBarWinController; private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser; private final ComponentName mRecentsComponentName; - private final DeviceProvisionedController mDeviceProvisionedController; private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>(); private final Intent mQuickStepIntent; private final ScreenshotHelper mScreenshotHelper; @@ -383,8 +384,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen, Insets visibleInsets, int taskId) { - mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets, - taskId, SCREENSHOT_OVERVIEW, mHandler, null); + // Deprecated } @Override @@ -434,6 +434,21 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + @Override + public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen, + Insets visibleInsets, Task.TaskKey task) { + mScreenshotHelper.provideScreenshot( + screenImageBundle, + locationInScreen, + visibleInsets, + task.id, + task.userId, + task.sourceComponent, + SCREENSHOT_OVERVIEW, + mHandler, + null); + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -480,7 +495,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis return; } - mCurrentBoundedUserId = mDeviceProvisionedController.getCurrentUser(); + mCurrentBoundedUserId = getCurrentUserId(); mOverviewProxy = IOverviewProxy.Stub.asInterface(service); Bundle params = new Bundle(); @@ -523,22 +538,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } }; - private final DeviceProvisionedListener mDeviceProvisionedCallback = - new DeviceProvisionedListener() { - @Override - public void onUserSetupChanged() { - if (mDeviceProvisionedController.isCurrentUserSetup()) { - internalConnectToCurrentUser(); - } - } - - @Override - public void onUserSwitched() { - mConnectionBackoffAttempts = 0; - internalConnectToCurrentUser(); - } - }; - private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged; // This is the death handler for the binder from the launcher service @@ -548,18 +547,18 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject public OverviewProxyService(Context context, CommandQueue commandQueue, - DeviceProvisionedController provisionController, NavigationBarController navBarController, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, PipUI pipUI, Optional<Divider> dividerOptional, - Optional<Lazy<StatusBar>> statusBarOptionalLazy) { + Optional<Lazy<StatusBar>> statusBarOptionalLazy, + BroadcastDispatcher broadcastDispatcher) { + super(broadcastDispatcher); mContext = context; mPipUI = pipUI; mStatusBarOptionalLazy = statusBarOptionalLazy; mHandler = new Handler(); mNavBarController = navBarController; mStatusBarWinController = statusBarWinController; - mDeviceProvisionedController = provisionController; mConnectionBackoffAttempts = 0; mDividerOptional = dividerOptional; mRecentsComponentName = ComponentName.unflattenFromString(context.getString( @@ -580,7 +579,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // Listen for device provisioned/user setup updateEnabledState(); - mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback); + startTracking(); // Listen for launcher package changes IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); @@ -604,6 +603,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis }); } + @Override + public void onUserSwitched(int newUserId) { + mConnectionBackoffAttempts = 0; + internalConnectToCurrentUser(); + } + public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton, boolean gestureSwipeLeft) { try { @@ -709,10 +714,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis disconnectFromLauncherService(); // If user has not setup yet or already connected, do not try to connect - if (!mDeviceProvisionedController.isCurrentUserSetup() || !isEnabled()) { - Log.v(TAG_OPS, "Cannot attempt connection, is setup " - + mDeviceProvisionedController.isCurrentUserSetup() + ", is enabled " - + isEnabled()); + if (!isEnabled()) { + Log.v(TAG_OPS, "Cannot attempt connection, is enabled " + isEnabled()); return; } mHandler.removeCallbacks(mConnectionRunnable); @@ -722,7 +725,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mBound = mContext.bindServiceAsUser(launcherServiceIntent, mOverviewServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, - UserHandle.of(mDeviceProvisionedController.getCurrentUser())); + UserHandle.of(getCurrentUserId())); } catch (SecurityException e) { Log.e(TAG_OPS, "Unable to bind because of security error", e); } @@ -881,8 +884,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis pw.println(TAG_OPS + " state:"); pw.print(" recentsComponentName="); pw.println(mRecentsComponentName); pw.print(" isConnected="); pw.println(mOverviewProxy != null); - pw.print(" isCurrentUserSetup="); pw.println(mDeviceProvisionedController - .isCurrentUserSetup()); pw.print(" connectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts); pw.print(" quickStepIntent="); pw.println(mQuickStepIntent); diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 960c50129a56..3e268f63d65e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -225,8 +225,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis res.getString(R.string.screenrecord_name)); String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE - ? res.getString(R.string.screenrecord_ongoing_screen_and_audio) - : res.getString(R.string.screenrecord_ongoing_screen_only); + ? res.getString(R.string.screenrecord_ongoing_screen_only) + : res.getString(R.string.screenrecord_ongoing_screen_and_audio); mRecordingNotificationBuilder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_screenrecord) diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java index 752f4fddf24b..edbc3cfdece5 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java @@ -38,6 +38,7 @@ import java.nio.ByteBuffer; public class ScreenInternalAudioRecorder { private static String TAG = "ScreenAudioRecorder"; private static final int TIMEOUT = 500; + private static final float MIC_VOLUME_SCALE = 1.4f; private final Context mContext; private AudioRecord mAudioRecord; private AudioRecord mAudioRecordMic; @@ -148,6 +149,10 @@ public class ScreenInternalAudioRecorder { readShortsInternal = mAudioRecord.read(bufferInternal, 0, bufferInternal.length); readShortsMic = mAudioRecordMic.read(bufferMic, 0, bufferMic.length); + + // modify the volume + bufferMic = scaleValues(bufferMic, + readShortsMic, MIC_VOLUME_SCALE); readBytes = Math.min(readShortsInternal, readShortsMic) * 2; buffer = addAndConvertBuffers(bufferInternal, readShortsInternal, bufferMic, readShortsMic); @@ -168,6 +173,19 @@ public class ScreenInternalAudioRecorder { }); } + private short[] scaleValues(short[] buff, int len, float scale) { + for (int i = 0; i < len; i++) { + int oldValue = buff[i]; + int newValue = (int) (buff[i] * scale); + if (newValue > Short.MAX_VALUE) { + newValue = Short.MAX_VALUE; + } else if (newValue < Short.MIN_VALUE) { + newValue = Short.MIN_VALUE; + } + buff[i] = (short) (newValue); + } + return buff; + } private byte[] addAndConvertBuffers(short[] a1, int a1Limit, short[] a2, int a2Limit) { int size = Math.max(a1Limit, a2Limit); if (size < 0) return new byte[0]; diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index c967648c544e..8551c88d133a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -55,9 +55,9 @@ import java.util.Date; */ public class ScreenMediaRecorder { private static final int TOTAL_NUM_TRACKS = 1; - private static final int VIDEO_BIT_RATE = 10000000; private static final int VIDEO_FRAME_RATE = 30; - private static final int AUDIO_BIT_RATE = 16; + private static final int VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO = 6; + private static final int AUDIO_BIT_RATE = 196000; private static final int AUDIO_SAMPLE_RATE = 44100; private static final int MAX_DURATION_MS = 60 * 60 * 1000; private static final long MAX_FILESIZE_BYTES = 5000000000L; @@ -108,7 +108,7 @@ public class ScreenMediaRecorder { // Set up audio source if (mAudioSource == MIC) { - mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); } mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); @@ -121,10 +121,13 @@ public class ScreenMediaRecorder { wm.getDefaultDisplay().getRealMetrics(metrics); int screenWidth = metrics.widthPixels; int screenHeight = metrics.heightPixels; + int refereshRate = (int) wm.getDefaultDisplay().getRefreshRate(); + int vidBitRate = screenHeight * screenWidth * refereshRate / VIDEO_FRAME_RATE + * VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO; mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mMediaRecorder.setVideoSize(screenWidth, screenHeight); - mMediaRecorder.setVideoFrameRate(VIDEO_FRAME_RATE); - mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE); + mMediaRecorder.setVideoFrameRate(refereshRate); + mMediaRecorder.setVideoEncodingBitRate(vidBitRate); mMediaRecorder.setMaxDuration(MAX_DURATION_MS); mMediaRecorder.setMaxFileSize(MAX_FILESIZE_BYTES); diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index abd7e7159260..d057a8a43c43 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -24,12 +24,9 @@ import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.NONE; import android.app.Activity; import android.app.PendingIntent; import android.os.Bundle; -import android.util.Log; import android.view.Gravity; -import android.view.View; import android.view.ViewGroup; import android.view.Window; -import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Spinner; @@ -88,8 +85,8 @@ public class ScreenRecordDialog extends Activity { }); mModes = new ArrayList<>(); - mModes.add(INTERNAL); mModes.add(MIC); + mModes.add(INTERNAL); mModes.add(MIC_AND_INTERNAL); mAudioSwitch = findViewById(R.id.screenrecord_audio_switch); diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java index 2e0e746594b4..3e78489e5707 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordingAdapter.java @@ -88,12 +88,6 @@ public class ScreenRecordingAdapter extends ArrayAdapter<ScreenRecordingAudioSou return layout; } - private void setDescription(LinearLayout layout, int description) { - if (description != Resources.ID_NULL) { - ((TextView) layout.getChildAt(1)).setText(description); - } - } - @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { switch (getItem(position)) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index a9d3772a0fdb..9b1734d40674 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -35,6 +35,7 @@ import android.app.ActivityOptions; import android.app.Notification; import android.app.PendingIntent; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -494,8 +495,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, - Insets visibleInsets, int taskId, Consumer<Uri> finisher, Runnable onComplete) { - // TODO use taskId and visibleInsets + Insets visibleInsets, int taskId, int userId, ComponentName topComponent, + Consumer<Uri> finisher, Runnable onComplete) { + // TODO: use task Id, userId, topComponent for smart handler + // TODO: use visibleInsets for animation mOnCompleteRunnable = onComplete; takeScreenshot(screenshot, finisher, screenshotScreenBounds); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java index 095c32f4a2ce..0017b1f79b74 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java @@ -24,6 +24,7 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -234,8 +235,10 @@ public class GlobalScreenshotLegacy { } void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, - Insets visibleInsets, int taskId, Consumer<Uri> finisher) { - // TODO use taskId and visibleInsets + Insets visibleInsets, int taskId, int userId, ComponentName topComponent, + Consumer<Uri> finisher) { + // TODO: use task Id, userId, topComponent for smart handler + // TODO: use visibleInsets for animation takeScreenshot(screenshot, finisher, false, false, screenshotScreenBounds); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 98030d45b05e..8322fe08d3c2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -20,6 +20,7 @@ import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_ import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI; import android.app.Service; +import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Insets; @@ -37,6 +38,7 @@ import android.view.WindowManager; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ScreenshotHelper; +import com.android.systemui.shared.recents.utilities.BitmapUtil; import java.util.function.Consumer; @@ -107,16 +109,19 @@ public class TakeScreenshotService extends Service { } break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: - Bitmap screenshot = screenshotRequest.getBitmap(); + Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap( + screenshotRequest.getBitmapBundle()); Rect screenBounds = screenshotRequest.getBoundsInScreen(); Insets insets = screenshotRequest.getInsets(); int taskId = screenshotRequest.getTaskId(); + int userId = screenshotRequest.getUserId(); + ComponentName topComponent = screenshotRequest.getTopComponent(); if (useCornerFlow) { - mScreenshot.handleImageAsScreenshot( - screenshot, screenBounds, insets, taskId, uriConsumer, onComplete); + mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets, + taskId, userId, topComponent, uriConsumer, onComplete); } else { - mScreenshotLegacy.handleImageAsScreenshot( - screenshot, screenBounds, insets, taskId, uriConsumer); + mScreenshotLegacy.handleImageAsScreenshot(screenshot, screenBounds, insets, + taskId, userId, topComponent, uriConsumer); } break; default: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 217148df60e2..5628a24f40ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -52,7 +52,6 @@ import com.android.systemui.Interpolators; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; -import com.android.systemui.media.MediaDeviceManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.notification.NotificationEntryListener; @@ -102,6 +101,12 @@ public class NotificationMediaManager implements Dumpable { PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED); PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR); } + private static final HashSet<Integer> INACTIVE_MEDIA_STATES = new HashSet<>(); + static { + INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_NONE); + INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_STOPPED); + INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_ERROR); + } private final NotificationEntryManager mEntryManager; private final MediaDataManager mMediaDataManager; @@ -190,8 +195,7 @@ public class NotificationMediaManager implements Dumpable { KeyguardBypassController keyguardBypassController, @Main DelayableExecutor mainExecutor, DeviceConfigProxy deviceConfig, - MediaDataManager mediaDataManager, - MediaDeviceManager mediaDeviceManager) { + MediaDataManager mediaDataManager) { mContext = context; mMediaArtworkProcessor = mediaArtworkProcessor; mKeyguardBypassController = keyguardBypassController; @@ -212,13 +216,11 @@ public class NotificationMediaManager implements Dumpable { @Override public void onPendingEntryAdded(NotificationEntry entry) { mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); - mediaDeviceManager.onNotificationAdded(entry.getKey(), entry.getSbn()); } @Override public void onPreEntryUpdated(NotificationEntry entry) { mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn()); - mediaDeviceManager.onNotificationAdded(entry.getKey(), entry.getSbn()); } @Override @@ -239,7 +241,6 @@ public class NotificationMediaManager implements Dumpable { int reason) { onNotificationRemoved(entry.getKey()); mediaDataManager.onNotificationRemoved(entry.getKey()); - mediaDeviceManager.onNotificationRemoved(entry.getKey()); } }); @@ -252,10 +253,24 @@ public class NotificationMediaManager implements Dumpable { mPropertiesChangedListener); } + /** + * Check if a state should be considered actively playing + * @param state a PlaybackState + * @return true if playing + */ public static boolean isPlayingState(int state) { return !PAUSED_MEDIA_STATES.contains(state); } + /** + * Check if a state should be considered active (playing or paused) + * @param state a PlaybackState + * @return true if active + */ + public static boolean isActiveState(int state) { + return !INACTIVE_MEDIA_STATES.contains(state); + } + public void setUpWithPresenter(NotificationPresenter presenter) { mPresenter = presenter; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index c988e1251d3f..84c8db3218e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -24,7 +24,6 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.media.MediaDataManager; -import com.android.systemui.media.MediaDeviceManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; @@ -51,8 +50,6 @@ import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.concurrency.DelayableExecutor; -import java.util.concurrent.Executor; - import javax.inject.Singleton; import dagger.Lazy; @@ -105,8 +102,7 @@ public interface StatusBarDependenciesModule { KeyguardBypassController keyguardBypassController, @Main DelayableExecutor mainExecutor, DeviceConfigProxy deviceConfigProxy, - MediaDataManager mediaDataManager, - MediaDeviceManager mediaDeviceManager) { + MediaDataManager mediaDataManager) { return new NotificationMediaManager( context, statusBarLazy, @@ -116,8 +112,7 @@ public interface StatusBarDependenciesModule { keyguardBypassController, mainExecutor, deviceConfigProxy, - mediaDataManager, - mediaDeviceManager); + mediaDataManager); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java index 9dcc187cb0ef..87612f15ed3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java @@ -52,7 +52,7 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon private NotificationGuts mGutsContainer; private OnClickListener mOnOk = v -> { - closeControls(v); + mGutsContainer.closeControls(v, false); }; public AppOpsInfo(Context context, AttributeSet attrs) { @@ -117,6 +117,7 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon }); TextView ok = findViewById(R.id.ok); ok.setOnClickListener(mOnOk); + ok.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate()); } private String getPrompt() { @@ -160,19 +161,6 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon } } - private void closeControls(View v) { - mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false); - int[] parentLoc = new int[2]; - int[] targetLoc = new int[2]; - mGutsContainer.getLocationOnScreen(parentLoc); - v.getLocationOnScreen(targetLoc); - final int centerX = v.getWidth() / 2; - final int centerY = v.getHeight() / 2; - final int x = targetLoc[0] - parentLoc[0] + centerX; - final int y = targetLoc[1] - parentLoc[1] + centerY; - mGutsContainer.closeControls(x, y, false, false); - } - @Override public void setGutsParent(NotificationGuts guts) { mGutsContainer = guts; @@ -200,6 +188,7 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon @Override public boolean handleCloseControls(boolean save, boolean force) { + mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index f7ad50edb2f6..94e12e82f850 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1122,9 +1122,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public void setGutsView(MenuItem item) { - if (mGuts != null && item.getGutsView() instanceof NotificationGuts.GutsContent) { - ((NotificationGuts.GutsContent) item.getGutsView()).setGutsParent(mGuts); - mGuts.setGutsContent((NotificationGuts.GutsContent) item.getGutsView()); + if (getGuts() != null && item.getGutsView() instanceof NotificationGuts.GutsContent) { + getGuts().setGutsContent((NotificationGuts.GutsContent) item.getGutsView()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 9925909c3e16..8a4fdc24dc1b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -557,9 +557,9 @@ public class NotificationContentView extends FrameLayout { private void focusExpandButtonIfNecessary() { if (mFocusOnVisibilityChange) { - NotificationHeaderView header = getVisibleNotificationHeader(); - if (header != null) { - ImageView expandButton = header.getExpandButton(); + NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType); + if (wrapper != null) { + View expandButton = wrapper.getExpandButton(); if (expandButton != null) { expandButton.requestAccessibilityFocus(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 2e7b17587085..e9d89589172e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -53,6 +53,7 @@ import android.transition.TransitionManager; import android.transition.TransitionSet; import android.util.AttributeSet; import android.util.Log; +import android.util.Slog; import android.view.LayoutInflater; import android.view.View; import android.view.accessibility.AccessibilityEvent; @@ -115,6 +116,7 @@ public class NotificationConversationInfo extends LinearLayout implements private OnSnoozeClickListener mOnSnoozeClickListener; private OnSettingsClickListener mOnSettingsClickListener; private NotificationGuts mGutsContainer; + private OnConversationSettingsClickListener mOnConversationSettingsClickListener; @VisibleForTesting boolean mSkipPost = false; @@ -137,13 +139,13 @@ public class NotificationConversationInfo extends LinearLayout implements mSelectedAction = ACTION_HOME; mShortcutManager.requestPinShortcut(mShortcutInfo, null); mShadeController.animateCollapsePanels(); - closeControls(v, true); + mGutsContainer.closeControls(v, true); }; private OnClickListener mOnSnoozeClick = v -> { mSelectedAction = ACTION_SNOOZE; mOnSnoozeClickListener.onClick(v, 1); - closeControls(v, true); + mGutsContainer.closeControls(v, true); }; */ @@ -164,7 +166,11 @@ public class NotificationConversationInfo extends LinearLayout implements private OnClickListener mOnDone = v -> { mPressedApply = true; - closeControls(v, true); + // If the user selected Priority, maybe show the priority onboarding + if (mSelectedAction == ACTION_FAVORITE && shouldShowPriorityOnboarding()) { + showPriorityOnboarding(); + } + mGutsContainer.closeControls(v, true); }; public NotificationConversationInfo(Context context, AttributeSet attrs) { @@ -175,6 +181,10 @@ public class NotificationConversationInfo extends LinearLayout implements void onClick(View v, NotificationChannel channel, int appUid); } + public interface OnConversationSettingsClickListener { + void onClick(); + } + public interface OnAppSettingsClickListener { void onClick(View v, Intent intent); } @@ -190,14 +200,6 @@ public class NotificationConversationInfo extends LinearLayout implements } mSelectedAction = selectedAction; - onSelectedActionChanged(); - } - - private void onSelectedActionChanged() { - // If the user selected Priority, maybe show the priority onboarding - if (mSelectedAction == ACTION_FAVORITE && shouldShowPriorityOnboarding()) { - showPriorityOnboarding(); - } } public void bindNotification( @@ -216,7 +218,8 @@ public class NotificationConversationInfo extends LinearLayout implements Provider<PriorityOnboardingDialogController.Builder> builderProvider, boolean isDeviceProvisioned, @Main Handler mainHandler, - @Background Handler bgHandler) { + @Background Handler bgHandler, + OnConversationSettingsClickListener onConversationSettingsClickListener) { mSelectedAction = -1; mINotificationManager = iNotificationManager; mVisualStabilityManager = visualStabilityManager; @@ -231,6 +234,7 @@ public class NotificationConversationInfo extends LinearLayout implements mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; mOnSnoozeClickListener = onSnoozeClickListener; + mOnConversationSettingsClickListener = onConversationSettingsClickListener; mIconFactory = conversationIconFactory; mUserContext = userContext; mBubbleMetadata = bubbleMetadata; @@ -258,6 +262,7 @@ public class NotificationConversationInfo extends LinearLayout implements View done = findViewById(R.id.done); done.setOnClickListener(mOnDone); + done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate()); } private void bindActions() { @@ -322,7 +327,6 @@ public class NotificationConversationInfo extends LinearLayout implements ImageView image = findViewById(R.id.conversation_icon); image.setImageDrawable(mIconFactory.getConversationDrawable( mShortcutInfo, mPackageName, mAppUid, important)); - } private void bindPackage() { @@ -520,9 +524,9 @@ public class NotificationConversationInfo extends LinearLayout implements boolean ignoreDnd = false; try { - ignoreDnd = (mINotificationManager - .getConsolidatedNotificationPolicy().priorityConversationSenders - & NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT) != 0; + ignoreDnd = mINotificationManager + .getConsolidatedNotificationPolicy().priorityConversationSenders == + NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT; } catch (RemoteException e) { Log.e(TAG, "Could not check conversation senders", e); } @@ -537,31 +541,14 @@ public class NotificationConversationInfo extends LinearLayout implements .setView(onboardingView) .setIgnoresDnd(ignoreDnd) .setShowsAsBubble(showAsBubble) + .setIcon(((ImageView) findViewById(R.id.conversation_icon)).getDrawable()) + .setOnSettingsClick(mOnConversationSettingsClickListener) .build(); controller.init(); controller.show(); } - /** - * Closes the controls and commits the updated importance values (indirectly). - * - * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the - * user does not have the ability to undo the action anymore. - */ - @VisibleForTesting - void closeControls(View v, boolean save) { - int[] parentLoc = new int[2]; - int[] targetLoc = new int[2]; - mGutsContainer.getLocationOnScreen(parentLoc); - v.getLocationOnScreen(targetLoc); - final int centerX = v.getWidth() / 2; - final int centerY = v.getHeight() / 2; - final int x = targetLoc[0] - parentLoc[0] + centerX; - final int y = targetLoc[1] - parentLoc[1] + centerY; - mGutsContainer.closeControls(x, y, save, false /* force */); - } - @Override public void setGutsParent(NotificationGuts guts) { mGutsContainer = guts; @@ -631,8 +618,7 @@ public class NotificationConversationInfo extends LinearLayout implements try { switch (mAction) { case ACTION_FAVORITE: - mChannelToUpdate.setImportantConversation( - !mChannelToUpdate.isImportantConversation()); + mChannelToUpdate.setImportantConversation(true); if (mChannelToUpdate.isImportantConversation()) { mChannelToUpdate.setAllowBubbles(true); if (mAppBubble == BUBBLE_PREFERENCE_NONE) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java index c762b73a1648..eeac46a60ac8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java @@ -22,12 +22,14 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewAnimationUtils; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import androidx.annotation.Nullable; @@ -59,6 +61,31 @@ public class NotificationGuts extends FrameLayout { private GutsContent mGutsContent; + private View.AccessibilityDelegate mGutsContentAccessibilityDelegate = + new View.AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo( + View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (super.performAccessibilityAction(host, action, args)) { + return true; + } + + switch (action) { + case AccessibilityNodeInfo.ACTION_LONG_CLICK: + closeControls(host, false); + return true; + } + + return false; + } + }; + public interface GutsContent { public void setGutsParent(NotificationGuts listener); @@ -110,6 +137,11 @@ public class NotificationGuts extends FrameLayout { * view on the lockscreen */ boolean needsFalsingProtection(); + + /** + * Equivalent to {@link View#setAccessibilityDelegate(AccessibilityDelegate)} + */ + void setAccessibilityDelegate(AccessibilityDelegate gutsContentAccessibilityDelegate); } public interface OnGutsClosedListener { @@ -146,6 +178,8 @@ public class NotificationGuts extends FrameLayout { } public void setGutsContent(GutsContent content) { + content.setGutsParent(this); + content.setAccessibilityDelegate(mGutsContentAccessibilityDelegate); mGutsContent = content; removeAllViews(); addView(mGutsContent.getContentView()); @@ -237,13 +271,29 @@ public class NotificationGuts extends FrameLayout { /** * Closes any exposed guts/views. + */ + public void closeControls(View eventSource, boolean save) { + int[] parentLoc = new int[2]; + int[] targetLoc = new int[2]; + getLocationOnScreen(parentLoc); + eventSource.getLocationOnScreen(targetLoc); + final int centerX = eventSource.getWidth() / 2; + final int centerY = eventSource.getHeight() / 2; + final int x = targetLoc[0] - parentLoc[0] + centerX; + final int y = targetLoc[1] - parentLoc[1] + centerY; + + closeControls(x, y, save, false); + } + + /** + * Closes any exposed guts/views. * * @param x x coordinate to animate the close circular reveal with * @param y y coordinate to animate the close circular reveal with * @param save whether the state should be saved * @param force whether the guts should be force-closed regardless of state. */ - public void closeControls(int x, int y, boolean save, boolean force) { + private void closeControls(int x, int y, boolean save, boolean force) { // First try to dismiss any blocking helper. boolean wasBlockingHelperDismissed = Dependency.get(NotificationBlockingHelperManager.class) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index a64dcdffff1e..1074adc3383d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -216,6 +216,11 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx } } + private void startConversationSettingsActivity(int uid, ExpandableNotificationRow row) { + final Intent intent = new Intent(Settings.ACTION_CONVERSATION_SETTINGS); + mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row); + } + private boolean bindGuts(final ExpandableNotificationRow row) { row.ensureGutsInflated(); return bindGuts(row, mGutsMenuItem); @@ -438,6 +443,12 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mListContainer.getSwipeActionHelper().snooze(sbn, hours); }; + final NotificationConversationInfo.OnConversationSettingsClickListener + onConversationSettingsListener = + () -> { + startConversationSettingsActivity(sbn.getUid(), row); + }; + if (!userHandle.equals(UserHandle.ALL) || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) { onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { @@ -468,7 +479,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mBuilderProvider, mDeviceProvisionedController.isDeviceProvisioned(), mMainHandler, - mBgHandler); + mBgHandler, + onConversationSettingsListener); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index f434fbbcd916..a131ebef77db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -141,7 +141,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G // used by standard ui private OnClickListener mOnDismissSettings = v -> { mPressedApply = true; - closeControls(v, true); + mGutsContainer.closeControls(v, true); }; public NotificationInfo(Context context, AttributeSet attrs) { @@ -250,7 +250,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G View done = findViewById(R.id.done); done.setOnClickListener(mOnDismissSettings); - + done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate()); View silent = findViewById(R.id.silence); View alert = findViewById(R.id.alert); @@ -330,7 +330,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mUniqueChannelsInRow, mPkgIcon, mOnSettingsClickListener); mChannelEditorDialogController.setOnFinishListener(() -> { mPresentingChannelEditorDialog = false; - closeControls(this, false); + mGutsContainer.closeControls(this, false); }); mChannelEditorDialogController.show(); } @@ -528,25 +528,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G return intent; } - /** - * Closes the controls and commits the updated importance values (indirectly). - * - * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the - * user does not have the ability to undo the action anymore. - */ - @VisibleForTesting - void closeControls(View v, boolean save) { - int[] parentLoc = new int[2]; - int[] targetLoc = new int[2]; - mGutsContainer.getLocationOnScreen(parentLoc); - v.getLocationOnScreen(targetLoc); - final int centerX = v.getWidth() / 2; - final int centerY = v.getHeight() / 2; - final int x = targetLoc[0] - parentLoc[0] + centerX; - final int y = targetLoc[1] - parentLoc[1] + centerY; - mGutsContainer.closeControls(x, y, save, false /* force */); - } - @Override public void setGutsParent(NotificationGuts guts) { mGutsContainer = guts; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java index cde3dfd66aaf..1ffb244b51c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java @@ -380,16 +380,8 @@ public class NotificationSnooze extends LinearLayout private void undoSnooze(View v) { mSelectedOption = null; - int[] parentLoc = new int[2]; - int[] targetLoc = new int[2]; - mGutsContainer.getLocationOnScreen(parentLoc); - v.getLocationOnScreen(targetLoc); - final int centerX = v.getWidth() / 2; - final int centerY = v.getHeight() / 2; - final int x = targetLoc[0] - parentLoc[0] + centerX; - final int y = targetLoc[1] - parentLoc[1] + centerY; showSnoozeOptions(false); - mGutsContainer.closeControls(x, y, false /* save */, false /* force */); + mGutsContainer.closeControls(v, false); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java index eb28c58d95b7..f1fe54ad4024 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java @@ -89,7 +89,7 @@ public class PartialConversationInfo extends LinearLayout implements private OnClickListener mOnDone = v -> { mPressedApply = true; - closeControls(v, true); + mGutsContainer.closeControls(v, true); }; public PartialConversationInfo(Context context, AttributeSet attrs) { @@ -132,6 +132,7 @@ public class PartialConversationInfo extends LinearLayout implements View done = findViewById(R.id.done); done.setOnClickListener(mOnDone); + done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate()); } private void bindActions() { @@ -172,7 +173,7 @@ public class PartialConversationInfo extends LinearLayout implements mUniqueChannelsInRow, mPkgIcon, mOnSettingsClickListener); mChannelEditorDialogController.setOnFinishListener(() -> { mPresentingChannelEditorDialog = false; - closeControls(this, false); + mGutsContainer.closeControls(this, false); }); mChannelEditorDialogController.show(); } @@ -240,7 +241,6 @@ public class PartialConversationInfo extends LinearLayout implements } catch (PackageManager.NameNotFoundException e) { mPkgIcon = mPm.getDefaultActivityIcon(); } - ((TextView) findViewById(R.id.pkg_name)).setText(mAppName); } private void bindDelegate() { @@ -317,25 +317,6 @@ public class PartialConversationInfo extends LinearLayout implements } } - /** - * Closes the controls and commits the updated importance values (indirectly). - * - * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the - * user does not have the ability to undo the action anymore. - */ - @VisibleForTesting - void closeControls(View v, boolean save) { - int[] parentLoc = new int[2]; - int[] targetLoc = new int[2]; - mGutsContainer.getLocationOnScreen(parentLoc); - v.getLocationOnScreen(targetLoc); - final int centerX = v.getWidth() / 2; - final int centerY = v.getHeight() / 2; - final int x = targetLoc[0] - parentLoc[0] + centerX; - final int y = targetLoc[1] - parentLoc[1] + centerY; - mGutsContainer.closeControls(x, y, save, false /* force */); - } - @Override public void setGutsParent(NotificationGuts guts) { mGutsContainer = guts; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt index d1b405256f39..88c325880241 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt @@ -21,19 +21,21 @@ import android.content.Context import android.graphics.Color import android.graphics.PixelFormat import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.text.SpannableStringBuilder +import android.text.style.BulletSpan import android.view.Gravity import android.view.View -import android.view.View.GONE import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.Window import android.view.WindowInsets.Type.statusBars import android.view.WindowManager -import android.widget.LinearLayout +import android.widget.ImageView import android.widget.TextView import com.android.systemui.Prefs import com.android.systemui.R -import java.lang.IllegalStateException +import com.android.systemui.statusbar.notification.row.NotificationConversationInfo.OnConversationSettingsClickListener import javax.inject.Inject /** @@ -43,7 +45,9 @@ class PriorityOnboardingDialogController @Inject constructor( val view: View, val context: Context, val ignoresDnd: Boolean, - val showsAsBubble: Boolean + val showsAsBubble: Boolean, + val icon : Drawable, + val onConversationSettingsClickListener : OnConversationSettingsClickListener ) { private lateinit var dialog: Dialog @@ -62,11 +66,21 @@ class PriorityOnboardingDialogController @Inject constructor( dialog.dismiss() } + private fun settings() { + // Log that the user has seen the onboarding + Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, true) + dialog.dismiss() + onConversationSettingsClickListener?.onClick() + } + class Builder @Inject constructor() { private lateinit var view: View private lateinit var context: Context private var ignoresDnd = false private var showAsBubble = false + private lateinit var icon: Drawable + private lateinit var onConversationSettingsClickListener + : OnConversationSettingsClickListener fun setView(v: View): Builder { view = v @@ -88,9 +102,20 @@ class PriorityOnboardingDialogController @Inject constructor( return this } + fun setIcon(draw : Drawable) : Builder { + icon = draw + return this + } + + fun setOnSettingsClick(onClick : OnConversationSettingsClickListener) : Builder { + onConversationSettingsClickListener = onClick + return this + } + fun build(): PriorityOnboardingDialogController { val controller = PriorityOnboardingDialogController( - view, context, ignoresDnd, showAsBubble) + view, context, ignoresDnd, showAsBubble, icon, + onConversationSettingsClickListener) return controller } } @@ -113,13 +138,32 @@ class PriorityOnboardingDialogController @Inject constructor( done() } - if (!ignoresDnd) { - findViewById<LinearLayout>(R.id.ignore_dnd_tip).visibility = GONE + findViewById<TextView>(R.id.settings_button)?.setOnClickListener { + settings() } - if (!showsAsBubble) { - findViewById<LinearLayout>(R.id.floating_bubble_tip).visibility = GONE + findViewById<ImageView>(R.id.conversation_icon)?.setImageDrawable(icon) + + val gapWidth = dialog.context.getResources().getDimensionPixelSize( + R.dimen.conversation_onboarding_bullet_gap_width) + val description = SpannableStringBuilder() + description.append(context.getText(R.string.priority_onboarding_show_at_top_text), + BulletSpan(gapWidth), /* flags */0) + description.append(System.lineSeparator()) + description.append(context.getText(R.string.priority_onboarding_show_avatar_text), + BulletSpan(gapWidth), /* flags */0) + if (showsAsBubble) { + description.append(System.lineSeparator()) + description.append(context.getText( + R.string.priority_onboarding_appear_as_bubble_text), + BulletSpan(gapWidth), /* flags */0) + } + if (ignoresDnd) { + description.append(System.lineSeparator()) + description.append(context.getText(R.string.priority_onboarding_ignores_dnd_text), + BulletSpan(gapWidth), /* flags */0) } + findViewById<TextView>(R.id.behaviors).setText(description) window?.apply { setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) @@ -129,7 +173,7 @@ class PriorityOnboardingDialogController @Inject constructor( attributes = attributes.apply { format = PixelFormat.TRANSLUCENT - title = ChannelEditorDialogController::class.java.simpleName + title = PriorityOnboardingDialogController::class.java.simpleName gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL fitInsetsTypes = attributes.fitInsetsTypes and statusBars().inv() width = MATCH_PARENT diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt index 15499b87d56d..fe70c818216e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt @@ -50,6 +50,7 @@ class NotificationConversationTemplateViewWrapper constructor( private lateinit var conversationBadgeBg: View private lateinit var expandButton: View private lateinit var expandButtonContainer: View + private lateinit var expandButtonInnerContainer: View private lateinit var imageMessageContainer: ViewGroup private lateinit var messagingLinearLayout: MessagingLinearLayout private lateinit var conversationTitleView: View @@ -69,6 +70,8 @@ class NotificationConversationTemplateViewWrapper constructor( expandButton = requireViewById(com.android.internal.R.id.expand_button) expandButtonContainer = requireViewById(com.android.internal.R.id.expand_button_container) + expandButtonInnerContainer = + requireViewById(com.android.internal.R.id.expand_button_inner_container) importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring) appName = requireViewById(com.android.internal.R.id.app_name_text) conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text) @@ -134,6 +137,8 @@ class NotificationConversationTemplateViewWrapper constructor( ) } + override fun getExpandButton() = expandButtonInnerContainer + override fun setShelfIconVisible(visible: Boolean) { if (conversationLayout.isImportantConversation) { if (conversationIconView.visibility != GONE) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index f8b783113ccb..4c9cb209424a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -317,6 +317,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @Override + public View getExpandButton() { + return mExpandButton; + } + + @Override public int getOriginalIconColor() { return mIcon.getOriginalIconColor(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 02e537d2879f..30080e3d8cc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -240,6 +240,13 @@ public abstract class NotificationViewWrapper implements TransformableView { return null; } + /** + * @return the expand button if it exists + */ + public @Nullable View getExpandButton() { + return null; + } + public int getOriginalIconColor() { return Notification.COLOR_INVALID; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java index 46c873db8a08..4337e20c0a39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java @@ -40,6 +40,7 @@ import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseRelativeLayout; import com.android.systemui.statusbar.policy.KeyButtonView; +import java.io.PrintWriter; import java.util.Objects; public class NavigationBarInflaterView extends FrameLayout @@ -469,4 +470,10 @@ public class NavigationBarInflaterView extends FrameLayout private static float convertDpToPx(Context context, float dp) { return dp * context.getResources().getDisplayMetrics().density; } + + public void dump(PrintWriter pw) { + pw.println("NavigationBarInflaterView {"); + pw.println(" mCurrentLayout: " + mCurrentLayout); + pw.println(" }"); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 2978772cac5e..6b37ac317cdd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -1198,6 +1198,9 @@ public class NavigationBarView extends FrameLayout implements pw.println(" }"); + if (mNavigationInflaterView != null) { + mNavigationInflaterView.dump(pw); + } mContextualButtonGroup.dump(pw); mRecentsOnboarding.dump(pw); mRegionSamplingHelper.dump(pw); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index f58cce58af74..76c51d61459a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -572,6 +572,9 @@ public class NotificationIconAreaController implements DarkReceiver, .setInterpolator(Interpolators.LINEAR) .setDuration(AOD_ICONS_APPEAR_DURATION) .start(); + } else { + mAodIcons.setAlpha(1.0f); + mAodIcons.setTranslationY(0); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index b4de3cd5d43b..18a7adda3f7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -640,8 +640,7 @@ public class MobileSignalController extends SignalController< + " dataState=" + state.getDataRegistrationState()); } mServiceState = state; - // onDisplayInfoChanged is invoked directly after onServiceStateChanged, so not calling - // updateTelephony() to prevent icon flickering in case of overrides. + updateTelephony(); } @Override @@ -651,12 +650,6 @@ public class MobileSignalController extends SignalController< + " type=" + networkType); } mDataState = state; - if (networkType != mTelephonyDisplayInfo.getNetworkType()) { - Log.d(mTag, "onDataConnectionStateChanged:" - + " network type change and reset displayInfo. type=" + networkType); - mTelephonyDisplayInfo = new TelephonyDisplayInfo(networkType, - TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE); - } updateTelephony(); } diff --git a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java index 6c3538cb6142..a31ea7c3ab17 100644 --- a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java +++ b/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java @@ -40,7 +40,7 @@ public class DismissCircleView extends FrameLayout { setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); - mIconView.setImageDrawable(res.getDrawable(R.drawable.dismiss_target_x)); + mIconView.setImageDrawable(res.getDrawable(R.drawable.ic_close_white)); addView(mIconView); setViewSizes(); diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index b1792d003290..5c9db54a0f00 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -133,4 +133,13 @@ public class Utils { Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1); return flag > 0; } + + /** + * Allow media resumption controls. Requires {@link #useQsMediaPlayer(Context)} to be enabled. + * Off by default, but can be enabled by setting to 1 + */ + public static boolean useMediaResumption(Context context) { + int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_resumption", 0); + return useQsMediaPlayer(context) && flag > 0; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 9d2b6f4deb14..1ba36e19b404 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -67,7 +67,6 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var player: MediaControlPanel - private lateinit var fgExecutor: FakeExecutor private lateinit var bgExecutor: FakeExecutor @Mock private lateinit var activityStarter: ActivityStarter @@ -97,14 +96,12 @@ public class MediaControlPanelTest : SysuiTestCase() { @Before fun setUp() { - fgExecutor = FakeExecutor(FakeSystemClock()) bgExecutor = FakeExecutor(FakeSystemClock()) activityStarter = mock(ActivityStarter::class.java) mediaHostStatesManager = mock(MediaHostStatesManager::class.java) - player = MediaControlPanel(context, fgExecutor, bgExecutor, activityStarter, - mediaHostStatesManager) + player = MediaControlPanel(context, bgExecutor, activityStarter, mediaHostStatesManager) // Mock out a view holder for the player to attach to. holder = mock(PlayerViewHolder::class.java) @@ -171,7 +168,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindWhenUnattached() { val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, null, null, device) + emptyList(), PACKAGE, null, null, device, null) player.bind(state) assertThat(player.isPlaying()).isFalse() } @@ -180,7 +177,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindText() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device) + emptyList(), PACKAGE, session.getSessionToken(), null, device, null) player.bind(state) assertThat(appName.getText()).isEqualTo(APP) assertThat(titleText.getText()).isEqualTo(TITLE) @@ -191,7 +188,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindBackgroundColor() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device) + emptyList(), PACKAGE, session.getSessionToken(), null, device, null) player.bind(state) val list = ArgumentCaptor.forClass(ColorStateList::class.java) verify(view).setBackgroundTintList(list.capture()) @@ -202,7 +199,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, device) + emptyList(), PACKAGE, session.getSessionToken(), null, device, null) player.bind(state) assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) assertThat(seamless.isEnabled()).isTrue() @@ -212,7 +209,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindDisabledDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice) + emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, null) player.bind(state) assertThat(seamless.isEnabled()).isFalse() assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( @@ -223,7 +220,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindNullDevice() { player.attach(holder) val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, null) + emptyList(), PACKAGE, session.getSessionToken(), null, null, null) player.bind(state) assertThat(seamless.isEnabled()).isTrue() assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index 48e3b0a9d993..bed5c9eb6df5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -79,16 +79,16 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mManager.addListener(mListener); mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, - new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, KEY); + new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, null, KEY, false); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME); } @Test public void eventNotEmittedWithoutDevice() { // WHEN data source emits an event without device data - mDataListener.onMediaDataLoaded(KEY, mMediaData); + mDataListener.onMediaDataLoaded(KEY, null, mMediaData); // THEN an event isn't emitted - verify(mListener, never()).onMediaDataLoaded(eq(KEY), any()); + verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any()); } @Test @@ -96,7 +96,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { // WHEN device source emits an event without media data mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); // THEN an event isn't emitted - verify(mListener, never()).onMediaDataLoaded(eq(KEY), any()); + verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any()); } @Test @@ -104,22 +104,22 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { // GIVEN that a device event has already been received mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); // WHEN media event is received - mDataListener.onMediaDataLoaded(KEY, mMediaData); + mDataListener.onMediaDataLoaded(KEY, null, mMediaData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); - verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture()); + verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture()); assertThat(captor.getValue().getDevice()).isNotNull(); } @Test public void emitEventAfterMediaFirst() { // GIVEN that media event has already been received - mDataListener.onMediaDataLoaded(KEY, mMediaData); + mDataListener.onMediaDataLoaded(KEY, null, mMediaData); // WHEN device event is received mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); // THEN the listener receives a combined event ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); - verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture()); + verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture()); assertThat(captor.getValue().getDevice()).isNotNull(); } @@ -133,7 +133,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void mediaDataRemovedAfterMediaEvent() { - mDataListener.onMediaDataLoaded(KEY, mMediaData); + mDataListener.onMediaDataLoaded(KEY, null, mMediaData); mDataListener.onMediaDataRemoved(KEY); verify(mListener).onMediaDataRemoved(eq(KEY)); } @@ -145,6 +145,18 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { verify(mListener).onMediaDataRemoved(eq(KEY)); } + @Test + public void mediaDataKeyUpdated() { + // GIVEN that device and media events have already been received + mDataListener.onMediaDataLoaded(KEY, null, mMediaData); + mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData); + // WHEN the key is changed + mDataListener.onMediaDataLoaded("NEW_KEY", KEY, mMediaData); + // THEN the listener gets a load event with the correct keys + ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class); + verify(mListener).onMediaDataLoaded(eq("NEW_KEY"), any(), captor.capture()); + } + private MediaDataManager.Listener captureDataListener() { ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass( MediaDataManager.Listener.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index c0aef8adc4af..3a3140f2ff53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -23,8 +23,6 @@ import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaSession import android.media.session.PlaybackState -import android.os.Process -import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -67,6 +65,7 @@ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var manager: MediaDeviceManager + @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var lmmFactory: LocalMediaManagerFactory @Mock private lateinit var lmm: LocalMediaManager @Mock private lateinit var mr2: MediaRouter2Manager @@ -80,13 +79,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var metadataBuilder: MediaMetadata.Builder private lateinit var playbackBuilder: PlaybackState.Builder private lateinit var notifBuilder: Notification.Builder - private lateinit var sbn: StatusBarNotification + private lateinit var mediaData: MediaData @JvmField @Rule val mockito = MockitoJUnit.rule() @Before fun setUp() { fakeExecutor = FakeExecutor(FakeSystemClock()) - manager = MediaDeviceManager(context, lmmFactory, mr2, featureFlag, fakeExecutor) + manager = MediaDeviceManager(context, lmmFactory, mr2, featureFlag, fakeExecutor, + mediaDataManager) manager.addListener(listener) // Configure mocks. @@ -117,8 +117,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { setSmallIcon(android.R.drawable.ic_media_pause) setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken())) } - sbn = StatusBarNotification(PACKAGE, PACKAGE, 0, "TAG", Process.myUid(), 0, 0, - notifBuilder.build(), Process.myUserHandle(), 0) + mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null, + emptyList(), emptyList(), PACKAGE, session.sessionToken, null, null, null) } @After @@ -128,33 +128,33 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun removeUnknown() { - manager.onNotificationRemoved("unknown") + manager.onMediaDataRemoved("unknown") } @Test fun addNotification() { - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) verify(lmmFactory).create(PACKAGE) } @Test fun featureDisabled() { whenever(featureFlag.enabled).thenReturn(false) - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) verify(lmmFactory, never()).create(PACKAGE) } @Test fun addAndRemoveNotification() { - manager.onNotificationAdded(KEY, sbn) - manager.onNotificationRemoved(KEY) + manager.onMediaDataLoaded(KEY, null, mediaData) + manager.onMediaDataRemoved(KEY) verify(lmm).unregisterCallback(any()) } @Test fun deviceEventOnAddNotification() { // WHEN a notification is added - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) val deviceCallback = captureCallback() // THEN the update is dispatched to the listener val data = captureDeviceData(KEY) @@ -165,7 +165,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun deviceListUpdate() { - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) val deviceCallback = captureCallback() // WHEN the device list changes deviceCallback.onDeviceListUpdate(mutableListOf(device)) @@ -179,7 +179,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun selectedDeviceStateChanged() { - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) val deviceCallback = captureCallback() // WHEN the selected device changes state deviceCallback.onSelectedDeviceStateChanged(device, 1) @@ -193,9 +193,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun listenerReceivesKeyRemoved() { - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) // WHEN the notification is removed - manager.onNotificationRemoved(KEY) + manager.onMediaDataRemoved(KEY) // THEN the listener receives key removed event verify(listener).onKeyRemoved(eq(KEY)) } @@ -205,7 +205,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // GIVEN that MR2Manager returns null for routing session whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN a notification is added - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) // THEN the device is disabled val data = captureDeviceData(KEY) assertThat(data.enabled).isFalse() @@ -216,7 +216,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceChanged() { // GIVEN a notif is added - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) reset(listener) // AND MR2Manager returns null for routing session whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) @@ -234,7 +234,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceListUpdate() { // GIVEN a notif is added - manager.onNotificationAdded(KEY, sbn) + manager.onMediaDataLoaded(KEY, null, mediaData) reset(listener) // GIVEN that MR2Manager returns null for routing session whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt index c21343cb5423..643a3352c30c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -16,7 +16,9 @@ package com.android.systemui.media +import android.media.MediaMetadata import android.media.session.MediaController +import android.media.session.MediaSession import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest @@ -41,6 +43,10 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit private const val KEY = "KEY" +private const val PACKAGE = "PKG" +private const val SESSION_KEY = "SESSION_KEY" +private const val SESSION_ARTIST = "SESSION_ARTIST" +private const val SESSION_TITLE = "SESSION_TITLE" private fun <T> eq(value: T): T = Mockito.eq(value) ?: value private fun <T> anyObject(): T { @@ -54,12 +60,15 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Mock private lateinit var mediaControllerFactory: MediaControllerFactory @Mock private lateinit var mediaController: MediaController @Mock private lateinit var executor: DelayableExecutor - @Mock private lateinit var mediaData: MediaData @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit @Mock private lateinit var cancellationRunnable: Runnable @Captor private lateinit var timeoutCaptor: ArgumentCaptor<Runnable> @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback> @JvmField @Rule val mockito = MockitoJUnit.rule() + private lateinit var metadataBuilder: MediaMetadata.Builder + private lateinit var playbackBuilder: PlaybackState.Builder + private lateinit var session: MediaSession + private lateinit var mediaData: MediaData private lateinit var mediaTimeoutListener: MediaTimeoutListener @Before @@ -68,22 +77,39 @@ class MediaTimeoutListenerTest : SysuiTestCase() { `when`(executor.executeDelayed(any(), anyLong())).thenReturn(cancellationRunnable) mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor) mediaTimeoutListener.timeoutCallback = timeoutCallback + + // Create a media session and notification for testing. + metadataBuilder = MediaMetadata.Builder().apply { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) + } + playbackBuilder = PlaybackState.Builder().apply { + setState(PlaybackState.STATE_PAUSED, 6000L, 1f) + setActions(PlaybackState.ACTION_PLAY) + } + session = MediaSession(context, SESSION_KEY).apply { + setMetadata(metadataBuilder.build()) + setPlaybackState(playbackBuilder.build()) + } + session.setActive(true) + mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null, + emptyList(), emptyList(), PACKAGE, session.sessionToken, null, null, null) } @Test fun testOnMediaDataLoaded_registersPlaybackListener() { - mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData) + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) // Ignores is same key clearInvocations(mediaController) - mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData) + mediaTimeoutListener.onMediaDataLoaded(KEY, KEY, mediaData) verify(mediaController, never()).registerCallback(anyObject()) } @Test fun testOnMediaDataRemoved_unregistersPlaybackListener() { - mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData) + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) mediaTimeoutListener.onMediaDataRemoved(KEY) verify(mediaController).unregisterCallback(anyObject()) @@ -124,7 +150,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testIsTimedOut() { - mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData) + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) assertThat(mediaTimeoutListener.isTimedOut(KEY)).isFalse() } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java index dd5cb585d0aa..ec73a7571969 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java @@ -144,7 +144,7 @@ public class AppOpsInfoTest extends SysuiTestCase { final View okButton = mAppOpsInfo.findViewById(R.id.ok); okButton.performClick(); assertEquals(1, latch.getCount()); - verify(mGutsParent, times(1)).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean()); + verify(mGutsParent, times(1)).closeControls(eq(okButton), anyBoolean()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java index b018b59e4389..ed4f8b330e23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.row; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -30,11 +29,13 @@ import android.app.AppOpsManager; import android.util.ArraySet; import android.view.NotificationHeaderView; import android.view.View; +import android.view.ViewPropertyAnimator; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.SysuiTestCase; import org.junit.Before; @@ -98,4 +99,42 @@ public class NotificationContentViewTest extends SysuiTestCase { verify(mockExpanded, times(1)).setVisibility(View.VISIBLE); verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE); } + + @Test + @UiThreadTest + public void testExpandButtonFocusIsCalled() { + View mockContractedEB = mock(NotificationExpandButton.class); + View mockContracted = mock(NotificationHeaderView.class); + when(mockContracted.animate()).thenReturn(mock(ViewPropertyAnimator.class)); + when(mockContracted.findViewById(com.android.internal.R.id.expand_button)).thenReturn( + mockContractedEB); + + View mockExpandedEB = mock(NotificationExpandButton.class); + View mockExpanded = mock(NotificationHeaderView.class); + when(mockExpanded.animate()).thenReturn(mock(ViewPropertyAnimator.class)); + when(mockExpanded.findViewById(com.android.internal.R.id.expand_button)).thenReturn( + mockExpandedEB); + + View mockHeadsUpEB = mock(NotificationExpandButton.class); + View mockHeadsUp = mock(NotificationHeaderView.class); + when(mockHeadsUp.animate()).thenReturn(mock(ViewPropertyAnimator.class)); + when(mockHeadsUp.findViewById(com.android.internal.R.id.expand_button)).thenReturn( + mockHeadsUpEB); + + // Set up all 3 child forms + mView.setContractedChild(mockContracted); + mView.setExpandedChild(mockExpanded); + mView.setHeadsUpChild(mockHeadsUp); + + // This is required to call requestAccessibilityFocus() + mView.setFocusOnVisibilityChange(); + + // The following will initialize the view and switch from not visible to expanded. + // (heads-up is actually an alternate form of contracted, hence this enters expanded state) + mView.setHeadsUp(true); + + verify(mockContractedEB, times(0)).requestAccessibilityFocus(); + verify(mockExpandedEB, times(1)).requestAccessibilityFocus(); + verify(mockHeadsUpEB, times(0)).requestAccessibilityFocus(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index b39de34f6c63..4122cf5466e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -173,7 +173,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { doAnswer((Answer<Object>) invocation -> { mNotificationInfo.handleCloseControls(true, false); return null; - }).when(mNotificationGuts).closeControls(anyInt(), anyInt(), eq(true), eq(false)); + }).when(mNotificationGuts).closeControls(any(View.class), eq(true)); // Our view is never attached to a window so the View#post methods in NotificationInfo never // get called. Setting this will skip the post and do the action immediately. mNotificationInfo.mSkipPost = true; @@ -256,7 +256,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon); assertEquals(mIconDrawable, view.getDrawable()); } @@ -280,7 +280,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name); assertTrue(textView.getText().toString().contains("App Name")); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); @@ -331,7 +331,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); final TextView textView = mNotificationInfo.findViewById(R.id.group_name); assertTrue(textView.getText().toString().contains(group.getName())); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); @@ -356,7 +356,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); final TextView textView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); assertEquals(GONE, textView.getVisibility()); @@ -380,7 +380,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); } @@ -415,7 +415,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); assertTrue(nameView.getText().toString().contains("Proxied")); @@ -443,7 +443,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); settingsButton.performClick(); @@ -469,7 +469,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -496,7 +496,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, false, mTestHandler, - mTestHandler); + mTestHandler, null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -521,7 +521,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); View view = mNotificationInfo.findViewById(R.id.silence); assertThat(view.isSelected()).isTrue(); } @@ -549,7 +549,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); View view = mNotificationInfo.findViewById(R.id.default_behavior); assertThat(view.isSelected()).isTrue(); assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo( @@ -580,7 +580,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); View view = mNotificationInfo.findViewById(R.id.default_behavior); assertThat(view.isSelected()).isTrue(); assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo( @@ -610,7 +610,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); View fave = mNotificationInfo.findViewById(R.id.priority); fave.performClick(); @@ -654,7 +654,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); mTestableLooper.processAllMessages(); @@ -697,7 +697,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); View silence = mNotificationInfo.findViewById(R.id.silence); @@ -741,7 +741,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); View fave = mNotificationInfo.findViewById(R.id.priority); fave.performClick(); @@ -778,7 +778,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); View fave = mNotificationInfo.findViewById(R.id.priority); fave.performClick(); @@ -793,6 +793,45 @@ public class NotificationConversationInfoTest extends SysuiTestCase { } @Test + public void testFavorite_thenDefaultThenFavorite_andSave_nothingChanged() throws Exception { + mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH); + mConversationChannel.setImportance(IMPORTANCE_HIGH); + mConversationChannel.setImportantConversation(true); + + mNotificationInfo.bindNotification( + mShortcutManager, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + mBubbleMetadata, + null, + null, + mIconFactory, + mContext, + mBuilderProvider, + true, + mTestHandler, + mTestHandler, null); + + View fave = mNotificationInfo.findViewById(R.id.priority); + fave.performClick(); + mNotificationInfo.findViewById(R.id.default_behavior).performClick(); + fave.performClick(); + mNotificationInfo.findViewById(R.id.done).performClick(); + mTestableLooper.processAllMessages(); + + ArgumentCaptor<NotificationChannel> captor = + ArgumentCaptor.forClass(NotificationChannel.class); + verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( + anyString(), anyInt(), captor.capture()); + assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance()); + assertTrue(captor.getValue().isImportantConversation()); + } + + @Test public void testDefault_andSave() throws Exception { mConversationChannel.setAllowBubbles(true); mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH); @@ -813,7 +852,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -849,7 +888,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -885,7 +924,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -920,7 +959,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); View silence = mNotificationInfo.findViewById(R.id.silence); silence.performClick(); @@ -954,7 +993,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage( anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID)); @@ -979,7 +1018,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mBuilderProvider, true, mTestHandler, - mTestHandler); + mTestHandler, null); verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage( anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID)); @@ -1014,10 +1053,14 @@ public class NotificationConversationInfoTest extends SysuiTestCase { () -> b, true, mTestHandler, - mTestHandler); + mTestHandler, null); // WHEN user clicks "priority" mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE); + verify(controller, never()).show(); + + // and then done + mNotificationInfo.findViewById(R.id.done).performClick(); // THEN the user is presented with the priority onboarding screen verify(controller, atLeastOnce()).show(); @@ -1050,7 +1093,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { () -> b, true, mTestHandler, - mTestHandler); + mTestHandler, null); // WHEN user clicks "priority" mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java index e56ef5b92f87..f327967ebd73 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java @@ -161,25 +161,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { } @Test - public void testBindNotification_SetsTextApplicationName() throws Exception { - when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); - mInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mNotificationChannelSet, - mEntry, - null, - true, - false); - final TextView textView = mInfo.findViewById(R.id.pkg_name); - assertTrue(textView.getText().toString().contains("App Name")); - assertEquals(VISIBLE, mInfo.findViewById(R.id.header).getVisibility()); - } - - @Test public void testBindNotification_SetsName() { mInfo.bindNotification( mMockPackageManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java index be43e19cfc70..177e845bfead 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; @@ -56,6 +57,8 @@ public class NotificationIconAreaControllerTest extends SysuiTestCase { @Mock NotificationMediaManager mNotificationMediaManager; @Mock + NotificationIconContainer mNotificationIconContainer; + @Mock DozeParameters mDozeParameters; @Mock NotificationShadeWindowView mNotificationShadeWindowView; @@ -67,7 +70,7 @@ public class NotificationIconAreaControllerTest extends SysuiTestCase { when(mStatusBar.getNotificationShadeWindowView()).thenReturn(mNotificationShadeWindowView); when(mNotificationShadeWindowView.findViewById(anyInt())).thenReturn( - mock(NotificationIconContainer.class)); + mNotificationIconContainer); mController = new NotificationIconAreaController(mContext, mStatusBar, mStatusBarStateController, mWakeUpCoordinator, mKeyguardBypassController, @@ -87,4 +90,12 @@ public class NotificationIconAreaControllerTest extends SysuiTestCase { assertTrue(mController.shouldShouldLowPriorityIcons()); } + + @Test + public void testAppearResetsTranslation() { + when(mDozeParameters.shouldControlScreenOff()).thenReturn(false); + mController.appearAodIcons(); + verify(mNotificationIconContainer).setTranslationY(0); + verify(mNotificationIconContainer).setAlpha(1.0f); + } } diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index f08429bb0696..3fd9ee9a330b 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -730,12 +730,7 @@ public class IpServer extends StateMachine { final String upstreamIface = v6only.getInterfaceName(); params = new RaParams(); - // When BPF offload is enabled, we advertise an mtu lower by 16, which is the closest - // multiple of 8 >= 14, the ethernet header size. This makes kernel ebpf tethering - // offload happy. This hack should be reverted once we have the kernel fixed up. - // Note: this will automatically clamp to at least 1280 (ipv6 minimum mtu) - // see RouterAdvertisementDaemon.java putMtu() - params.mtu = mUsingBpfOffload ? v6only.getMtu() - 16 : v6only.getMtu(); + params.mtu = v6only.getMtu(); params.hasDefaultRoute = v6only.hasIpv6DefaultRoute(); if (params.hasDefaultRoute) params.hopLimit = getHopLimit(upstreamIface, ttlAdjustment); diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java index e2330ca6ffe9..0ec8654f2a20 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java @@ -75,9 +75,7 @@ final class AutofillInlineSessionController { @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) { // TODO(b/151123764): rename the method to better reflect what it does. if (mSession != null) { - // Send an empty response to IME and destroy the existing session. - mSession.onInlineSuggestionsResponseLocked( - InlineFillUi.emptyUi(mSession.getAutofillIdLocked())); + // Destroy the existing session. mSession.destroySessionLocked(); mInlineFillUi = null; } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 3c0d880916ee..0d4efed25da3 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -954,7 +954,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } /** - * Call IBluetooth.onLeServiceUp() to continue if Bluetooth should be on. + * Call IBluetooth.onLeServiceUp() to continue if Bluetooth should be on, + * call IBluetooth.onBrEdrDown() to disable if Bluetooth should be off. */ private void continueFromBleOnState() { if (DBG) { @@ -966,11 +967,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { Slog.e(TAG, "onBluetoothServiceUp: mBluetooth is null!"); return; } - if (!mEnableExternal && !isBleAppPresent() && isAirplaneModeOn()) { - // Airplane mode is turned on while enabling BLE only mode, disable - // BLE now. - disableBleScanMode(); - sendBrEdrDownCallback(); + if (!mEnableExternal && !isBleAppPresent()) { + Slog.i(TAG, "Bluetooth was disabled while enabling BLE, disable BLE now"); + mEnable = false; + mBluetooth.onBrEdrDown(); return; } if (isBluetoothPersistedStateOnBluetooth() || !isBleAppPresent()) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1634f6e62897..0ab571854c72 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1374,10 +1374,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nri == null || net == null || !LOGD_BLOCKED_NETWORKINFO) { return; } - String action = blocked ? "BLOCKED" : "UNBLOCKED"; - log(String.format("Blocked status changed to %s for %d(%d) on netId %d", blocked, - nri.mUid, nri.request.requestId, net.netId)); - mNetworkInfoBlockingLogs.log(action + " " + nri.mUid); + final String action = blocked ? "BLOCKED" : "UNBLOCKED"; + mNetworkInfoBlockingLogs.log(String.format( + "%s %d(%d) on netId %d", action, nri.mUid, nri.request.requestId, net.netId)); } /** diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index e066d99147ba..a153d4191a5c 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -1894,6 +1894,8 @@ public class VibratorService extends IVibratorService.Stub return runWaveform(); } else if ("prebaked".equals(cmd)) { return runPrebaked(); + } else if ("capabilities".equals(cmd)) { + return runCapabilities(); } else if ("cancel".equals(cmd)) { cancelVibrate(mToken); return 0; @@ -2016,10 +2018,15 @@ public class VibratorService extends IVibratorService.Stub Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runPrebaked"); try { CommonOptions commonOptions = new CommonOptions(); + boolean shouldFallback = false; String opt; while ((opt = getNextOption()) != null) { - commonOptions.check(opt); + if ("-b".equals(opt)) { + shouldFallback = true; + } else { + commonOptions.check(opt); + } } if (checkDoNotDisturb(commonOptions)) { @@ -2033,8 +2040,7 @@ public class VibratorService extends IVibratorService.Stub description = "Shell command"; } - VibrationEffect effect = - VibrationEffect.get(id, false); + VibrationEffect effect = VibrationEffect.get(id, shouldFallback); VibrationAttributes attrs = createVibrationAttributes(commonOptions); vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command", mToken); @@ -2044,12 +2050,39 @@ public class VibratorService extends IVibratorService.Stub } } + private int runCapabilities() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runCapabilities"); + try (PrintWriter pw = getOutPrintWriter();) { + pw.println("Vibrator capabilities:"); + if (hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { + pw.println(" Always on effects"); + } + if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { + pw.println(" Compose effects"); + } + if (mSupportsAmplitudeControl || hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) { + pw.println(" Amplitude control"); + } + if (mSupportsExternalControl || hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { + pw.println(" External control"); + } + if (hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) { + pw.println(" External amplitude control"); + } + pw.println(""); + return 0; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) { final int flags = commonOptions.force ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY : 0; return new VibrationAttributes.Builder() - .setUsage(VibrationAttributes.USAGE_UNKNOWN) + // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects. + .setUsage(VibrationAttributes.USAGE_TOUCH) .replaceFlags(flags) .build(); } @@ -2062,19 +2095,26 @@ public class VibratorService extends IVibratorService.Stub pw.println(" Prints this help text."); pw.println(""); pw.println(" vibrate duration [description]"); - pw.println(" Vibrates for duration milliseconds; ignored when device is on DND "); - pw.println(" (Do Not Disturb) mode."); + pw.println(" Vibrates for duration milliseconds; ignored when device is on "); + pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting "); + pw.println(" will be used to scale amplitude."); pw.println(" waveform [-d description] [-r index] [-a] duration [amplitude] ..."); - pw.println(" Vibrates for durations and amplitudes in list;"); - pw.println(" ignored when device is on DND (Do Not Disturb) mode."); + pw.println(" Vibrates for durations and amplitudes in list; ignored when "); + pw.println(" device is on DND (Do Not Disturb) mode; touch feedback strength "); + pw.println(" user setting will be used to scale amplitude."); pw.println(" If -r is provided, the waveform loops back to the specified"); pw.println(" index (e.g. 0 loops from the beginning)"); pw.println(" If -a is provided, the command accepts duration-amplitude pairs;"); pw.println(" otherwise, it accepts durations only and alternates off/on"); pw.println(" Duration is in milliseconds; amplitude is a scale of 1-255."); - pw.println(" prebaked effect-id [description]"); + pw.println(" prebaked [-b] effect-id [description]"); pw.println(" Vibrates with prebaked effect; ignored when device is on DND "); - pw.println(" (Do Not Disturb) mode."); + pw.println(" (Do Not Disturb) mode; touch feedback strength user setting "); + pw.println(" will be used to scale amplitude."); + pw.println(" If -b is provided, the prebaked fallback effect will be played if"); + pw.println(" the device doesn't support the given effect-id."); + pw.println(" capabilities"); + pw.println(" Prints capabilities of this device."); pw.println(" cancel"); pw.println(" Cancels any active vibration"); pw.println("Common Options:"); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index efd3c3e9bfc1..27d9ba08e4a2 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1908,8 +1908,11 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#adjustVolume(int, int) */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, String caller) { + boolean hasModifyAudioSettings = + mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage, - caller, Binder.getCallingUid(), hasModifyAudioSettings(), VOL_ADJUST_NORMAL); + caller, Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL); } private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, @@ -2014,10 +2017,13 @@ public class AudioService extends IAudioService.Stub + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage); return; } + final boolean hasModifyAudioSettings = + mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType, direction/*val1*/, flags/*val2*/, callingPackage)); adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, - Binder.getCallingUid(), hasModifyAudioSettings(), VOL_ADJUST_NORMAL); + Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL); } protected void adjustStreamVolume(int streamType, int direction, int flags, @@ -2528,10 +2534,13 @@ public class AudioService extends IAudioService.Stub + " MODIFY_AUDIO_ROUTING callingPackage=" + callingPackage); return; } + final boolean hasModifyAudioSettings = + mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED; sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, index/*val1*/, flags/*val2*/, callingPackage)); setStreamVolume(streamType, index, flags, callingPackage, callingPackage, - Binder.getCallingUid(), hasModifyAudioSettings()); + Binder.getCallingUid(), hasModifyAudioSettings); } private boolean canChangeAccessibilityVolume() { @@ -3197,7 +3206,8 @@ public class AudioService extends IAudioService.Stub ensureValidStreamType(streamType); final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID - || (hasModifyAudioSettings()) + || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED) || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) == PackageManager.PERMISSION_GRANTED); return (mStreamStates[streamType].getMinIndex(isPrivileged) + 5) / 10; @@ -4755,18 +4765,9 @@ public class AudioService extends IAudioService.Stub handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); } - private boolean hasModifyAudioSettings() { - return mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) - == PackageManager.PERMISSION_GRANTED; - } - - private boolean hasModifyAudioSettings(int pid, int uid) { - return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) - == PackageManager.PERMISSION_GRANTED; - } - boolean checkAudioSettingsPermission(String method) { - if (hasModifyAudioSettings()) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED) { return true; } String msg = "Audio Settings Permission Denial: " + method + " from pid=" @@ -7688,10 +7689,13 @@ public class AudioService extends IAudioService.Stub @Override public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags, String callingPackage, int uid, int pid) { + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; // direction and stream type swap here because the public // adjustSuggested has a different order than the other methods. adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage, - callingPackage, uid, hasModifyAudioSettings(pid, uid), VOL_ADJUST_NORMAL); + callingPackage, uid, hasModifyAudioSettings, VOL_ADJUST_NORMAL); } @Override @@ -7702,15 +7706,21 @@ public class AudioService extends IAudioService.Stub direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage) .append(" uid:").append(uid).toString())); } + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; adjustStreamVolume(streamType, direction, flags, callingPackage, - callingPackage, uid, hasModifyAudioSettings(pid, uid), VOL_ADJUST_NORMAL); + callingPackage, uid, hasModifyAudioSettings, VOL_ADJUST_NORMAL); } @Override public void setStreamVolumeForUid(int streamType, int direction, int flags, String callingPackage, int uid, int pid) { + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid, - hasModifyAudioSettings(pid, uid)); + hasModifyAudioSettings); } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 9de95abafdda..b9669c74a6df 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -40,6 +40,7 @@ import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiHotplugEvent; import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.hdmi.IHdmiControlService; import android.hardware.hdmi.IHdmiControlStatusChangeListener; @@ -63,6 +64,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; @@ -268,6 +270,11 @@ public class HdmiControlService extends SystemService { private final ArrayList<HdmiControlStatusChangeListenerRecord> mHdmiControlStatusChangeListenerRecords = new ArrayList<>(); + // List of records for HDMI control volume control status change listener for death monitoring. + @GuardedBy("mLock") + private final RemoteCallbackList<IHdmiCecVolumeControlFeatureListener> + mHdmiCecVolumeControlFeatureListenerRecords = new RemoteCallbackList<>(); + // List of records for hotplug event listener to handle the the caller killed in action. @GuardedBy("mLock") private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = @@ -1814,6 +1821,21 @@ public class HdmiControlService extends SystemService { } @Override + public void addHdmiCecVolumeControlFeatureListener( + final IHdmiCecVolumeControlFeatureListener listener) { + enforceAccessPermission(); + HdmiControlService.this.addHdmiCecVolumeControlFeatureListener(listener); + } + + @Override + public void removeHdmiCecVolumeControlFeatureListener( + final IHdmiCecVolumeControlFeatureListener listener) { + enforceAccessPermission(); + HdmiControlService.this.removeHdmiControlVolumeControlStatusChangeListener(listener); + } + + + @Override public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { enforceAccessPermission(); HdmiControlService.this.addHotplugEventListener(listener); @@ -2409,6 +2431,33 @@ public class HdmiControlService extends SystemService { } } + @VisibleForTesting + void addHdmiCecVolumeControlFeatureListener( + final IHdmiCecVolumeControlFeatureListener listener) { + mHdmiCecVolumeControlFeatureListenerRecords.register(listener); + + runOnServiceThread(new Runnable() { + @Override + public void run() { + // Return the current status of mHdmiCecVolumeControlEnabled; + synchronized (mLock) { + try { + listener.onHdmiCecVolumeControlFeature(mHdmiCecVolumeControlEnabled); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to report HdmiControlVolumeControlStatusChange: " + + mHdmiCecVolumeControlEnabled, e); + } + } + } + }); + } + + @VisibleForTesting + void removeHdmiControlVolumeControlStatusChangeListener( + final IHdmiCecVolumeControlFeatureListener listener) { + mHdmiCecVolumeControlFeatureListenerRecords.unregister(listener); + } + private void addHotplugEventListener(final IHdmiHotplugEventListener listener) { final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); try { @@ -2682,6 +2731,19 @@ public class HdmiControlService extends SystemService { } } + private void announceHdmiCecVolumeControlFeatureChange(boolean isEnabled) { + assertRunOnServiceThread(); + mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> { + try { + listener.onHdmiCecVolumeControlFeature(isEnabled); + } catch (RemoteException e) { + Slog.e(TAG, + "Failed to report HdmiControlVolumeControlStatusChange: " + + isEnabled); + } + }); + } + public HdmiCecLocalDeviceTv tv() { return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); } @@ -3026,6 +3088,7 @@ public class HdmiControlService extends SystemService { isHdmiCecVolumeControlEnabled); } } + announceHdmiCecVolumeControlFeatureChange(isHdmiCecVolumeControlEnabled); } boolean isHdmiCecVolumeControlEnabled() { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index ccbe96f30e04..067bdcb111fb 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -28,6 +28,9 @@ import static android.os.PowerManager.locationPowerSaveModeToString; import static com.android.server.location.CallerIdentity.PERMISSION_COARSE; import static com.android.server.location.CallerIdentity.PERMISSION_FINE; +import static com.android.server.location.UserInfoHelper.UserListener.CURRENT_USER_CHANGED; +import static com.android.server.location.UserInfoHelper.UserListener.USER_STARTED; +import static com.android.server.location.UserInfoHelper.UserListener.USER_STOPPED; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -64,6 +67,7 @@ import android.location.LocationProvider; import android.location.LocationRequest; import android.location.LocationTime; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; @@ -101,7 +105,7 @@ import com.android.server.location.AbstractLocationProvider.State; import com.android.server.location.CallerIdentity.PermissionLevel; import com.android.server.location.LocationRequestStatistics.PackageProviderKey; import com.android.server.location.LocationRequestStatistics.PackageStatistics; -import com.android.server.location.UserInfoHelper.UserListener; +import com.android.server.location.UserInfoHelper.UserListener.UserChange; import com.android.server.location.gnss.GnssManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; @@ -132,11 +136,13 @@ public class LocationManagerService extends ILocationManager.Stub { */ public static class Lifecycle extends SystemService { + private final UserInfoHelper mUserInfoHelper; private final LocationManagerService mService; public Lifecycle(Context context) { super(context); - mService = new LocationManagerService(context); + mUserInfoHelper = new SystemUserInfoHelper(context); + mService = new LocationManagerService(context, mUserInfoHelper); } @Override @@ -161,6 +167,29 @@ public class LocationManagerService extends ILocationManager.Stub { mService.onSystemThirdPartyAppsCanStart(); } } + + @Override + public void onUserStarting(TargetUser user) { + mUserInfoHelper.dispatchOnUserStarted(user.getUserIdentifier()); + } + + @Override + public void onUserSwitching(TargetUser from, TargetUser to) { + mUserInfoHelper.dispatchOnCurrentUserChanged(from.getUserIdentifier(), + to.getUserIdentifier()); + } + + @Override + public void onUserStopped(TargetUser user) { + mUserInfoHelper.dispatchOnUserStopped(user.getUserIdentifier()); + } + + private static class SystemUserInfoHelper extends UserInfoHelper { + + SystemUserInfoHelper(Context context) { + super(context); + } + } } public static final String TAG = "LocationManagerService"; @@ -232,7 +261,7 @@ public class LocationManagerService extends ILocationManager.Stub { @PowerManager.LocationPowerSaveMode private int mBatterySaverMode; - private LocationManagerService(Context context) { + private LocationManagerService(Context context, UserInfoHelper userInfoHelper) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); mHandler = FgThread.getHandler(); mLocalService = new LocalService(); @@ -240,7 +269,7 @@ public class LocationManagerService extends ILocationManager.Stub { LocalServices.addService(LocationManagerInternal.class, mLocalService); mAppOpsHelper = new AppOpsHelper(mContext); - mUserInfoHelper = new UserInfoHelper(mContext); + mUserInfoHelper = userInfoHelper; mSettingsHelper = new SettingsHelper(mContext, mHandler); mAppForegroundHelper = new AppForegroundHelper(mContext); mLocationUsageLogger = new LocationUsageLogger(); @@ -342,7 +371,7 @@ public class LocationManagerService extends ILocationManager.Stub { // initialize the current users. we would get the user started notifications for these // users eventually anyways, but this takes care of it as early as possible. for (int userId: mUserInfoHelper.getCurrentUserIds()) { - onUserChanged(userId, UserListener.USER_STARTED); + onUserChanged(userId, USER_STARTED); } } } @@ -596,32 +625,23 @@ public class LocationManagerService extends ILocationManager.Stub { } } - private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) { + private void onUserChanged(@UserIdInt int userId, @UserChange int change) { switch (change) { - case UserListener.USER_SWITCHED: - if (D) { - Log.d(TAG, "user " + userId + " current status changed"); - } + case CURRENT_USER_CHANGED: synchronized (mLock) { for (LocationProviderManager manager : mProviderManagers) { manager.onEnabledChangedLocked(userId); } } break; - case UserListener.USER_STARTED: - if (D) { - Log.d(TAG, "user " + userId + " started"); - } + case USER_STARTED: synchronized (mLock) { for (LocationProviderManager manager : mProviderManagers) { manager.onUserStarted(userId); } } break; - case UserListener.USER_STOPPED: - if (D) { - Log.d(TAG, "user " + userId + " stopped"); - } + case USER_STOPPED: synchronized (mLock) { for (LocationProviderManager manager : mProviderManagers) { manager.onUserStopped(userId); @@ -957,10 +977,22 @@ public class LocationManagerService extends ILocationManager.Stub { pw.increaseIndent(); // for now we only dump for the parent user - int userId = mUserInfoHelper.getCurrentUserIds()[0]; - pw.println("last location=" + mLastLocation.get(userId)); - pw.println("last coarse location=" + mLastCoarseLocation.get(userId)); - pw.println("enabled=" + isEnabled(userId)); + int[] userIds = mUserInfoHelper.getCurrentUserIds(); + if (userIds.length == 1) { + int userId = userIds[0]; + pw.println("last location=" + mLastLocation.get(userId)); + pw.println("last coarse location=" + mLastCoarseLocation.get(userId)); + pw.println("enabled=" + isEnabled(userId)); + } else { + for (int userId : userIds) { + pw.println("user " + userId + ":"); + pw.increaseIndent(); + pw.println("last location=" + mLastLocation.get(userId)); + pw.println("last coarse location=" + mLastCoarseLocation.get(userId)); + pw.println("enabled=" + isEnabled(userId)); + pw.decreaseIndent(); + } + } } mProvider.dump(fd, pw, args); @@ -1666,6 +1698,9 @@ public class LocationManagerService extends ILocationManager.Stub { * Note: must be constructed with lock held. */ private UpdateRecord(String provider, LocationRequest request, Receiver receiver) { + if (Build.IS_DEBUGGABLE) { + Preconditions.checkState(Thread.holdsLock(mLock)); + } mExpirationRealtimeMs = request.getExpirationRealtimeMs(SystemClock.elapsedRealtime()); mProvider = provider; mRealRequest = request; @@ -1703,6 +1738,10 @@ public class LocationManagerService extends ILocationManager.Stub { * Method to be called when a record will no longer be used. */ private void disposeLocked(boolean removeReceiver) { + if (Build.IS_DEBUGGABLE) { + Preconditions.checkState(Thread.holdsLock(mLock)); + } + CallerIdentity identity = mReceiver.mCallerIdentity; mRequestStatistics.stopRequesting(identity.packageName, identity.featureId, mProvider); diff --git a/services/core/java/com/android/server/location/UserInfoHelper.java b/services/core/java/com/android/server/location/UserInfoHelper.java index a3dcc40bdf2d..53bff8eacb4c 100644 --- a/services/core/java/com/android/server/location/UserInfoHelper.java +++ b/services/core/java/com/android/server/location/UserInfoHelper.java @@ -20,48 +20,48 @@ import static android.os.UserManager.DISALLOW_SHARE_LOCATION; import static com.android.server.location.LocationManagerService.D; import static com.android.server.location.LocationManagerService.TAG; +import static com.android.server.location.UserInfoHelper.UserListener.CURRENT_USER_CHANGED; +import static com.android.server.location.UserInfoHelper.UserListener.USER_STARTED; +import static com.android.server.location.UserInfoHelper.UserListener.USER_STOPPED; +import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.ActivityManager; -import android.content.BroadcastReceiver; +import android.app.ActivityManagerInternal; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.Binder; -import android.os.Build; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; -import com.android.server.FgThread; +import com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; /** * Provides accessors and listeners for all user info. */ -public class UserInfoHelper { +public abstract class UserInfoHelper { /** * Listener for current user changes. */ public interface UserListener { - int USER_SWITCHED = 1; + int CURRENT_USER_CHANGED = 1; int USER_STARTED = 2; int USER_STOPPED = 3; - @IntDef({USER_SWITCHED, USER_STARTED, USER_STOPPED}) + @IntDef({CURRENT_USER_CHANGED, USER_STARTED, USER_STOPPED}) @Retention(RetentionPolicy.SOURCE) @interface UserChange {} @@ -75,143 +75,101 @@ public class UserInfoHelper { private final CopyOnWriteArrayList<UserListener> mListeners; @GuardedBy("this") - @Nullable private UserManager mUserManager; - - @UserIdInt private volatile int mCurrentUserId; - + @Nullable private ActivityManagerInternal mActivityManagerInternal; @GuardedBy("this") - @UserIdInt private int mCachedParentUserId; - @GuardedBy("this") - private int[] mCachedProfileUserIds; + @Nullable private UserManager mUserManager; public UserInfoHelper(Context context) { mContext = context; mListeners = new CopyOnWriteArrayList<>(); - - mCurrentUserId = UserHandle.USER_NULL; - mCachedParentUserId = UserHandle.USER_NULL; - mCachedProfileUserIds = new int[]{UserHandle.USER_NULL}; } /** Called when system is ready. */ + @CallSuper public synchronized void onSystemReady() { - if (mUserManager != null) { + if (mActivityManagerInternal != null) { return; } + mActivityManagerInternal = Objects.requireNonNull( + LocalServices.getService(ActivityManagerInternal.class)); mUserManager = mContext.getSystemService(UserManager.class); - - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_USER_SWITCHED); - intentFilter.addAction(Intent.ACTION_USER_STARTED); - intentFilter.addAction(Intent.ACTION_USER_STOPPED); - intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); - intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); - - mContext.registerReceiverAsUser(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action == null) { - return; - } - int userId; - switch (action) { - case Intent.ACTION_USER_SWITCHED: - userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - if (userId != UserHandle.USER_NULL) { - onCurrentUserChanged(userId); - } - break; - case Intent.ACTION_USER_STARTED: - userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - if (userId != UserHandle.USER_NULL) { - onUserStarted(userId); - } - break; - case Intent.ACTION_USER_STOPPED: - userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - if (userId != UserHandle.USER_NULL) { - onUserStopped(userId); - } - break; - case Intent.ACTION_MANAGED_PROFILE_ADDED: - case Intent.ACTION_MANAGED_PROFILE_REMOVED: - onUserProfilesChanged(); - break; - } - } - }, UserHandle.ALL, intentFilter, null, FgThread.getHandler()); - - mCurrentUserId = ActivityManager.getCurrentUser(); } /** * Adds a listener for user changed events. Callbacks occur on an unspecified thread. */ - public void addListener(UserListener listener) { + public final void addListener(UserListener listener) { mListeners.add(listener); } /** * Removes a listener for user changed events. */ - public void removeListener(UserListener listener) { + public final void removeListener(UserListener listener) { mListeners.remove(listener); } - private void onCurrentUserChanged(@UserIdInt int newUserId) { - if (newUserId == mCurrentUserId) { - return; - } - + protected void dispatchOnUserStarted(@UserIdInt int userId) { if (D) { - Log.d(TAG, "current user switched from u" + mCurrentUserId + " to u" + newUserId); + Log.d(TAG, "u" + userId + " started"); } - int oldUserId = mCurrentUserId; - mCurrentUserId = newUserId; - - onUserChanged(oldUserId, UserListener.USER_SWITCHED); - onUserChanged(newUserId, UserListener.USER_SWITCHED); + for (UserListener listener : mListeners) { + listener.onUserChanged(userId, USER_STARTED); + } } - private void onUserStarted(@UserIdInt int userId) { + protected void dispatchOnUserStopped(@UserIdInt int userId) { if (D) { - Log.d(TAG, "u" + userId + " started"); + Log.d(TAG, "u" + userId + " stopped"); } - onUserChanged(userId, UserListener.USER_STARTED); + for (UserListener listener : mListeners) { + listener.onUserChanged(userId, USER_STOPPED); + } } - private void onUserStopped(@UserIdInt int userId) { + protected void dispatchOnCurrentUserChanged(@UserIdInt int fromUserId, + @UserIdInt int toUserId) { + int[] fromUserIds = getProfileIds(fromUserId); + int[] toUserIds = getProfileIds(toUserId); if (D) { - Log.d(TAG, "u" + userId + " stopped"); + Log.d(TAG, "current user changed from u" + Arrays.toString(fromUserIds) + " to u" + + Arrays.toString(toUserIds)); } - onUserChanged(userId, UserListener.USER_STOPPED); - } - - private void onUserChanged(@UserIdInt int userId, @UserListener.UserChange int change) { for (UserListener listener : mListeners) { - listener.onUserChanged(userId, change); + for (int userId : fromUserIds) { + listener.onUserChanged(userId, CURRENT_USER_CHANGED); + } } - } - private synchronized void onUserProfilesChanged() { - // this intent is only sent to the current user - if (mCachedParentUserId == mCurrentUserId) { - mCachedParentUserId = UserHandle.USER_NULL; - mCachedProfileUserIds = new int[]{UserHandle.USER_NULL}; + for (UserListener listener : mListeners) { + for (int userId : toUserIds) { + listener.onUserChanged(userId, CURRENT_USER_CHANGED); + } } } /** * Returns an array of current user ids. This will always include the current user, and will - * also include any profiles of the current user. + * also include any profiles of the current user. The caller must never mutate the returned + * array. */ public int[] getCurrentUserIds() { - return getProfileUserIdsForParentUser(mCurrentUserId); + synchronized (this) { + if (mActivityManagerInternal == null) { + return new int[] {}; + } + } + + long identity = Binder.clearCallingIdentity(); + try { + return mActivityManagerInternal.getCurrentProfileIds(); + } finally { + Binder.restoreCallingIdentity(identity); + } } /** @@ -219,54 +177,47 @@ public class UserInfoHelper { * user. */ public boolean isCurrentUserId(@UserIdInt int userId) { - int currentUserId = mCurrentUserId; - return userId == currentUserId || ArrayUtils.contains( - getProfileUserIdsForParentUser(currentUserId), userId); - } + synchronized (this) { + if (mActivityManagerInternal == null) { + return false; + } + } - @GuardedBy("this") - private synchronized int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) { - if (parentUserId != mCachedParentUserId) { - long identity = Binder.clearCallingIdentity(); - try { - Preconditions.checkState(mUserManager != null); - - // more expensive check - check that argument really is a parent user id - if (Build.IS_DEBUGGABLE) { - Preconditions.checkArgument( - mUserManager.getProfileParent(parentUserId) == null); - } + long identity = Binder.clearCallingIdentity(); + try { + return mActivityManagerInternal.isCurrentProfile(userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } - mCachedParentUserId = parentUserId; - mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId); - } finally { - Binder.restoreCallingIdentity(identity); - } + private int[] getProfileIds(@UserIdInt int userId) { + synchronized (this) { + Preconditions.checkState(mUserManager != null); } - return mCachedProfileUserIds; + long identity = Binder.clearCallingIdentity(); + try { + return mUserManager.getEnabledProfileIds(userId); + } finally { + Binder.restoreCallingIdentity(identity); + } } /** * Dump info for debugging. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - boolean systemRunning; - synchronized (this) { - systemRunning = mUserManager != null; - } - - if (systemRunning) { - int[] currentUserIds = getProfileUserIdsForParentUser(mCurrentUserId); - pw.println("current users: " + Arrays.toString(currentUserIds)); - for (int userId : currentUserIds) { - if (mUserManager.hasUserRestrictionForUser(DISALLOW_SHARE_LOCATION, + int[] currentUserProfiles = getCurrentUserIds(); + pw.println("current users: " + Arrays.toString(currentUserProfiles)); + UserManager userManager = mContext.getSystemService(UserManager.class); + if (userManager != null) { + for (int userId : currentUserProfiles) { + if (userManager.hasUserRestrictionForUser(DISALLOW_SHARE_LOCATION, UserHandle.of(userId))) { pw.println(" u" + userId + " restricted"); } } - } else { - pw.println("current user: " + mCurrentUserId); } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 9d3385f20695..a95dc3035200 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -246,6 +246,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; +import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -2173,19 +2174,19 @@ public class NotificationManagerService extends SystemService { mStatsManager.setPullAtomCallback( PACKAGE_NOTIFICATION_PREFERENCES, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + ConcurrentUtils.DIRECT_EXECUTOR, mPullAtomCallback ); mStatsManager.setPullAtomCallback( PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + ConcurrentUtils.DIRECT_EXECUTOR, mPullAtomCallback ); mStatsManager.setPullAtomCallback( PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES, null, // use default PullAtomMetadata values - BackgroundThread.getExecutor(), + ConcurrentUtils.DIRECT_EXECUTOR, mPullAtomCallback ); } diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java index 81ee7d9eeef7..52fdc7983636 100644 --- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -21,7 +21,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.pm.ApplicationInfo; import android.content.pm.DataLoaderParamsParcel; import android.content.pm.IDataLoader; import android.content.pm.IDataLoaderManager; @@ -122,19 +121,7 @@ public class DataLoaderManagerService extends SystemService { ri.serviceInfo.packageName, ri.serviceInfo.name); // There should only be one matching provider inside the given package. // If there's more than one, return the first one found. - try { - ApplicationInfo ai = pm.getApplicationInfo(resolved.getPackageName(), 0); - if (!ai.isPrivilegedApp()) { - Slog.w(TAG, - "Data loader: " + resolved + " is not a privileged app, skipping."); - continue; - } - return resolved; - } catch (PackageManager.NameNotFoundException ex) { - Slog.w(TAG, - "Privileged data loader: " + resolved + " not found, skipping."); - } - + return resolved; } Slog.e(TAG, "Didn't find any matching data loader service provider."); return null; diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 236a6816b3e3..f827721be3b7 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -40,6 +40,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.VersionedPackage; @@ -126,8 +127,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS; /** Automatically destroy staged sessions that have not changed state in this time */ private static final long MAX_TIME_SINCE_UPDATE_MILLIS = 7 * DateUtils.DAY_IN_MILLIS; - /** Upper bound on number of active sessions for a UID */ - private static final long MAX_ACTIVE_SESSIONS = 1024; + /** Upper bound on number of active sessions for a UID that has INSTALL_PACKAGES */ + private static final long MAX_ACTIVE_SESSIONS_WITH_PERMISSION = 1024; + /** Upper bound on number of active sessions for a UID without INSTALL_PACKAGES */ + private static final long MAX_ACTIVE_SESSIONS_NO_PERMISSION = 50; /** Upper bound on number of historical sessions for a UID */ private static final long MAX_HISTORICAL_SESSIONS = 1048576; @@ -503,7 +506,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements + "to use a data loader"); } - String requestedInstallerPackageName = params.installerPackageName != null + // App package name and label length is restricted so that really long strings aren't + // written to disk. + if (params.appPackageName != null + && params.appPackageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) { + params.appPackageName = null; + } + + params.appLabel = TextUtils.trimToSize(params.appLabel, + PackageItemInfo.MAX_SAFE_LABEL_LENGTH); + + String requestedInstallerPackageName = (params.installerPackageName != null + && params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH) ? params.installerPackageName : installerPackageName; if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) { @@ -635,12 +649,23 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + if (params.whitelistedRestrictedPermissions != null) { + mPermissionManager.retainHardAndSoftRestrictedPermissions( + params.whitelistedRestrictedPermissions); + } + final int sessionId; final PackageInstallerSession session; synchronized (mSessions) { // Sanity check that installer isn't going crazy final int activeCount = getSessionCount(mSessions, callingUid); - if (activeCount >= MAX_ACTIVE_SESSIONS) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) + == PackageManager.PERMISSION_GRANTED) { + if (activeCount >= MAX_ACTIVE_SESSIONS_WITH_PERMISSION) { + throw new IllegalStateException( + "Too many active sessions for UID " + callingUid); + } + } else if (activeCount >= MAX_ACTIVE_SESSIONS_NO_PERMISSION) { throw new IllegalStateException( "Too many active sessions for UID " + callingUid); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6bb10c79d382..766fae64f647 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -148,6 +148,8 @@ import android.app.ResourcesManager; import android.app.admin.IDevicePolicyManager; import android.app.admin.SecurityLog; import android.app.backup.IBackupManager; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -639,6 +641,19 @@ public class PackageManagerService extends IPackageManager.Stub */ private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW; + /** + * Adding an installer package name to a package that does not have one set requires the + * INSTALL_PACKAGES permission. + * + * If the caller targets R, this will throw a SecurityException. Otherwise the request will + * fail silently. In both cases, and regardless of whether this change is enabled, the + * installer package will remain unchanged. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE = + 150857253; + public static final String PLATFORM_PACKAGE_NAME = "android"; private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; @@ -5264,15 +5279,17 @@ public class PackageManagerService extends IPackageManager.Stub * </ul> */ int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, - boolean matchSystemOnly) { + boolean isImplicitImageCaptureIntentAndNotSetByDpc) { return updateFlagsForResolve(flags, userId, callingUid, - wantInstantApps, matchSystemOnly, false /*onlyExposedExplicitly*/); + wantInstantApps, false /*onlyExposedExplicitly*/, + isImplicitImageCaptureIntentAndNotSetByDpc); } int updateFlagsForResolve(int flags, int userId, int callingUid, - boolean wantInstantApps, boolean onlyExposedExplicitly, boolean matchSystemOnly) { + boolean wantInstantApps, boolean onlyExposedExplicitly, + boolean isImplicitImageCaptureIntentAndNotSetByDpc) { // Safe mode means we shouldn't match any third-party components - if (mSafeMode || matchSystemOnly) { + if (mSafeMode || isImplicitImageCaptureIntentAndNotSetByDpc) { flags |= PackageManager.MATCH_SYSTEM_ONLY; } if (getInstantAppPackageName(callingUid) != null) { @@ -6400,7 +6417,8 @@ public class PackageManagerService extends IPackageManager.Stub if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart, - intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); + isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, + flags)); mPermissionManager.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/, false /*checkShell*/, "resolve intent"); @@ -6438,7 +6456,7 @@ public class PackageManagerService extends IPackageManager.Stub final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver()); final int flags = updateFlagsForResolve( 0, userId, callingUid, false /*includeInstantApps*/, - intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); + isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, 0)); final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags, userId); synchronized (mLock) { @@ -6684,6 +6702,40 @@ public class PackageManagerService extends IPackageManager.Stub return true; } + /** + * From Android R, camera intents have to match system apps. The only exception to this is if + * the DPC has set the camera persistent preferred activity. This case was introduced + * because it is important that the DPC has the ability to set both system and non-system + * camera persistent preferred activities. + * + * @return {@code true} if the intent is a camera intent and the persistent preferred + * activity was not set by the DPC. + */ + @GuardedBy("mLock") + private boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId, + String resolvedType, int flags) { + return intent.isImplicitImageCaptureIntent() && !isPersistentPreferredActivitySetByDpm( + intent, userId, resolvedType, flags); + } + + private boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId, + String resolvedType, int flags) { + PersistentPreferredIntentResolver ppir = mSettings.mPersistentPreferredActivities + .get(userId); + //TODO(b/158003772): Remove double query + List<PersistentPreferredActivity> pprefs = ppir != null + ? ppir.queryIntent(intent, resolvedType, + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, + userId) + : new ArrayList<>(); + for (PersistentPreferredActivity ppa : pprefs) { + if (ppa.mIsSetByDpm) { + return true; + } + } + return false; + } + @GuardedBy("mLock") private ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType, int flags, List<ResolveInfo> query, boolean debug, int userId) { @@ -6767,7 +6819,8 @@ public class PackageManagerService extends IPackageManager.Stub android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1; flags = updateFlagsForResolve( flags, userId, callingUid, false /*includeInstantApps*/, - intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); + isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, + flags)); intent = updateIntentForResolve(intent); // writer synchronized (mLock) { @@ -6980,7 +7033,8 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mLock) { int flags = updateFlagsForResolve(0, parent.id, callingUid, false /*includeInstantApps*/, - intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); + isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, parent.id, + resolvedType, 0)); CrossProfileDomainInfo xpDomainInfo = getCrossProfileDomainPreferredLpr( intent, resolvedType, flags, sourceUserId, parent.id); return xpDomainInfo != null; @@ -7067,7 +7121,8 @@ public class PackageManagerService extends IPackageManager.Stub flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart, comp != null || pkgName != null /*onlyExposedExplicitly*/, - intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); + isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, + flags)); if (comp != null) { final List<ResolveInfo> list = new ArrayList<>(1); final ActivityInfo ai = getActivityInfo(comp, flags, userId); @@ -7856,7 +7911,8 @@ public class PackageManagerService extends IPackageManager.Stub if (!mUserManager.exists(userId)) return Collections.emptyList(); final int callingUid = Binder.getCallingUid(); flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, - intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); + isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, + flags)); mPermissionManager.enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/, false /*checkShell*/, "query intent activity options"); @@ -8043,7 +8099,8 @@ public class PackageManagerService extends IPackageManager.Stub "query intent receivers"); final String instantAppPkgName = getInstantAppPackageName(callingUid); flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, - intent.isImplicitImageCaptureIntent() /*matchSystemOnly*/); + isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, + flags)); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -8134,7 +8191,7 @@ public class PackageManagerService extends IPackageManager.Stub int userId, int callingUid) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, - false /* matchSystemOnly */); + false /* isImplicitImageCaptureIntentAndNotSetByDpc */); List<ResolveInfo> query = queryIntentServicesInternal( intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/); if (query != null) { @@ -8166,7 +8223,7 @@ public class PackageManagerService extends IPackageManager.Stub "query intent receivers"); final String instantAppPkgName = getInstantAppPackageName(callingUid); flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps, - false /* matchSystemOnly */); + false /* isImplicitImageCaptureIntentAndNotSetByDpc */); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -8304,7 +8361,7 @@ public class PackageManagerService extends IPackageManager.Stub final int callingUid = Binder.getCallingUid(); final String instantAppPkgName = getInstantAppPackageName(callingUid); flags = updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, - false /* matchSystemOnly */); + false /* isImplicitImageCaptureIntentAndNotSetByDpc */); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -14130,19 +14187,38 @@ public class PackageManagerService extends IPackageManager.Stub // be signed with the same cert as the caller. String targetInstallerPackageName = targetPackageSetting.installSource.installerPackageName; - if (targetInstallerPackageName != null) { - PackageSetting setting = mSettings.mPackages.get( - targetInstallerPackageName); - // If the currently set package isn't valid, then it's always - // okay to change it. - if (setting != null) { - if (compareSignatures(callerSignature, - setting.signatures.mSigningDetails.signatures) - != PackageManager.SIGNATURE_MATCH) { - throw new SecurityException( - "Caller does not have same cert as old installer package " - + targetInstallerPackageName); + PackageSetting targetInstallerPkgSetting = targetInstallerPackageName == null ? null : + mSettings.mPackages.get(targetInstallerPackageName); + + if (targetInstallerPkgSetting != null) { + if (compareSignatures(callerSignature, + targetInstallerPkgSetting.signatures.mSigningDetails.signatures) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as old installer package " + + targetInstallerPackageName); + } + } else if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) + != PackageManager.PERMISSION_GRANTED) { + // This is probably an attempt to exploit vulnerability b/150857253 of taking + // privileged installer permissions when the installer has been uninstalled or + // was never set. + EventLog.writeEvent(0x534e4554, "150857253", callingUid, ""); + + long binderToken = Binder.clearCallingIdentity(); + try { + if (mInjector.getCompatibility().isChangeEnabledByUid( + THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE, + callingUid)) { + throw new SecurityException("Neither user " + callingUid + + " nor current process has " + + Manifest.permission.INSTALL_PACKAGES); + } else { + // If change disabled, fail silently for backwards compatibility + return; } + } finally { + Binder.restoreCallingIdentity(binderToken); } } @@ -19840,7 +19916,7 @@ public class PackageManagerService extends IPackageManager.Stub } synchronized (mLock) { mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter( - new PersistentPreferredActivity(filter, activity)); + new PersistentPreferredActivity(filter, activity, true)); scheduleWritePackageRestrictionsLocked(userId); } updateDefaultHomeNotLocked(userId); diff --git a/services/core/java/com/android/server/pm/PersistentPreferredActivity.java b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java index 0d4cdf9dee53..5a6fd0923f53 100644 --- a/services/core/java/com/android/server/pm/PersistentPreferredActivity.java +++ b/services/core/java/com/android/server/pm/PersistentPreferredActivity.java @@ -16,31 +16,34 @@ package com.android.server.pm; +import android.content.ComponentName; +import android.content.IntentFilter; +import android.util.Log; + import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; -import android.content.ComponentName; -import android.content.IntentFilter; -import android.util.Log; - import java.io.IOException; class PersistentPreferredActivity extends IntentFilter { private static final String ATTR_NAME = "name"; // component name private static final String ATTR_FILTER = "filter"; // filter + private static final String ATTR_SET_BY_DPM = "set-by-dpm"; // set by DPM private static final String TAG = "PersistentPreferredActivity"; private static final boolean DEBUG_FILTERS = false; final ComponentName mComponent; + final boolean mIsSetByDpm; - PersistentPreferredActivity(IntentFilter filter, ComponentName activity) { + PersistentPreferredActivity(IntentFilter filter, ComponentName activity, boolean isSetByDpm) { super(filter); mComponent = activity; + mIsSetByDpm = isSetByDpm; } PersistentPreferredActivity(XmlPullParser parser) throws XmlPullParserException, IOException { @@ -52,6 +55,8 @@ class PersistentPreferredActivity extends IntentFilter { "Bad activity name " + shortComponent + " at " + parser.getPositionDescription()); } + mIsSetByDpm = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_SET_BY_DPM)); + int outerDepth = parser.getDepth(); String tagName = parser.getName(); int type; @@ -83,6 +88,7 @@ class PersistentPreferredActivity extends IntentFilter { public void writeToXml(XmlSerializer serializer) throws IOException { serializer.attribute(null, ATTR_NAME, mComponent.flattenToShortString()); + serializer.attribute(null, ATTR_SET_BY_DPM, Boolean.toString(mIsSetByDpm)); serializer.startTag(null, ATTR_FILTER); super.writeToXml(serializer); serializer.endTag(null, ATTR_FILTER); @@ -91,6 +97,7 @@ class PersistentPreferredActivity extends IntentFilter { @Override public String toString() { return "PersistentPreferredActivity{0x" + Integer.toHexString(System.identityHashCode(this)) - + " " + mComponent.flattenToShortString() + "}"; + + " " + mComponent.flattenToShortString() + + ", mIsSetByDpm=" + mIsSetByDpm + "}"; } } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 3ec139763e80..0c42ff6be520 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -2419,6 +2419,9 @@ public class ShortcutService extends IShortcutService.Stub { @Override public ParceledListSlice<ShortcutManager.ShareShortcutInfo> getShareTargets(String packageName, IntentFilter filter, @UserIdInt int userId) { + Preconditions.checkStringNotEmpty(packageName, "packageName"); + Objects.requireNonNull(filter, "intentFilter"); + verifyCaller(packageName, userId); enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, "getShareTargets"); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 79805e3b42ae..8ccf837f64dc 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -77,7 +77,11 @@ import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.rollback.WatchdogRollbackLogger; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -102,6 +106,9 @@ public class StagingManager { private final PreRebootVerificationHandler mPreRebootVerificationHandler; private final Supplier<PackageParser2> mPackageParserSupplier; + private final File mFailureReasonFile = new File("/metadata/staged-install/failure_reason.txt"); + private String mFailureReason; + @GuardedBy("mStagedSessions") private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>(); @@ -125,6 +132,12 @@ public class StagingManager { mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mPreRebootVerificationHandler = new PreRebootVerificationHandler( BackgroundThread.get().getLooper()); + + if (mFailureReasonFile.exists()) { + try (BufferedReader reader = new BufferedReader(new FileReader(mFailureReasonFile))) { + mFailureReason = reader.readLine(); + } catch (Exception ignore) { } + } } /** @@ -383,10 +396,19 @@ public class StagingManager { } // Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device. - private void abortCheckpoint(String errorMsg) { - Slog.e(TAG, "Aborting checkpoint: " + errorMsg); + private void abortCheckpoint(int sessionId, String errorMsg) { + String failureReason = "Failed to install sessionId: " + sessionId + " Error: " + errorMsg; + Slog.e(TAG, failureReason); try { if (supportsCheckpoint() && needsCheckpoint()) { + // Store failure reason for next reboot + try (BufferedWriter writer = + new BufferedWriter(new FileWriter(mFailureReasonFile))) { + writer.write(failureReason); + } catch (Exception e) { + Slog.w(TAG, "Failed to save failure reason: ", e); + } + // Only revert apex sessions if device supports updating apex if (mApexManager.isApexSupported()) { mApexManager.revertActiveSessions(); @@ -592,14 +614,12 @@ public class StagingManager { // If checkpoint is supported, then we only resume sessions if we are in checkpointing // mode. If not, we fail all sessions. if (supportsCheckpoint() && !needsCheckpoint()) { - // TODO(b/146343545): Persist failure reason across checkpoint reboot - Slog.d(TAG, "Reverting back to safe state. Marking " + session.sessionId - + " as failed."); - String errorMsg = "Reverting back to safe state"; - if (!TextUtils.isEmpty(mNativeFailureReason)) { - errorMsg = "Entered fs-rollback mode and reverted session due to crashing " - + "native process: " + mNativeFailureReason; + String errorMsg = "Reverting back to safe state. Marking " + session.sessionId + + " as failed"; + if (!TextUtils.isEmpty(mFailureReason)) { + errorMsg = errorMsg + ": " + mFailureReason; } + Slog.d(TAG, errorMsg); session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_UNKNOWN, errorMsg); return; } @@ -624,7 +644,7 @@ public class StagingManager { + "supposed to be activated"; session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); - abortCheckpoint(errorMsg); + abortCheckpoint(session.sessionId, errorMsg); return; } if (isApexSessionFailed(apexSessionInfo)) { @@ -636,7 +656,7 @@ public class StagingManager { } session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); - abortCheckpoint(errorMsg); + abortCheckpoint(session.sessionId, errorMsg); return; } if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) { @@ -647,7 +667,7 @@ public class StagingManager { + "didn't activate nor fail. Marking it as failed anyway."; session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMsg); - abortCheckpoint(errorMsg); + abortCheckpoint(session.sessionId, errorMsg); return; } } @@ -664,7 +684,7 @@ public class StagingManager { installApksInSession(session); } catch (PackageManagerException e) { session.setStagedSessionFailed(e.error, e.getMessage()); - abortCheckpoint(e.getMessage()); + abortCheckpoint(session.sessionId, e.getMessage()); // If checkpoint is not supported, we have to handle failure for one staged session. if (!hasApex) { @@ -1189,6 +1209,8 @@ public class StagingManager { ctx.unregisterReceiver(this); } }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); + + mFailureReasonFile.delete(); } private static class LocalIntentReceiverAsync { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index d5c9424528bd..40fa798309c1 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2250,9 +2250,6 @@ public class UserManagerService extends IUserManager.Stub { // Managed profiles have their own specific rules. final boolean isManagedProfile = type.isManagedProfile(); if (isManagedProfile) { - if (ActivityManager.isLowRamDeviceStatic()) { - return false; - } if (!mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_MANAGED_USERS)) { return false; diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 8f3bf39d4fc5..cd53fb9ba52f 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -816,7 +816,7 @@ public final class DefaultPermissionGrantPolicy { if (!TextUtils.isEmpty(contentCapturePackageName)) { grantPermissionsToSystemPackage(pm, contentCapturePackageName, userId, PHONE_PERMISSIONS, SMS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, - CONTACTS_PERMISSIONS, STORAGE_PERMISSIONS); + CONTACTS_PERMISSIONS); } // Atthention Service 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 b0d4d957fc21..d3f3ba1dc6bb 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -26,6 +26,7 @@ import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; +import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE; import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; @@ -1804,8 +1805,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { continue; } - // If this permission was granted by default, make sure it is. - if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0) { + // If this permission was granted by default or role, make sure it is. + if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0 + || (oldFlags & FLAG_PERMISSION_GRANTED_BY_ROLE) != 0) { // PermissionPolicyService will handle the app op for runtime permissions later. grantRuntimePermissionInternal(permName, packageName, false, Process.SYSTEM_UID, userId, delayingPermCallback); @@ -4948,6 +4950,20 @@ public class PermissionManagerService extends IPermissionManager.Stub { StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback); } } + + @Override + public void retainHardAndSoftRestrictedPermissions(@NonNull List<String> permissions) { + synchronized (mLock) { + Iterator<String> iterator = permissions.iterator(); + while (iterator.hasNext()) { + String permission = iterator.next(); + BasePermission basePermission = mSettings.mPermissions.get(permission); + if (basePermission == null || !basePermission.isHardOrSoftRestricted()) { + iterator.remove(); + } + } + } + } } private static final class OnPermissionChangeListeners extends Handler { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index 57a25eddf7ce..4412162a5cc8 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -36,6 +36,7 @@ import java.util.function.Consumer; * TODO: Should be merged into PermissionManagerInternal, but currently uses internal classes. */ public abstract class PermissionManagerServiceInternal extends PermissionManagerInternal { + /** * Provider for package names. */ @@ -455,4 +456,10 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager /** Called when a new user has been created. */ public abstract void onNewUserCreated(@UserIdInt int userId); + + /** + * Removes invalid permissions which are not {@link PermissionInfo#FLAG_HARD_RESTRICTED} or + * {@link PermissionInfo#FLAG_SOFT_RESTRICTED} from the input. + */ + public abstract void retainHardAndSoftRestrictedPermissions(@NonNull List<String> permissions); } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java index 49c781905898..2f963b7e6b35 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -20,11 +20,16 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback; import android.hardware.soundtrigger.V2_2.ISoundTriggerHw; +import android.media.audio.common.AudioConfig; +import android.media.audio.common.AudioOffloadInfo; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerModule; import android.media.soundtrigger_middleware.ModelParameterRange; +import android.media.soundtrigger_middleware.PhraseRecognitionEvent; +import android.media.soundtrigger_middleware.PhraseRecognitionExtra; import android.media.soundtrigger_middleware.PhraseSoundModel; import android.media.soundtrigger_middleware.RecognitionConfig; +import android.media.soundtrigger_middleware.RecognitionEvent; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundModelType; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; @@ -540,20 +545,20 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient { switch (mModelType) { case SoundModelType.GENERIC: { android.media.soundtrigger_middleware.RecognitionEvent event = - new android.media.soundtrigger_middleware.RecognitionEvent(); + newEmptyRecognitionEvent(); event.status = android.media.soundtrigger_middleware.RecognitionStatus.ABORTED; + event.type = SoundModelType.GENERIC; mCallback.onRecognition(mHandle, event); } break; case SoundModelType.KEYPHRASE: { android.media.soundtrigger_middleware.PhraseRecognitionEvent event = - new android.media.soundtrigger_middleware.PhraseRecognitionEvent(); - event.common = - new android.media.soundtrigger_middleware.RecognitionEvent(); + newEmptyPhraseRecognitionEvent(); event.common.status = android.media.soundtrigger_middleware.RecognitionStatus.ABORTED; + event.common.type = SoundModelType.KEYPHRASE; mCallback.onPhraseRecognition(mHandle, event); } break; @@ -614,4 +619,35 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient { } } } + + /** + * Creates a default-initialized recognition event. + * + * Object fields are default constructed. + * Array fields are initialized to 0 length. + * + * @return The event. + */ + private static RecognitionEvent newEmptyRecognitionEvent() { + RecognitionEvent result = new RecognitionEvent(); + result.audioConfig = new AudioConfig(); + result.audioConfig.offloadInfo = new AudioOffloadInfo(); + result.data = new byte[0]; + return result; + } + + /** + * Creates a default-initialized phrase recognition event. + * + * Object fields are default constructed. + * Array fields are initialized to 0 length. + * + * @return The event. + */ + private static PhraseRecognitionEvent newEmptyPhraseRecognitionEvent() { + PhraseRecognitionEvent result = new PhraseRecognitionEvent(); + result.common = newEmptyRecognitionEvent(); + result.phraseExtras = new PhraseRecognitionExtra[0]; + return result; + } } 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 6c1ff728e6b9..ab459fdadd21 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -3319,8 +3319,8 @@ public class StatsPullAtomService extends SystemService { public void run() { try { estimateAppOpsSamplingRate(); - } catch (Exception e) { - Slog.e(TAG, "AppOps sampling ratio estimation failed"); + } catch (Throwable e) { + Slog.e(TAG, "AppOps sampling ratio estimation failed: ", e); synchronized (mAppOpsSamplingRateLock) { mAppOpsSamplingRate = min(mAppOpsSamplingRate, 10); } @@ -3361,7 +3361,7 @@ public class StatsPullAtomService extends SystemService { Instant.now().minus(1, ChronoUnit.DAYS).toEpochMilli(), Long.MAX_VALUE).setFlags( OP_FLAGS_PULLED).build(); - appOps.getHistoricalOps(histOpsRequest, mContext.getMainExecutor(), ops::complete); + appOps.getHistoricalOps(histOpsRequest, AsyncTask.THREAD_POOL_EXECUTOR, ops::complete); HistoricalOps histOps = ops.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); List<AppOpEntry> opsList = diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index 9476e9260c73..c38d649ada9b 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -77,6 +77,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.Xml; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; @@ -122,6 +123,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { PackageManagerInternal mPmInternal; /** File storing persisted {@link #mGrantedUriPermissions}. */ + @GuardedBy("mLock") private final AtomicFile mGrantFile; /** XML constants used in {@link #mGrantFile} */ @@ -142,6 +144,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { * This optimized lookup structure maps from {@link UriPermission#targetUid} * to {@link UriPermission#uri} to {@link UriPermission}. */ + @GuardedBy("mLock") private final SparseArray<ArrayMap<GrantUri, UriPermission>> mGrantedUriPermissions = new SparseArray<>(); @@ -206,39 +209,44 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } } + @Override + public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg, + Uri uri, final int modeFlags, int sourceUserId, int targetUserId) { + grantUriPermissionFromOwnerUnlocked(token, fromUid, targetPkg, uri, modeFlags, sourceUserId, + targetUserId); + } + /** * @param uri This uri must NOT contain an embedded userId. * @param sourceUserId The userId in which the uri is to be resolved. * @param targetUserId The userId of the app that receives the grant. */ - @Override - public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg, Uri uri, - final int modeFlags, int sourceUserId, int targetUserId) { + private void grantUriPermissionFromOwnerUnlocked(IBinder token, int fromUid, String targetPkg, + Uri uri, final int modeFlags, int sourceUserId, int targetUserId) { targetUserId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), targetUserId, false, ALLOW_FULL_ONLY, "grantUriPermissionFromOwner", null); - synchronized(mLock) { - UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token); - if (owner == null) { - throw new IllegalArgumentException("Unknown owner: " + token); - } - if (fromUid != Binder.getCallingUid()) { - if (Binder.getCallingUid() != myUid()) { - // Only system code can grant URI permissions on behalf - // of other users. - throw new SecurityException("nice try"); - } - } - if (targetPkg == null) { - throw new IllegalArgumentException("null target"); - } - if (uri == null) { - throw new IllegalArgumentException("null uri"); - } - grantUriPermission(fromUid, targetPkg, new GrantUri(sourceUserId, uri, modeFlags), - modeFlags, owner, targetUserId); + UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token); + if (owner == null) { + throw new IllegalArgumentException("Unknown owner: " + token); } + if (fromUid != Binder.getCallingUid()) { + if (Binder.getCallingUid() != myUid()) { + // Only system code can grant URI permissions on behalf + // of other users. + throw new SecurityException("nice try"); + } + } + if (targetPkg == null) { + throw new IllegalArgumentException("null target"); + } + if (uri == null) { + throw new IllegalArgumentException("null uri"); + } + + grantUriPermissionUnlocked(fromUid, targetPkg, new GrantUri(sourceUserId, uri, modeFlags), + modeFlags, owner, targetUserId); } @Override @@ -362,7 +370,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { persistChanged |= prefixPerm.takePersistableModes(modeFlags); } - persistChanged |= maybePrunePersistedUriGrants(uid); + persistChanged |= maybePrunePersistedUriGrantsLocked(uid); if (persistChanged) { schedulePersistUriGrants(); @@ -374,8 +382,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { public void clearGrantedUriPermissions(String packageName, int userId) { mAmInternal.enforceCallingPermission( CLEAR_APP_GRANTED_URI_PERMISSIONS, "clearGrantedUriPermissions"); - synchronized(mLock) { - removeUriPermissionsForPackage(packageName, userId, true, true); + synchronized (mLock) { + removeUriPermissionsForPackageLocked(packageName, userId, true, true); } } @@ -416,11 +424,11 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { if (exactPerm != null) { persistChanged |= exactPerm.releasePersistableModes(modeFlags); - removeUriPermissionIfNeeded(exactPerm); + removeUriPermissionIfNeededLocked(exactPerm); } if (prefixPerm != null) { persistChanged |= prefixPerm.releasePersistableModes(modeFlags); - removeUriPermissionIfNeeded(prefixPerm); + removeUriPermissionIfNeededLocked(prefixPerm); } if (persistChanged) { @@ -441,8 +449,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { * @param targetOnly When {@code true}, only remove permissions where the app is the target, * not source. */ - void removeUriPermissionsForPackage( - String packageName, int userHandle, boolean persistable, boolean targetOnly) { + @GuardedBy("mLock") + private void removeUriPermissionsForPackageLocked(String packageName, int userHandle, + boolean persistable, boolean targetOnly) { if (userHandle == UserHandle.USER_ALL && packageName == null) { throw new IllegalArgumentException("Must narrow by either package or user"); } @@ -494,7 +503,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } /** Returns if the ContentProvider has granted a uri to callingUid */ - boolean checkAuthorityGrants(int callingUid, ProviderInfo cpi, int userId, boolean checkUser) { + @GuardedBy("mLock") + private boolean checkAuthorityGrantsLocked(int callingUid, ProviderInfo cpi, int userId, + boolean checkUser) { final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid); if (perms != null) { for (int i = perms.size() - 1; i >= 0; i--) { @@ -530,7 +541,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { * * @return if any mutations occured that require persisting. */ - private boolean maybePrunePersistedUriGrants(int uid) { + @GuardedBy("mLock") + private boolean maybePrunePersistedUriGrantsLocked(int uid) { final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(uid); if (perms == null) return false; if (perms.size() < MAX_PERSISTED_URI_GRANTS) return false; @@ -552,14 +564,14 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { if (DEBUG) Slog.v(TAG, "Trimming grant created at " + perm.persistedCreateTime); perm.releasePersistableModes(~0); - removeUriPermissionIfNeeded(perm); + removeUriPermissionIfNeededLocked(perm); } return true; } /** Like checkGrantUriPermission, but takes an Intent. */ - NeededUriGrants checkGrantUriPermissionFromIntent(int callingUid, + private NeededUriGrants checkGrantUriPermissionFromIntentUnlocked(int callingUid, String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId) { if (DEBUG) Slog.v(TAG, "Checking URI perm to data=" + (intent != null ? intent.getData() : null) @@ -598,7 +610,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } if (data != null) { GrantUri grantUri = GrantUri.resolve(contentUserHint, data, mode); - targetUid = checkGrantUriPermission(callingUid, targetPkg, grantUri, mode, targetUid); + targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, mode, + targetUid); if (targetUid > 0) { if (needed == null) { needed = new NeededUriGrants(targetPkg, targetUid, mode); @@ -611,7 +624,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { Uri uri = clip.getItemAt(i).getUri(); if (uri != null) { GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode); - targetUid = checkGrantUriPermission(callingUid, targetPkg, + targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, mode, targetUid); if (targetUid > 0) { if (needed == null) { @@ -622,7 +635,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } else { Intent clipIntent = clip.getItemAt(i).getIntent(); if (clipIntent != null) { - NeededUriGrants newNeeded = checkGrantUriPermissionFromIntent( + NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentUnlocked( callingUid, targetPkg, clipIntent, mode, needed, targetUserId); if (newNeeded != null) { needed = newNeeded; @@ -635,7 +648,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { return needed; } - void readGrantedUriPermissions() { + @GuardedBy("mLock") + private void readGrantedUriPermissionsLocked() { if (DEBUG) Slog.v(TAG, "readGrantedUriPermissions()"); final long now = System.currentTimeMillis(); @@ -681,7 +695,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { if (targetUid != -1) { final GrantUri grantUri = new GrantUri(sourceUserId, uri, prefix ? Intent.FLAG_GRANT_PREFIX_URI_PERMISSION : 0); - final UriPermission perm = findOrCreateUriPermission( + final UriPermission perm = findOrCreateUriPermissionLocked( sourcePkg, targetPkg, targetUid, grantUri); perm.initPersistedModes(modeFlags, createdTime); } @@ -703,7 +717,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } } - private UriPermission findOrCreateUriPermission(String sourcePkg, + @GuardedBy("mLock") + private UriPermission findOrCreateUriPermissionLocked(String sourcePkg, String targetPkg, int targetUid, GrantUri grantUri) { ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid); if (targetUris == null) { @@ -740,15 +755,18 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { return; } - final UriPermission perm = findOrCreateUriPermission( - pi.packageName, targetPkg, targetUid, grantUri); + final UriPermission perm; + synchronized (mLock) { + perm = findOrCreateUriPermissionLocked(pi.packageName, targetPkg, targetUid, grantUri); + } perm.grantModes(modeFlags, owner); mPmInternal.grantImplicitAccess(UserHandle.getUserId(targetUid), null, UserHandle.getAppId(targetUid), pi.applicationInfo.uid, false /*direct*/); } /** Like grantUriPermissionUnchecked, but takes an Intent. */ - void grantUriPermissionUncheckedFromIntent(NeededUriGrants needed, UriPermissionOwner owner) { + private void grantUriPermissionUncheckedFromIntent(NeededUriGrants needed, + UriPermissionOwner owner) { if (needed == null) { return; } @@ -759,7 +777,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } } - void grantUriPermission(int callingUid, String targetPkg, GrantUri grantUri, + private void grantUriPermissionUnlocked(int callingUid, String targetPkg, GrantUri grantUri, final int modeFlags, UriPermissionOwner owner, int targetUserId) { if (targetPkg == null) { throw new NullPointerException("targetPkg"); @@ -767,7 +785,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { int targetUid = mPmInternal.getPackageUidInternal(targetPkg, MATCH_DEBUG_TRIAGED_MISSING, targetUserId); - targetUid = checkGrantUriPermission(callingUid, targetPkg, grantUri, modeFlags, targetUid); + targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, modeFlags, + targetUid); if (targetUid < 0) { return; } @@ -775,7 +794,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { grantUriPermissionUnchecked(targetUid, targetPkg, grantUri, modeFlags, owner); } - void revokeUriPermission(String targetPackage, int callingUid, GrantUri grantUri, + private void revokeUriPermission(String targetPackage, int callingUid, GrantUri grantUri, final int modeFlags) { if (DEBUG) Slog.v(TAG, "Revoking all granted permissions to " + grantUri); @@ -788,8 +807,19 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { return; } + final boolean callerHoldsPermissions = checkHoldingPermissionsUnlocked(pi, grantUri, + callingUid, modeFlags); + synchronized (mLock) { + revokeUriPermissionLocked(targetPackage, callingUid, grantUri, modeFlags, + callerHoldsPermissions); + } + } + + @GuardedBy("mLock") + private void revokeUriPermissionLocked(String targetPackage, int callingUid, GrantUri grantUri, + final int modeFlags, final boolean callerHoldsPermissions) { // Does the caller have this permission on the URI? - if (!checkHoldingPermissions(pi, grantUri, callingUid, modeFlags)) { + if (!callerHoldsPermissions) { // If they don't have direct access to the URI, then revoke any // ownerless URI permissions that have been granted to them. final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid); @@ -861,7 +891,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { * the given {@link ProviderInfo}. Final permission checking is always done * in {@link ContentProvider}. */ - private boolean checkHoldingPermissions( + private boolean checkHoldingPermissionsUnlocked( ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) { if (DEBUG) Slog.v(TAG, "checkHoldingPermissions: uri=" + grantUri + " uid=" + uid); if (UserHandle.getUserId(uid) != grantUri.sourceUserId) { @@ -870,11 +900,17 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { return false; } } - return checkHoldingPermissionsInternal(pi, grantUri, uid, modeFlags, true); + return checkHoldingPermissionsInternalUnlocked(pi, grantUri, uid, modeFlags, true); } - private boolean checkHoldingPermissionsInternal(ProviderInfo pi, + private boolean checkHoldingPermissionsInternalUnlocked(ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags, boolean considerUidPermissions) { + // We must never hold our local mLock in this method, since we may need + // to call into ActivityManager for dynamic permission checks + if (Thread.holdsLock(mLock)) { + throw new IllegalStateException("Must never hold local mLock"); + } + if (pi.applicationInfo.uid == uid) { return true; } else if (!pi.exported) { @@ -968,7 +1004,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { return readMet && writeMet && forceMet; } - private void removeUriPermissionIfNeeded(UriPermission perm) { + @GuardedBy("mLock") + private void removeUriPermissionIfNeededLocked(UriPermission perm) { if (perm.modeFlags != 0) { return; } @@ -985,6 +1022,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } } + @GuardedBy("mLock") private UriPermission findUriPermissionLocked(int targetUid, GrantUri grantUri) { final ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid); if (targetUris != null) { @@ -1020,7 +1058,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { * If you already know the uid of the target, you can supply it in * lastTargetUid else set that to -1. */ - int checkGrantUriPermission(int callingUid, String targetPkg, GrantUri grantUri, + private int checkGrantUriPermissionUnlocked(int callingUid, String targetPkg, GrantUri grantUri, int modeFlags, int lastTargetUid) { if (!Intent.isAccessUriMode(modeFlags)) { return -1; @@ -1076,7 +1114,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { boolean targetHoldsPermission = false; if (targetUid >= 0) { // First... does the target actually need this permission? - if (checkHoldingPermissions(pi, grantUri, targetUid, modeFlags)) { + if (checkHoldingPermissionsUnlocked(pi, grantUri, targetUid, modeFlags)) { // No need to grant the target this permission. if (DEBUG) Slog.v(TAG, "Target " + targetPkg + " already has full permission to " + grantUri); @@ -1144,7 +1182,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { */ boolean specialCrossUserGrant = targetUid >= 0 && UserHandle.getUserId(targetUid) != grantUri.sourceUserId - && checkHoldingPermissionsInternal(pi, grantUri, callingUid, + && checkHoldingPermissionsInternalUnlocked(pi, grantUri, callingUid, modeFlags, false /*without considering the uid permissions*/); // Second... is the provider allowing granting of URI permissions? @@ -1179,9 +1217,13 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } // Third... does the caller itself have permission to access this uri? - if (!checkHoldingPermissions(pi, grantUri, callingUid, modeFlags)) { + if (!checkHoldingPermissionsUnlocked(pi, grantUri, callingUid, modeFlags)) { // Require they hold a strong enough Uri permission - if (!checkUriPermission(grantUri, callingUid, modeFlags)) { + final boolean res; + synchronized (mLock) { + res = checkUriPermissionLocked(grantUri, callingUid, modeFlags); + } + if (!res) { if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(pi.readPermission)) { throw new SecurityException( "UID " + callingUid + " does not have permission to " + grantUri @@ -1200,13 +1242,14 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { /** * @param userId The userId in which the uri is to be resolved. */ - int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri, int modeFlags, - int userId) { - return checkGrantUriPermission(callingUid, targetPkg, + private int checkGrantUriPermissionUnlocked(int callingUid, String targetPkg, Uri uri, + int modeFlags, int userId) { + return checkGrantUriPermissionUnlocked(callingUid, targetPkg, new GrantUri(userId, uri, modeFlags), modeFlags, -1); } - boolean checkUriPermission(GrantUri grantUri, int uid, final int modeFlags) { + @GuardedBy("mLock") + private boolean checkUriPermissionLocked(GrantUri grantUri, int uid, final int modeFlags) { final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0; final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE : UriPermission.STRENGTH_OWNED; @@ -1238,7 +1281,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { return false; } - private void writeGrantedUriPermissions() { + @GuardedBy("mLock") + private void writeGrantedUriPermissionsLocked() { if (DEBUG) Slog.v(TAG, "writeGrantedUriPermissions()"); final long startTime = SystemClock.uptimeMillis(); @@ -1299,34 +1343,35 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { public void handleMessage(Message msg) { switch (msg.what) { case PERSIST_URI_GRANTS_MSG: { - writeGrantedUriPermissions(); + synchronized (mLock) { + writeGrantedUriPermissionsLocked(); + } break; } } } } - final class LocalService implements UriGrantsManagerInternal { + private final class LocalService implements UriGrantsManagerInternal { @Override public void removeUriPermissionIfNeeded(UriPermission perm) { synchronized (mLock) { - UriGrantsManagerService.this.removeUriPermissionIfNeeded(perm); + UriGrantsManagerService.this.removeUriPermissionIfNeededLocked(perm); } } @Override public void revokeUriPermission(String targetPackage, int callingUid, GrantUri grantUri, int modeFlags) { - synchronized (mLock) { - UriGrantsManagerService.this.revokeUriPermission( - targetPackage, callingUid, grantUri, modeFlags); - } + UriGrantsManagerService.this.revokeUriPermission( + targetPackage, callingUid, grantUri, modeFlags); } @Override public boolean checkUriPermission(GrantUri grantUri, int uid, int modeFlags) { synchronized (mLock) { - return UriGrantsManagerService.this.checkUriPermission(grantUri, uid, modeFlags); + return UriGrantsManagerService.this.checkUriPermissionLocked(grantUri, uid, + modeFlags); } } @@ -1334,83 +1379,73 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { public int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri, int modeFlags, int userId) { enforceNotIsolatedCaller("checkGrantUriPermission"); - synchronized (mLock) { - return UriGrantsManagerService.this.checkGrantUriPermission( - callingUid, targetPkg, uri, modeFlags, userId); - } + return UriGrantsManagerService.this.checkGrantUriPermissionUnlocked( + callingUid, targetPkg, uri, modeFlags, userId); } @Override public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid, String targetPkg, int targetUserId) { - synchronized (mLock) { - final int mode = (intent != null) ? intent.getFlags() : 0; - return UriGrantsManagerService.this.checkGrantUriPermissionFromIntent( - callingUid, targetPkg, intent, mode, null, targetUserId); - } + final int mode = (intent != null) ? intent.getFlags() : 0; + return UriGrantsManagerService.this.checkGrantUriPermissionFromIntentUnlocked( + callingUid, targetPkg, intent, mode, null, targetUserId); } @Override public void grantUriPermissionUncheckedFromIntent(NeededUriGrants needed, UriPermissionOwner owner) { - synchronized (mLock) { - UriGrantsManagerService.this.grantUriPermissionUncheckedFromIntent(needed, owner); - } + UriGrantsManagerService.this.grantUriPermissionUncheckedFromIntent(needed, owner); } @Override public void onSystemReady() { synchronized (mLock) { - UriGrantsManagerService.this.readGrantedUriPermissions(); + UriGrantsManagerService.this.readGrantedUriPermissionsLocked(); } } @Override public IBinder newUriPermissionOwner(String name) { enforceNotIsolatedCaller("newUriPermissionOwner"); - synchronized(mLock) { - UriPermissionOwner owner = new UriPermissionOwner(this, name); - return owner.getExternalToken(); - } + UriPermissionOwner owner = new UriPermissionOwner(this, name); + return owner.getExternalToken(); } @Override public void removeUriPermissionsForPackage(String packageName, int userHandle, boolean persistable, boolean targetOnly) { - synchronized(mLock) { - UriGrantsManagerService.this.removeUriPermissionsForPackage( + synchronized (mLock) { + UriGrantsManagerService.this.removeUriPermissionsForPackageLocked( packageName, userHandle, persistable, targetOnly); } } @Override public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId) { - synchronized(mLock) { - final UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token); - if (owner == null) { - throw new IllegalArgumentException("Unknown owner: " + token); - } + final UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token); + if (owner == null) { + throw new IllegalArgumentException("Unknown owner: " + token); + } - if (uri == null) { - owner.removeUriPermissions(mode); - } else { - owner.removeUriPermission(new GrantUri(userId, uri, mode), mode); - } + if (uri == null) { + owner.removeUriPermissions(mode); + } else { + owner.removeUriPermission(new GrantUri(userId, uri, mode), mode); } } @Override public boolean checkAuthorityGrants(int callingUid, ProviderInfo cpi, int userId, boolean checkUser) { - synchronized(mLock) { - return UriGrantsManagerService.this.checkAuthorityGrants( + synchronized (mLock) { + return UriGrantsManagerService.this.checkAuthorityGrantsLocked( callingUid, cpi, userId, checkUser); } } @Override public void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) { - synchronized(mLock) { + synchronized (mLock) { boolean needSep = false; boolean printedAnything = false; if (mGrantedUriPermissions.size() > 0) { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 7f03778ab1c7..0d5621dc0e4f 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -23,7 +23,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; @@ -269,9 +268,6 @@ class ActivityStack extends Task { private final AnimatingActivityRegistry mAnimatingActivityRegistry = new AnimatingActivityRegistry(); - /** Stores the override windowing-mode from before a transient mode change (eg. split) */ - private int mRestoreOverrideWindowingMode = WINDOWING_MODE_UNDEFINED; - private boolean mTopActivityOccludesKeyguard; private ActivityRecord mTopDismissingKeyguardActivity; @@ -662,19 +658,6 @@ class ActivityStack extends Task { } /** - * A transient windowing mode is one which activities enter into temporarily. Examples of this - * are Split window modes and pip. Non-transient modes are modes that displays can adopt. - * - * @param windowingMode the windowingMode to test for transient-ness. - * @return {@code true} if the windowing mode is transient, {@code false} otherwise. - */ - private static boolean isTransientWindowingMode(int windowingMode) { - return windowingMode == WINDOWING_MODE_PINNED - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - } - - /** * Specialization of {@link #setWindowingMode(int)} for this subclass. * * @param preferredWindowingMode the preferred windowing mode. This may not be honored depending @@ -698,11 +681,6 @@ class ActivityStack extends Task { final int currentOverrideMode = getRequestedOverrideWindowingMode(); final Task topTask = getTopMostTask(); int windowingMode = preferredWindowingMode; - if (preferredWindowingMode == WINDOWING_MODE_UNDEFINED - && isTransientWindowingMode(currentMode)) { - // Leaving a transient mode. Interpret UNDEFINED as "restore" - windowingMode = mRestoreOverrideWindowingMode; - } // Need to make sure windowing mode is supported. If we in the process of creating the stack // no need to resolve the windowing mode again as it is already resolved to the right mode. @@ -712,29 +690,16 @@ class ActivityStack extends Task { windowingMode = WINDOWING_MODE_UNDEFINED; } } - if (taskDisplayArea.getRootSplitScreenPrimaryTask() == this - && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - // Resolution to split-screen secondary for the primary split-screen stack means - // we want to leave split-screen mode. - windowingMode = mRestoreOverrideWindowingMode; - } final boolean alreadyInSplitScreenMode = taskDisplayArea.isSplitScreenModeActivated(); - // Take any required action due to us not supporting the preferred windowing mode. - if (alreadyInSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN + if (creating && alreadyInSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN && isActivityTypeStandardOrUndefined()) { - final boolean preferredSplitScreen = - preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - if (preferredSplitScreen || creating) { - // Looks like we can't launch in split screen mode or the stack we are launching - // doesn't support split-screen mode, go ahead an dismiss split-screen and display a - // warning toast about it. - mAtmService.getTaskChangeNotificationController() - .notifyActivityDismissingDockedStack(); - taskDisplayArea.onSplitScreenModeDismissed(this); - } + // If the stack is being created explicitly in fullscreen mode, dismiss split-screen + // and display a warning toast about it. + mAtmService.getTaskChangeNotificationController() + .notifyActivityDismissingDockedStack(); + taskDisplayArea.onSplitScreenModeDismissed(this); } if (currentMode == windowingMode) { @@ -797,9 +762,6 @@ class ActivityStack extends Task { + " while there is already one isn't currently supported"); //return; } - if (isTransientWindowingMode(windowingMode) && !isTransientWindowingMode(currentMode)) { - mRestoreOverrideWindowingMode = currentOverrideMode; - } mTmpRect2.setEmpty(); if (windowingMode != WINDOWING_MODE_FULLSCREEN) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 59181a64f423..a5b94b327699 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4776,6 +4776,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @param sc The new SurfaceControl, where the DisplayContent's surfaces will be re-parented to. */ void reparentDisplayContent(WindowState win, SurfaceControl sc) { + if (mParentWindow != null) { + mParentWindow.removeEmbeddedDisplayContent(this); + } mParentWindow = win; mParentWindow.addEmbeddedDisplayContent(this); mParentSurfaceControl = sc; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 31897057e076..48609e17ba40 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3164,6 +3164,11 @@ class Task extends WindowContainer<WindowContainer> { } @Override + public SurfaceControl.Builder makeAnimationLeash() { + return super.makeAnimationLeash().setMetadata(METADATA_TASK_ID, mTaskId); + } + + @Override public SurfaceControl getAnimationLeashParent() { if (WindowManagerService.sHierarchicalAnimations) { return super.getAnimationLeashParent(); diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java index df0fa9cc3272..6e9428ee6976 100644 --- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java +++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java @@ -61,6 +61,7 @@ class TaskChangeNotificationController { private static final int NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG = 26; private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 27; private static final int NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG = 28; + private static final int NOTIFY_ACTIVITY_ROTATED_MSG = 29; // Delay in notifying task stack change listeners (in millis) private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100; @@ -183,6 +184,10 @@ class TaskChangeNotificationController { l.onTaskRequestedOrientationChanged(m.arg1, m.arg2); }; + private final TaskStackConsumer mNotifyOnActivityRotation = (l, m) -> { + l.onActivityRotation(); + }; + @FunctionalInterface public interface TaskStackConsumer { void accept(ITaskStackListener t, Message m) throws RemoteException; @@ -277,6 +282,9 @@ class TaskChangeNotificationController { case NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG: forAllRemoteListeners(mNotifyTaskRequestedOrientationChanged, msg); break; + case NOTIFY_ACTIVITY_ROTATED_MSG: + forAllRemoteListeners(mNotifyOnActivityRotation, msg); + break; } if (msg.obj instanceof SomeArgs) { ((SomeArgs) msg.obj).recycle(); @@ -574,4 +582,11 @@ class TaskChangeNotificationController { forAllLocalListeners(mNotifyTaskRequestedOrientationChanged, msg); msg.sendToTarget(); } + + /** @see android.app.ITaskStackListener#onActivityRotation() */ + void notifyOnActivityRotation() { + final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_ROTATED_MSG); + forAllLocalListeners(mNotifyOnActivityRotation, msg); + msg.sendToTarget(); + } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index d1cb2105246a..aee5a1d7838b 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -106,6 +106,8 @@ class WallpaperController { private static final int WALLPAPER_DRAW_TIMEOUT = 2; private int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL; + private boolean mShouldUpdateZoom; + /** * Temporary storage for taking a screenshot of the wallpaper. * @see #screenshotWallpaperLocked() @@ -400,6 +402,7 @@ class WallpaperController { void setWallpaperZoomOut(WindowState window, float zoom) { if (Float.compare(window.mWallpaperZoomOut, zoom) != 0) { window.mWallpaperZoomOut = zoom; + mShouldUpdateZoom = true; updateWallpaperOffsetLocked(window, false); } } @@ -623,9 +626,7 @@ class WallpaperController { mLastWallpaperX = mWallpaperTarget.mWallpaperX; mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep; } - if (mWallpaperTarget.mWallpaperZoomOut >= 0) { - mLastWallpaperZoomOut = mWallpaperTarget.mWallpaperZoomOut; - } + computeLastWallpaperZoomOut(); if (mWallpaperTarget.mWallpaperY >= 0) { mLastWallpaperY = mWallpaperTarget.mWallpaperY; mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep; @@ -804,8 +805,11 @@ class WallpaperController { * we'll have conflicts and break the "depth system" mental model. */ private void computeLastWallpaperZoomOut() { - mLastWallpaperZoomOut = 0; - mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true); + if (mShouldUpdateZoom) { + mLastWallpaperZoomOut = 0; + mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true); + mShouldUpdateZoom = false; + } } private float zoomOutToScale(float zoom) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f34510e2104a..8934e8f5c2e0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3848,6 +3848,11 @@ public class WindowManagerService extends IWindowManager.Stub final boolean rotationChanged = displayContent.updateRotationUnchecked(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + if (rotationChanged) { + mAtmService.getTaskChangeNotificationController() + .notifyOnActivityRotation(); + } + if (!rotationChanged || forceRelayout) { displayContent.setLayoutNeeded(); layoutNeeded = true; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 045089082fd8..36232e13fcf1 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5790,10 +5790,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // be invoked and we need to invoke it ourself. if (mLocalSyncId >= 0) { mBLASTSyncEngine.setReady(mLocalSyncId); - } else { - mWaitingListener.onTransactionReady(mWaitingSyncId, mBLASTSyncTransaction); + return mWinAnimator.finishDrawingLocked(null); } + mWaitingListener.onTransactionReady(mWaitingSyncId, mBLASTSyncTransaction); mUsingBLASTSyncTransaction = false; mWaitingSyncId = 0; diff --git a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java index 71e79b331cae..56727e81bda5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoHelperTest.java @@ -16,34 +16,23 @@ package com.android.server.location; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.initMocks; -import android.app.ActivityManager; -import android.content.BroadcastReceiver; +import android.app.ActivityManagerInternal; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.UserInfo; -import android.os.Handler; -import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.server.LocalServices; import com.android.server.location.UserInfoHelper.UserListener; import org.junit.After; @@ -51,16 +40,18 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.quality.Strictness; - -import java.util.ArrayList; -import java.util.List; @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class UserInfoHelperTest { + private static class TestUserInfoHelper extends UserInfoHelper { + TestUserInfoHelper(Context context) { + super(context); + } + } + private static final int USER1_ID = 1; private static final int USER1_MANAGED_ID = 11; private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID}; @@ -70,69 +61,30 @@ public class UserInfoHelperTest { @Mock private Context mContext; @Mock private UserManager mUserManager; + @Mock private ActivityManagerInternal mActivityManagerInternal; - private StaticMockitoSession mMockingSession; - private List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>(); - - private UserInfoHelper mHelper; + private TestUserInfoHelper mHelper; @Before public void setUp() { - mMockingSession = mockitoSession() - .initMocks(this) - .spyStatic(ActivityManager.class) - .strictness(Strictness.WARN) - .startMocking(); + initMocks(this); + LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInternal); doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); - doAnswer(invocation -> { - mBroadcastReceivers.add(invocation.getArgument(0)); - return null; - }).when(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), any( - UserHandle.class), any(IntentFilter.class), isNull(), any(Handler.class)); - doReturn(USER1_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER1_ID); - doReturn(USER2_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER2_ID); - doReturn(new UserInfo(USER1_ID, "", 0)).when(mUserManager).getProfileParent( - USER1_MANAGED_ID); - doReturn(new UserInfo(USER2_ID, "", 0)).when(mUserManager).getProfileParent( - USER2_MANAGED_ID); - - doReturn(USER1_ID).when(ActivityManager::getCurrentUser); - - mHelper = new UserInfoHelper(mContext); + + doReturn(USER1_PROFILES).when(mUserManager).getEnabledProfileIds(USER1_ID); + doReturn(USER2_PROFILES).when(mUserManager).getEnabledProfileIds(USER2_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER1_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER1_MANAGED_ID); + doReturn(USER1_PROFILES).when(mActivityManagerInternal).getCurrentProfileIds(); + + mHelper = new TestUserInfoHelper(mContext); mHelper.onSystemReady(); } @After public void tearDown() { - if (mMockingSession != null) { - mMockingSession.finishMocking(); - } - } - - private void switchUser(int userId) { - doReturn(userId).when(ActivityManager::getCurrentUser); - Intent intent = new Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, - userId); - for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) { - broadcastReceiver.onReceive(mContext, intent); - } - } - - private void startUser(int userId) { - Intent intent = new Intent(Intent.ACTION_USER_STARTED).putExtra(Intent.EXTRA_USER_HANDLE, - userId); - for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) { - broadcastReceiver.onReceive(mContext, intent); - } - } - - private void stopUser(int userId) { - Intent intent = new Intent(Intent.ACTION_USER_STOPPED).putExtra(Intent.EXTRA_USER_HANDLE, - userId); - for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) { - broadcastReceiver.onReceive(mContext, intent); - } + LocalServices.removeServiceForTest(ActivityManagerInternal.class); } @Test @@ -140,16 +92,21 @@ public class UserInfoHelperTest { UserListener listener = mock(UserListener.class); mHelper.addListener(listener); - switchUser(USER1_ID); - verify(listener, never()).onUserChanged(anyInt(), anyInt()); - - switchUser(USER2_ID); - verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.USER_SWITCHED); - verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.USER_SWITCHED); - - switchUser(USER1_ID); - verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.USER_SWITCHED); - verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.USER_SWITCHED); + mHelper.dispatchOnCurrentUserChanged(USER1_ID, USER2_ID); + verify(listener, times(1)).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED); + verify(listener, times(1)).onUserChanged(USER1_MANAGED_ID, + UserListener.CURRENT_USER_CHANGED); + verify(listener, times(1)).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED); + verify(listener, times(1)).onUserChanged(USER2_MANAGED_ID, + UserListener.CURRENT_USER_CHANGED); + + mHelper.dispatchOnCurrentUserChanged(USER2_ID, USER1_ID); + verify(listener, times(2)).onUserChanged(USER2_ID, UserListener.CURRENT_USER_CHANGED); + verify(listener, times(2)).onUserChanged(USER2_MANAGED_ID, + UserListener.CURRENT_USER_CHANGED); + verify(listener, times(2)).onUserChanged(USER1_ID, UserListener.CURRENT_USER_CHANGED); + verify(listener, times(2)).onUserChanged(USER1_MANAGED_ID, + UserListener.CURRENT_USER_CHANGED); } @Test @@ -157,11 +114,11 @@ public class UserInfoHelperTest { UserListener listener = mock(UserListener.class); mHelper.addListener(listener); - startUser(USER1_ID); + mHelper.dispatchOnUserStarted(USER1_ID); verify(listener).onUserChanged(USER1_ID, UserListener.USER_STARTED); - startUser(USER2_ID); - verify(listener).onUserChanged(USER2_ID, UserListener.USER_STARTED); + mHelper.dispatchOnUserStarted(USER1_MANAGED_ID); + verify(listener).onUserChanged(USER1_MANAGED_ID, UserListener.USER_STARTED); } @Test @@ -169,24 +126,22 @@ public class UserInfoHelperTest { UserListener listener = mock(UserListener.class); mHelper.addListener(listener); - stopUser(USER1_ID); - verify(listener).onUserChanged(USER1_ID, UserListener.USER_STOPPED); - - stopUser(USER2_ID); + mHelper.dispatchOnUserStopped(USER2_ID); verify(listener).onUserChanged(USER2_ID, UserListener.USER_STOPPED); + + mHelper.dispatchOnUserStopped(USER2_MANAGED_ID); + verify(listener).onUserChanged(USER2_MANAGED_ID, UserListener.USER_STOPPED); } @Test public void testCurrentUserIds() { assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER1_PROFILES); - switchUser(USER2_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_MANAGED_ID); + doReturn(USER2_PROFILES).when(mActivityManagerInternal).getCurrentProfileIds(); assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER2_PROFILES); - - switchUser(USER1_ID); - - assertThat(mHelper.getCurrentUserIds()).isEqualTo(USER1_PROFILES); } @Test @@ -196,7 +151,11 @@ public class UserInfoHelperTest { assertThat(mHelper.isCurrentUserId(USER2_ID)).isFalse(); assertThat(mHelper.isCurrentUserId(USER2_MANAGED_ID)).isFalse(); - switchUser(USER2_ID); + doReturn(false).when(mActivityManagerInternal).isCurrentProfile(USER1_ID); + doReturn(false).when(mActivityManagerInternal).isCurrentProfile(USER1_MANAGED_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_ID); + doReturn(true).when(mActivityManagerInternal).isCurrentProfile(USER2_MANAGED_ID); + doReturn(USER2_PROFILES).when(mActivityManagerInternal).getCurrentProfileIds(); assertThat(mHelper.isCurrentUserId(USER1_ID)).isFalse(); assertThat(mHelper.isCurrentUserId(USER2_ID)).isTrue(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 7af7a23b1ef6..c34b8e19a41d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -33,6 +33,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPortInfo; +import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener; import android.os.IPowerManager; import android.os.IThermalService; import android.os.Looper; @@ -261,4 +262,89 @@ public class HdmiControlServiceTest { mHdmiControlService.setHdmiCecVolumeControlEnabled(true); assertThat(mHdmiControlService.isHdmiCecVolumeControlEnabled()).isTrue(); } + + @Test + public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_enabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); + + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mTestLooper.dispatchAll(); + + assertThat(callback.mCallbackReceived).isTrue(); + assertThat(callback.mVolumeControlEnabled).isTrue(); + } + + @Test + public void addHdmiCecVolumeControlFeatureListener_emitsCurrentState_disabled() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); + + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mTestLooper.dispatchAll(); + + assertThat(callback.mCallbackReceived).isTrue(); + assertThat(callback.mVolumeControlEnabled).isFalse(); + } + + @Test + public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); + + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mTestLooper.dispatchAll(); + + assertThat(callback.mCallbackReceived).isTrue(); + assertThat(callback.mVolumeControlEnabled).isTrue(); + } + + @Test + public void addHdmiCecVolumeControlFeatureListener_honorsUnregistration() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + VolumeControlFeatureCallback callback = new VolumeControlFeatureCallback(); + + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback); + mTestLooper.dispatchAll(); + + mHdmiControlService.removeHdmiControlVolumeControlStatusChangeListener(callback); + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mTestLooper.dispatchAll(); + + assertThat(callback.mCallbackReceived).isTrue(); + assertThat(callback.mVolumeControlEnabled).isFalse(); + } + + @Test + public void addHdmiCecVolumeControlFeatureListener_notifiesStateUpdate_multiple() { + mHdmiControlService.setHdmiCecVolumeControlEnabled(false); + VolumeControlFeatureCallback callback1 = new VolumeControlFeatureCallback(); + VolumeControlFeatureCallback callback2 = new VolumeControlFeatureCallback(); + + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback1); + mHdmiControlService.addHdmiCecVolumeControlFeatureListener(callback2); + + + mHdmiControlService.setHdmiCecVolumeControlEnabled(true); + mTestLooper.dispatchAll(); + + assertThat(callback1.mCallbackReceived).isTrue(); + assertThat(callback2.mCallbackReceived).isTrue(); + assertThat(callback1.mVolumeControlEnabled).isTrue(); + assertThat(callback2.mVolumeControlEnabled).isTrue(); + } + + private static class VolumeControlFeatureCallback extends + IHdmiCecVolumeControlFeatureListener.Stub { + boolean mCallbackReceived = false; + boolean mVolumeControlEnabled = false; + + @Override + public void onHdmiCecVolumeControlFeature(boolean enabled) throws RemoteException { + this.mCallbackReceived = true; + this.mVolumeControlEnabled = enabled; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt index 5412bb5106ff..74b4d122cbc0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt @@ -18,8 +18,8 @@ package com.android.server.pm.parsing import android.content.pm.PackageManager import android.platform.test.annotations.Presubmit +import androidx.test.filters.LargeTest import com.google.common.truth.Expect -import com.google.common.truth.Truth.assertWithMessage import org.junit.Rule import org.junit.Test @@ -52,6 +52,7 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { } } + @LargeTest @Test fun packageInfoEquality() { val flags = PackageManager.GET_ACTIVITIES or @@ -65,7 +66,9 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { PackageManager.GET_SERVICES or PackageManager.GET_SHARED_LIBRARY_FILES or PackageManager.GET_SIGNATURES or - PackageManager.GET_SIGNING_CERTIFICATES + PackageManager.GET_SIGNING_CERTIFICATES or + PackageManager.MATCH_DIRECT_BOOT_UNAWARE or + PackageManager.MATCH_DIRECT_BOOT_AWARE val oldPackageInfo = oldPackages.asSequence().map { oldPackageInfo(it, flags) } val newPackageInfo = newPackages.asSequence().map { newPackageInfo(it, flags) } @@ -77,11 +80,79 @@ class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { } else { "$firstName | $secondName" } - expect.withMessage("${it.first?.applicationInfo?.sourceDir} $packageName") - .that(it.first?.dumpToString()) - .isEqualTo(it.second?.dumpToString()) + + // Main components are asserted independently to separate the failures. Otherwise the + // comparison would include every component in one massive string. + + val prefix = "${it.first?.applicationInfo?.sourceDir} $packageName" + + expect.withMessage("$prefix PackageInfo") + .that(it.second?.dumpToString()) + .isEqualTo(it.first?.dumpToString()) + + expect.withMessage("$prefix ApplicationInfo") + .that(it.second?.applicationInfo?.dumpToString()) + .isEqualTo(it.first?.applicationInfo?.dumpToString()) + + val firstActivityNames = it.first?.activities?.map { it.name } ?: emptyList() + val secondActivityNames = it.second?.activities?.map { it.name } ?: emptyList() + expect.withMessage("$prefix activities") + .that(secondActivityNames) + .containsExactlyElementsIn(firstActivityNames) + .inOrder() + + if (!it.first?.activities.isNullOrEmpty() && !it.second?.activities.isNullOrEmpty()) { + it.first?.activities?.zip(it.second?.activities!!)?.forEach { + expect.withMessage("$prefix ${it.first.name}") + .that(it.second.dumpToString()) + .isEqualTo(it.first.dumpToString()) + } + } + + val firstReceiverNames = it.first?.receivers?.map { it.name } ?: emptyList() + val secondReceiverNames = it.second?.receivers?.map { it.name } ?: emptyList() + expect.withMessage("$prefix receivers") + .that(secondReceiverNames) + .containsExactlyElementsIn(firstReceiverNames) + .inOrder() + + if (!it.first?.receivers.isNullOrEmpty() && !it.second?.receivers.isNullOrEmpty()) { + it.first?.receivers?.zip(it.second?.receivers!!)?.forEach { + expect.withMessage("$prefix ${it.first.name}") + .that(it.second.dumpToString()) + .isEqualTo(it.first.dumpToString()) + } + } + + val firstProviderNames = it.first?.providers?.map { it.name } ?: emptyList() + val secondProviderNames = it.second?.providers?.map { it.name } ?: emptyList() + expect.withMessage("$prefix providers") + .that(secondProviderNames) + .containsExactlyElementsIn(firstProviderNames) + .inOrder() + + if (!it.first?.providers.isNullOrEmpty() && !it.second?.providers.isNullOrEmpty()) { + it.first?.providers?.zip(it.second?.providers!!)?.forEach { + expect.withMessage("$prefix ${it.first.name}") + .that(it.second.dumpToString()) + .isEqualTo(it.first.dumpToString()) + } + } + + val firstServiceNames = it.first?.services?.map { it.name } ?: emptyList() + val secondServiceNames = it.second?.services?.map { it.name } ?: emptyList() + expect.withMessage("$prefix services") + .that(secondServiceNames) + .containsExactlyElementsIn(firstServiceNames) + .inOrder() + + if (!it.first?.services.isNullOrEmpty() && !it.second?.services.isNullOrEmpty()) { + it.first?.services?.zip(it.second?.services!!)?.forEach { + expect.withMessage("$prefix ${it.first.name}") + .that(it.second.dumpToString()) + .isEqualTo(it.first.dumpToString()) + } + } } } } - - diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt index 0f028f05d514..420ff19aab74 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt @@ -19,6 +19,7 @@ package com.android.server.pm.parsing import android.content.Context import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo +import android.content.pm.ComponentInfo import android.content.pm.ConfigurationInfo import android.content.pm.FeatureInfo import android.content.pm.InstrumentationInfo @@ -27,6 +28,8 @@ import android.content.pm.PackageParser import android.content.pm.PackageUserState import android.content.pm.PermissionInfo import android.content.pm.ProviderInfo +import android.content.pm.ServiceInfo +import android.os.Bundle import android.os.Debug import android.os.Environment import android.util.SparseArray @@ -38,8 +41,10 @@ import com.android.server.pm.pkg.PackageStateUnserialized import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import org.junit.BeforeClass -import org.mockito.Mockito +import org.mockito.Mockito.any +import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyString import org.mockito.Mockito.mock import java.io.File @@ -47,7 +52,7 @@ open class AndroidPackageParsingTestBase { companion object { - private const val VERIFY_ALL_APKS = false + private const val VERIFY_ALL_APKS = true /** For auditing memory usage differences */ private const val DUMP_HPROF_TO_EXTERNAL = false @@ -81,10 +86,14 @@ open class AndroidPackageParsingTestBase { .filter { file -> file.name.endsWith(".apk") } .toList() } + .distinct() private val dummyUserState = mock(PackageUserState::class.java).apply { installed = true - Mockito.`when`(isAvailable(anyInt())).thenReturn(true) + whenever(isAvailable(anyInt())) { true } + whenever(isMatch(any<ComponentInfo>(), anyInt())) { true } + whenever(isMatch(anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), + anyString(), anyInt())) { true } } lateinit var oldPackages: List<PackageParser.Package> @@ -145,6 +154,7 @@ open class AndroidPackageParsingTestBase { private fun mockPkgSetting(aPkg: AndroidPackage) = mockThrowOnUnmocked<PackageSetting> { this.pkg = aPkg whenever(pkgState) { PackageStateUnserialized() } + whenever(readUserState(anyInt())) { dummyUserState } } } @@ -156,19 +166,10 @@ open class AndroidPackageParsingTestBase { // The following methods prepend "this." because @hide APIs can cause an IDE to auto-import // the R.attr constant instead of referencing the field in an attempt to fix the error. - /** - * Known exclusions: - * - [ApplicationInfo.credentialProtectedDataDir] - * - [ApplicationInfo.dataDir] - * - [ApplicationInfo.deviceProtectedDataDir] - * - [ApplicationInfo.processName] - * - [ApplicationInfo.publicSourceDir] - * - [ApplicationInfo.scanPublicSourceDir] - * - [ApplicationInfo.scanSourceDir] - * - [ApplicationInfo.sourceDir] - * These attributes used to be assigned post-package-parsing as part of another component, - * but are now adjusted directly inside [PackageImpl]. - */ + // It's difficult to comment out a line in a triple quoted string, so this is used instead + // to ignore specific fields. A comment is required to explain why a field was ignored. + private fun Any?.ignored(comment: String): String = "IGNORED" + protected fun ApplicationInfo.dumpToString() = """ appComponentFactory=${this.appComponentFactory} backupAgentName=${this.backupAgentName} @@ -179,22 +180,31 @@ open class AndroidPackageParsingTestBase { compatibleWidthLimitDp=${this.compatibleWidthLimitDp} compileSdkVersion=${this.compileSdkVersion} compileSdkVersionCodename=${this.compileSdkVersionCodename} + credentialProtectedDataDir=${this.credentialProtectedDataDir + .ignored("Deferred pre-R, but assigned immediately in R")} + crossProfile=${this.crossProfile.ignored("Added in R")} + dataDir=${this.dataDir.ignored("Deferred pre-R, but assigned immediately in R")} descriptionRes=${this.descriptionRes} + deviceProtectedDataDir=${this.deviceProtectedDataDir + .ignored("Deferred pre-R, but assigned immediately in R")} enabled=${this.enabled} enabledSetting=${this.enabledSetting} flags=${Integer.toBinaryString(this.flags)} fullBackupContent=${this.fullBackupContent} + gwpAsanMode=${this.gwpAsanMode.ignored("Added in R")} hiddenUntilInstalled=${this.hiddenUntilInstalled} icon=${this.icon} iconRes=${this.iconRes} installLocation=${this.installLocation} + labelRes=${this.labelRes} largestWidthLimitDp=${this.largestWidthLimitDp} logo=${this.logo} longVersionCode=${this.longVersionCode} + ${"".ignored("mHiddenApiPolicy is a private field")} manageSpaceActivityName=${this.manageSpaceActivityName} - maxAspectRatio.compareTo(that.maxAspectRatio)=${this.maxAspectRatio} - metaData=${this.metaData} - minAspectRatio.compareTo(that.minAspectRatio)=${this.minAspectRatio} + maxAspectRatio=${this.maxAspectRatio} + metaData=${this.metaData.dumpToString()} + minAspectRatio=${this.minAspectRatio} minSdkVersion=${this.minSdkVersion} name=${this.name} nativeLibraryDir=${this.nativeLibraryDir} @@ -206,18 +216,27 @@ open class AndroidPackageParsingTestBase { permission=${this.permission} primaryCpuAbi=${this.primaryCpuAbi} privateFlags=${Integer.toBinaryString(this.privateFlags)} + processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} + publicSourceDir=${this.publicSourceDir + .ignored("Deferred pre-R, but assigned immediately in R")} requiresSmallestWidthDp=${this.requiresSmallestWidthDp} resourceDirs=${this.resourceDirs?.contentToString()} roundIconRes=${this.roundIconRes} - secondaryCpuAbi=${this.secondaryCpuAbi} - secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} + scanPublicSourceDir=${this.scanPublicSourceDir + .ignored("Deferred pre-R, but assigned immediately in R")} + scanSourceDir=${this.scanSourceDir + .ignored("Deferred pre-R, but assigned immediately in R")} seInfo=${this.seInfo} seInfoUser=${this.seInfoUser} + secondaryCpuAbi=${this.secondaryCpuAbi} + secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} sharedLibraryFiles=${this.sharedLibraryFiles?.contentToString()} sharedLibraryInfos=${this.sharedLibraryInfos} showUserIcon=${this.showUserIcon} + sourceDir=${this.sourceDir + .ignored("Deferred pre-R, but assigned immediately in R")} splitClassLoaderNames=${this.splitClassLoaderNames?.contentToString()} - splitDependencies=${this.splitDependencies} + splitDependencies=${this.splitDependencies.dumpToString()} splitNames=${this.splitNames?.contentToString()} splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} splitSourceDirs=${this.splitSourceDirs?.contentToString()} @@ -226,8 +245,8 @@ open class AndroidPackageParsingTestBase { targetSdkVersion=${this.targetSdkVersion} taskAffinity=${this.taskAffinity} theme=${this.theme} - uid=${this.uid} uiOptions=${this.uiOptions} + uid=${this.uid} versionCode=${this.versionCode} volumeUuid=${this.volumeUuid} zygotePreloadName=${this.zygotePreloadName} @@ -241,19 +260,27 @@ open class AndroidPackageParsingTestBase { """.trimIndent() protected fun InstrumentationInfo.dumpToString() = """ + banner=${this.banner} credentialProtectedDataDir=${this.credentialProtectedDataDir} dataDir=${this.dataDir} deviceProtectedDataDir=${this.deviceProtectedDataDir} functionalTest=${this.functionalTest} handleProfiling=${this.handleProfiling} + icon=${this.icon} + labelRes=${this.labelRes} + logo=${this.logo} + metaData=${this.metaData} + name=${this.name} nativeLibraryDir=${this.nativeLibraryDir} + nonLocalizedLabel=${this.nonLocalizedLabel} + packageName=${this.packageName} primaryCpuAbi=${this.primaryCpuAbi} publicSourceDir=${this.publicSourceDir} secondaryCpuAbi=${this.secondaryCpuAbi} secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} + showUserIcon=${this.showUserIcon} sourceDir=${this.sourceDir} - splitDependencies=${this.splitDependencies.sequence() - .map { it.first to it.second?.contentToString() }.joinToString()} + splitDependencies=${this.splitDependencies.dumpToString()} splitNames=${this.splitNames?.contentToString()} splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} splitSourceDirs=${this.splitSourceDirs?.contentToString()} @@ -262,25 +289,40 @@ open class AndroidPackageParsingTestBase { """.trimIndent() protected fun ActivityInfo.dumpToString() = """ + banner=${this.banner} colorMode=${this.colorMode} configChanges=${this.configChanges} + descriptionRes=${this.descriptionRes} + directBootAware=${this.directBootAware} documentLaunchMode=${this.documentLaunchMode} + enabled=${this.enabled} + exported=${this.exported} flags=${Integer.toBinaryString(this.flags)} + icon=${this.icon} + labelRes=${this.labelRes} launchMode=${this.launchMode} launchToken=${this.launchToken} lockTaskLaunchMode=${this.lockTaskLaunchMode} + logo=${this.logo} maxAspectRatio=${this.maxAspectRatio} maxRecents=${this.maxRecents} + metaData=${this.metaData.dumpToString()} minAspectRatio=${this.minAspectRatio} + name=${this.name} + nonLocalizedLabel=${this.nonLocalizedLabel} + packageName=${this.packageName} parentActivityName=${this.parentActivityName} permission=${this.permission} - persistableMode=${this.persistableMode} - privateFlags=${Integer.toBinaryString(this.privateFlags)} + persistableMode=${this.persistableMode.ignored("Could be dropped pre-R, fixed in R")} + privateFlags=${this.privateFlags} + processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} requestedVrComponent=${this.requestedVrComponent} resizeMode=${this.resizeMode} rotationAnimation=${this.rotationAnimation} screenOrientation=${this.screenOrientation} + showUserIcon=${this.showUserIcon} softInputMode=${this.softInputMode} + splitName=${this.splitName} targetActivity=${this.targetActivity} taskAffinity=${this.taskAffinity} theme=${this.theme} @@ -300,30 +342,77 @@ open class AndroidPackageParsingTestBase { protected fun PermissionInfo.dumpToString() = """ backgroundPermission=${this.backgroundPermission} + banner=${this.banner} descriptionRes=${this.descriptionRes} flags=${Integer.toBinaryString(this.flags)} group=${this.group} + icon=${this.icon} + labelRes=${this.labelRes} + logo=${this.logo} + metaData=${this.metaData.dumpToString()} + name=${this.name} nonLocalizedDescription=${this.nonLocalizedDescription} + nonLocalizedLabel=${this.nonLocalizedLabel} + packageName=${this.packageName} protectionLevel=${this.protectionLevel} requestRes=${this.requestRes} + showUserIcon=${this.showUserIcon} """.trimIndent() protected fun ProviderInfo.dumpToString() = """ + applicationInfo=${this.applicationInfo.ignored("Already checked")} authority=${this.authority} + banner=${this.banner} + descriptionRes=${this.descriptionRes} + directBootAware=${this.directBootAware} + enabled=${this.enabled} + exported=${this.exported} flags=${Integer.toBinaryString(this.flags)} forceUriPermissions=${this.forceUriPermissions} grantUriPermissions=${this.grantUriPermissions} + icon=${this.icon} initOrder=${this.initOrder} isSyncable=${this.isSyncable} + labelRes=${this.labelRes} + logo=${this.logo} + metaData=${this.metaData.dumpToString()} multiprocess=${this.multiprocess} + name=${this.name} + nonLocalizedLabel=${this.nonLocalizedLabel} + packageName=${this.packageName} pathPermissions=${this.pathPermissions?.joinToString { "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}" }} + processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} readPermission=${this.readPermission} + showUserIcon=${this.showUserIcon} + splitName=${this.splitName} uriPermissionPatterns=${this.uriPermissionPatterns?.contentToString()} writePermission=${this.writePermission} """.trimIndent() + protected fun ServiceInfo.dumpToString() = """ + applicationInfo=${this.applicationInfo.ignored("Already checked")} + banner=${this.banner} + descriptionRes=${this.descriptionRes} + directBootAware=${this.directBootAware} + enabled=${this.enabled} + exported=${this.exported} + flags=${Integer.toBinaryString(this.flags)} + icon=${this.icon} + labelRes=${this.labelRes} + logo=${this.logo} + mForegroundServiceType"${this.mForegroundServiceType} + metaData=${this.metaData.dumpToString()} + name=${this.name} + nonLocalizedLabel=${this.nonLocalizedLabel} + packageName=${this.packageName} + permission=${this.permission} + processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} + showUserIcon=${this.showUserIcon} + splitName=${this.splitName} + """.trimIndent() + protected fun ConfigurationInfo.dumpToString() = """ reqGlEsVersion=${this.reqGlEsVersion} reqInputFeatures=${this.reqInputFeatures} @@ -333,8 +422,10 @@ open class AndroidPackageParsingTestBase { """.trimIndent() protected fun PackageInfo.dumpToString() = """ - activities=${this.activities?.joinToString { it.dumpToString() }} - applicationInfo=${this.applicationInfo.dumpToString()} + activities=${this.activities?.joinToString { it.dumpToString() } + .ignored("Checked separately in test")} + applicationInfo=${this.applicationInfo.dumpToString() + .ignored("Checked separately in test")} baseRevisionCode=${this.baseRevisionCode} compileSdkVersion=${this.compileSdkVersion} compileSdkVersionCodename=${this.compileSdkVersionCodename} @@ -356,15 +447,18 @@ open class AndroidPackageParsingTestBase { overlayTarget=${this.overlayTarget} packageName=${this.packageName} permissions=${this.permissions?.joinToString { it.dumpToString() }} - providers=${this.providers?.joinToString { it.dumpToString() }} - receivers=${this.receivers?.joinToString { it.dumpToString() }} + providers=${this.providers?.joinToString { it.dumpToString() } + .ignored("Checked separately in test")} + receivers=${this.receivers?.joinToString { it.dumpToString() } + .ignored("Checked separately in test")} reqFeatures=${this.reqFeatures?.joinToString { it.dumpToString() }} requestedPermissions=${this.requestedPermissions?.contentToString()} requestedPermissionsFlags=${this.requestedPermissionsFlags?.contentToString()} requiredAccountType=${this.requiredAccountType} requiredForAllUsers=${this.requiredForAllUsers} restrictedAccountType=${this.restrictedAccountType} - services=${this.services?.contentToString()} + services=${this.services?.joinToString { it.dumpToString() } + .ignored("Checked separately in test")} sharedUserId=${this.sharedUserId} sharedUserLabel=${this.sharedUserLabel} signatures=${this.signatures?.joinToString { it.toCharsString() }} @@ -378,11 +472,17 @@ open class AndroidPackageParsingTestBase { versionName=${this.versionName} """.trimIndent() - @Suppress("unused") - private fun <T> SparseArray<T>.sequence(): Sequence<Pair<Int, T>> { - var index = 0 - return generateSequence { - index++.takeIf { it < size() }?.let { keyAt(it) to valueAt(index) } + private fun Bundle?.dumpToString() = this?.keySet()?.associateWith { get(it) }?.toString() + + private fun <T> SparseArray<T>?.dumpToString(): String { + if (this == null) { + return "EMPTY" + } + + val list = mutableListOf<Pair<Int, T>>() + for (index in (0 until size())) { + list += keyAt(index) to valueAt(index) } + return list.toString() } } diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java index e86399e1a631..62b6a65cc6cb 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java @@ -60,14 +60,12 @@ import java.util.Set; public class UriGrantsManagerServiceTest { private UriGrantsMockContext mContext; - private UriGrantsManagerService mService; - private UriGrantsManagerInternal mLocalService; + private UriGrantsManagerInternal mService; @Before public void setUp() throws Exception { mContext = new UriGrantsMockContext(InstrumentationRegistry.getContext()); - mService = UriGrantsManagerService.createForTest(mContext.getFilesDir()); - mLocalService = mService.getLocalService(); + mService = UriGrantsManagerService.createForTest(mContext.getFilesDir()).getLocalService(); } /** @@ -80,8 +78,7 @@ public class UriGrantsManagerServiceTest { final GrantUri expectedGrant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ); final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, - USER_PRIMARY); + intent, UID_PRIMARY_CAMERA, PKG_SOCIAL, USER_PRIMARY); assertEquals(PKG_SOCIAL, needed.targetPkg); assertEquals(UID_PRIMARY_SOCIAL, needed.targetUid); assertEquals(FLAG_READ, needed.flags); @@ -98,8 +95,7 @@ public class UriGrantsManagerServiceTest { final GrantUri expectedGrant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ); final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, - USER_SECONDARY); + intent, UID_PRIMARY_CAMERA, PKG_SOCIAL, USER_SECONDARY); assertEquals(PKG_SOCIAL, needed.targetPkg); assertEquals(UID_SECONDARY_SOCIAL, needed.targetUid); assertEquals(FLAG_READ, needed.flags); @@ -113,8 +109,7 @@ public class UriGrantsManagerServiceTest { public void testNeeded_public() { final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PUBLIC).addFlags(FLAG_READ); final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_PUBLIC, PKG_SOCIAL, intent, intent.getFlags(), null, - USER_PRIMARY); + intent, UID_PRIMARY_PUBLIC, PKG_SOCIAL, USER_PRIMARY); assertNull(needed); } @@ -128,7 +123,7 @@ public class UriGrantsManagerServiceTest { final GrantUri expectedGrant = new GrantUri(USER_PRIMARY, URI_PUBLIC, FLAG_READ); final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_PUBLIC, PKG_SOCIAL, intent, intent.getFlags(), null, USER_SECONDARY); + intent, UID_PRIMARY_PUBLIC, PKG_SOCIAL, USER_SECONDARY); assertEquals(PKG_SOCIAL, needed.targetPkg); assertEquals(UID_SECONDARY_SOCIAL, needed.targetUid); assertEquals(FLAG_READ, needed.flags); @@ -143,7 +138,7 @@ public class UriGrantsManagerServiceTest { final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PRIVATE).addFlags(FLAG_READ); try { mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_PRIVATE, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY); + intent, UID_PRIMARY_PRIVATE, PKG_SOCIAL, USER_PRIMARY); fail(); } catch (SecurityException expected) { } @@ -158,7 +153,7 @@ public class UriGrantsManagerServiceTest { final Intent intent = new Intent(Intent.ACTION_VIEW, URI_FORCE) .addFlags(FLAG_READ); final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_FORCE, PKG_FORCE, intent, intent.getFlags(), null, USER_PRIMARY); + intent, UID_PRIMARY_FORCE, PKG_FORCE, USER_PRIMARY); assertEquals(asSet(new GrantUri(USER_PRIMARY, URI_FORCE, 0)), needed.uris); } @@ -172,15 +167,15 @@ public class UriGrantsManagerServiceTest { { final Intent intent = new Intent(Intent.ACTION_VIEW, uri) .addFlags(FLAG_READ | Intent.FLAG_ACTIVITY_CLEAR_TASK); - assertNull(mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_COMPLEX, PKG_SOCIAL, - intent, intent.getFlags(), null, USER_PRIMARY)); + assertNull(mService.checkGrantUriPermissionFromIntent( + intent, UID_PRIMARY_COMPLEX, PKG_SOCIAL, USER_PRIMARY)); } { final Intent intent = new Intent(Intent.ACTION_VIEW, uri) .addFlags(FLAG_READ | FLAG_PREFIX); try { - mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_COMPLEX, PKG_SOCIAL, - intent, intent.getFlags(), null, USER_PRIMARY); + mService.checkGrantUriPermissionFromIntent( + intent, UID_PRIMARY_COMPLEX, PKG_SOCIAL, USER_PRIMARY); fail(); } catch (SecurityException expected) { } @@ -189,8 +184,8 @@ public class UriGrantsManagerServiceTest { final Intent intent = new Intent(Intent.ACTION_VIEW, uri) .addFlags(FLAG_READ | FLAG_PERSISTABLE); try { - mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_COMPLEX, PKG_SOCIAL, - intent, intent.getFlags(), null, USER_PRIMARY); + mService.checkGrantUriPermissionFromIntent( + intent, UID_PRIMARY_COMPLEX, PKG_SOCIAL, USER_PRIMARY); fail(); } catch (SecurityException expected) { } @@ -209,8 +204,7 @@ public class UriGrantsManagerServiceTest { final Intent intent = new Intent(Intent.ACTION_VIEW, uri) .addFlags(FLAG_READ); final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, - USER_SECONDARY); + intent, UID_PRIMARY_COMPLEX, PKG_SOCIAL, USER_SECONDARY); assertEquals(FLAG_READ, needed.flags); } { @@ -218,8 +212,7 @@ public class UriGrantsManagerServiceTest { .addFlags(FLAG_READ | FLAG_PREFIX); try { mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, - USER_SECONDARY); + intent, UID_PRIMARY_COMPLEX, PKG_SOCIAL, USER_SECONDARY); fail(); } catch (SecurityException expected) { } @@ -229,8 +222,7 @@ public class UriGrantsManagerServiceTest { .addFlags(FLAG_READ | FLAG_PERSISTABLE); try { mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, - USER_SECONDARY); + intent, UID_PRIMARY_COMPLEX, PKG_SOCIAL, USER_SECONDARY); fail(); } catch (SecurityException expected) { } @@ -248,21 +240,21 @@ public class UriGrantsManagerServiceTest { final Intent intent = new Intent(Intent.ACTION_VIEW, uri) .addFlags(FLAG_READ); final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY); + intent, UID_PRIMARY_COMPLEX, PKG_SOCIAL, USER_PRIMARY); assertEquals(asSet(new GrantUri(USER_PRIMARY, uri, 0)), needed.uris); } { final Intent intent = new Intent(Intent.ACTION_VIEW, uri) .addFlags(FLAG_READ | FLAG_PREFIX); final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY); + intent, UID_PRIMARY_COMPLEX, PKG_SOCIAL, USER_PRIMARY); assertEquals(asSet(new GrantUri(USER_PRIMARY, uri, FLAG_PREFIX)), needed.uris); } { final Intent intent = new Intent(Intent.ACTION_VIEW, uri) .addFlags(FLAG_READ | FLAG_PERSISTABLE); final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_COMPLEX, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY); + intent, UID_PRIMARY_COMPLEX, PKG_SOCIAL, USER_PRIMARY); assertEquals(asSet(new GrantUri(USER_PRIMARY, uri, 0)), needed.uris); } } @@ -284,8 +276,8 @@ public class UriGrantsManagerServiceTest { // When granting towards primary, persistable can't be honored so // the entire grant fails try { - mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, - intent.getFlags(), null, USER_PRIMARY); + mService.checkGrantUriPermissionFromIntent( + intent, UID_PRIMARY_CAMERA, PKG_SOCIAL, USER_PRIMARY); fail(); } catch (SecurityException expected) { } @@ -294,8 +286,8 @@ public class UriGrantsManagerServiceTest { // When granting towards secondary, persistable can't be honored so // the entire grant fails try { - mService.checkGrantUriPermissionFromIntent(UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, - intent.getFlags(), null, USER_SECONDARY); + mService.checkGrantUriPermissionFromIntent( + intent, UID_PRIMARY_CAMERA, PKG_SOCIAL, USER_SECONDARY); fail(); } catch (SecurityException expected) { } @@ -310,18 +302,16 @@ public class UriGrantsManagerServiceTest { public void testGrant_overlap() { final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PHOTO_1).addFlags(FLAG_READ); - final UriPermissionOwner activity = new UriPermissionOwner(mLocalService, "activity"); - final UriPermissionOwner service = new UriPermissionOwner(mLocalService, "service"); + final UriPermissionOwner activity = new UriPermissionOwner(mService, "activity"); + final UriPermissionOwner service = new UriPermissionOwner(mService, "service"); final GrantUri expectedGrant = new GrantUri(USER_PRIMARY, URI_PHOTO_1, FLAG_READ); // Grant read via activity and write via service mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY), - activity); + intent, UID_PRIMARY_CAMERA, PKG_SOCIAL, USER_PRIMARY), activity); mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY), - service); + intent, UID_PRIMARY_CAMERA, PKG_SOCIAL, USER_PRIMARY), service); // Verify that everything is good with the world assertTrue(mService.checkUriPermission(expectedGrant, UID_PRIMARY_SOCIAL, FLAG_READ)); @@ -338,7 +328,7 @@ public class UriGrantsManagerServiceTest { @Test public void testCheckAuthorityGrants() { final Intent intent = new Intent(Intent.ACTION_VIEW, URI_PHOTO_1).addFlags(FLAG_READ); - final UriPermissionOwner owner = new UriPermissionOwner(mLocalService, "primary"); + final UriPermissionOwner owner = new UriPermissionOwner(mService, "primary"); final ProviderInfo cameraInfo = mContext.mPmInternal.resolveContentProvider( PKG_CAMERA, 0, USER_PRIMARY); @@ -355,8 +345,7 @@ public class UriGrantsManagerServiceTest { // Granting primary camera to primary social mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent( - UID_PRIMARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY), - owner); + intent, UID_PRIMARY_CAMERA, PKG_SOCIAL, USER_PRIMARY), owner); assertTrue(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL, cameraInfo, USER_PRIMARY, true)); assertFalse(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL, @@ -368,8 +357,7 @@ public class UriGrantsManagerServiceTest { // Granting secondary camera to primary social mService.grantUriPermissionUncheckedFromIntent(mService.checkGrantUriPermissionFromIntent( - UID_SECONDARY_CAMERA, PKG_SOCIAL, intent, intent.getFlags(), null, USER_PRIMARY), - owner); + intent, UID_SECONDARY_CAMERA, PKG_SOCIAL, USER_PRIMARY), owner); assertTrue(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL, cameraInfo, USER_PRIMARY, true)); assertTrue(mService.checkAuthorityGrants(UID_PRIMARY_SOCIAL, diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java index 752707e5a5dc..d366efe0d979 100644 --- a/telephony/java/android/telephony/SmsCbMessage.java +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -594,6 +594,7 @@ public final class SmsCbMessage implements Parcelable { SmsCbEtwsInfo etwsInfo = getEtwsWarningInfo(); if (etwsInfo != null) { cv.put(CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType()); + cv.put(CellBroadcasts.ETWS_IS_PRIMARY, etwsInfo.isPrimary()); } SmsCbCmasInfo cmasInfo = getCmasWarningInfo(); @@ -667,9 +668,12 @@ public final class SmsCbMessage implements Parcelable { SmsCbEtwsInfo etwsInfo; int etwsWarningTypeColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_WARNING_TYPE); - if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) { + int etwsIsPrimaryColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_IS_PRIMARY); + if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn) + && etwsIsPrimaryColumn != -1 && !cursor.isNull(etwsIsPrimaryColumn)) { int warningType = cursor.getInt(etwsWarningTypeColumn); - etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null); + boolean isPrimary = cursor.getInt(etwsIsPrimaryColumn) != 0; + etwsInfo = new SmsCbEtwsInfo(warningType, false, false, isPrimary, null); } else { etwsInfo = null; } diff --git a/wifi/Android.bp b/wifi/Android.bp index 83b35616286a..9c5b7b66f2a3 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -135,7 +135,6 @@ java_sdk_library { permitted_packages: [ "android.hardware.wifi", "android.net.wifi", - "android.x.net.wifi", // Created by jarjar rules. "com.android.wifi.x", ], diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 70c5e72e4e0c..b841921355e9 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -632,7 +632,6 @@ public class WifiInfo implements Parcelable { /** * @hide - * TODO: makes real freq boundaries */ public boolean is24GHz() { return ScanResult.is24GHz(mFrequency); @@ -640,7 +639,6 @@ public class WifiInfo implements Parcelable { /** * @hide - * TODO: makes real freq boundaries */ @UnsupportedAppUsage public boolean is5GHz() { @@ -648,6 +646,13 @@ public class WifiInfo implements Parcelable { } /** + * @hide + */ + public boolean is6GHz() { + return ScanResult.is6GHz(mFrequency); + } + + /** * Record the MAC address of the WLAN interface * @param macAddress the MAC address in {@code XX:XX:XX:XX:XX:XX} form * @hide |