diff options
107 files changed, 3843 insertions, 2429 deletions
diff --git a/Android.bp b/Android.bp index f6d49e9f7fc7..15befaef84ae 100644 --- a/Android.bp +++ b/Android.bp @@ -739,6 +739,7 @@ java_defaults { "android.hardware.vibrator-V1.0-java", "android.hardware.vibrator-V1.1-java", "android.hardware.vibrator-V1.2-java", + "android.hardware.vibrator-V1.3-java", "android.hardware.wifi-V1.0-java-constants", "android.hardware.radio-V1.0-java", "android.hardware.radio-V1.3-java", diff --git a/api/current.txt b/api/current.txt index 7e0c33cfb657..72dd139fe39c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -42662,6 +42662,7 @@ package android.telecom { method public static java.lang.String capabilitiesToString(int); method public android.telecom.PhoneAccountHandle getAccountHandle(); method public int getCallCapabilities(); + method public int getCallDirection(); method public android.telecom.CallIdentification getCallIdentification(); method public int getCallProperties(); method public java.lang.String getCallerDisplayName(); @@ -42698,6 +42699,9 @@ package android.telecom { field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000 field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2 field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 + field public static final int DIRECTION_INCOMING = 0; // 0x0 + field public static final int DIRECTION_OUTGOING = 1; // 0x1 + field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff field public static final int PROPERTY_CONFERENCE = 1; // 0x1 field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4 field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20 diff --git a/api/system-current.txt b/api/system-current.txt index cb1e96a2bc82..891e76480640 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -537,6 +537,13 @@ package android.app { method public void onVrStateChanged(boolean); } + public final class WallpaperColors implements android.os.Parcelable { + ctor public WallpaperColors(android.graphics.Color, android.graphics.Color, android.graphics.Color, int); + method public int getColorHints(); + field public static final int HINT_SUPPORTS_DARK_TEXT = 1; // 0x1 + field public static final int HINT_SUPPORTS_DARK_THEME = 2; // 0x2 + } + public final class WallpaperInfo implements android.os.Parcelable { method public boolean supportsAmbientMode(); } @@ -2947,7 +2954,7 @@ package android.location { method public void setLocationControllerExtraPackage(java.lang.String); method public void setLocationControllerExtraPackageEnabled(boolean); method public void setLocationEnabledForUser(boolean, android.os.UserHandle); - method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle); + method public deprecated boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle); method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback); } diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index e0e1b7202347..ed22df52b87d 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -490,7 +490,6 @@ Landroid/location/ILocationManager$Stub;->asInterface(Landroid/os/IBinder;)Landr Landroid/location/ILocationManager$Stub;->TRANSACTION_getAllProviders:I Landroid/location/ILocationManager;->getAllProviders()Ljava/util/List; Landroid/location/ILocationManager;->getNetworkProviderPackage()Ljava/lang/String; -Landroid/location/ILocationManager;->reportLocation(Landroid/location/Location;Z)V Landroid/location/INetInitiatedListener$Stub;-><init>()V Landroid/location/INetInitiatedListener;->sendNiResponse(II)Z Landroid/location/LocationManager$ListenerTransport;-><init>(Landroid/location/LocationManager;Landroid/location/LocationListener;Landroid/os/Looper;)V diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java index ace814ab4fe0..38a98d3c8807 100644 --- a/core/java/android/app/WallpaperColors.java +++ b/core/java/android/app/WallpaperColors.java @@ -18,7 +18,7 @@ package android.app; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.annotation.SystemApi; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -56,6 +56,7 @@ public final class WallpaperColors implements Parcelable { * eg. A launcher may set its text color to black if this flag is specified. * @hide */ + @SystemApi public static final int HINT_SUPPORTS_DARK_TEXT = 1 << 0; /** @@ -64,6 +65,7 @@ public final class WallpaperColors implements Parcelable { * eg. A launcher may set its drawer color to black if this flag is specified. * @hide */ + @SystemApi public static final int HINT_SUPPORTS_DARK_THEME = 1 << 1; /** @@ -234,7 +236,7 @@ public final class WallpaperColors implements Parcelable { * @see WallpaperColors#fromDrawable(Drawable) * @hide */ - @UnsupportedAppUsage + @SystemApi public WallpaperColors(@NonNull Color primaryColor, @Nullable Color secondaryColor, @Nullable Color tertiaryColor, int colorHints) { @@ -349,7 +351,7 @@ public final class WallpaperColors implements Parcelable { * @return True if dark text is supported. * @hide */ - @UnsupportedAppUsage + @SystemApi public int getColorHints() { return mColorHints; } diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 2990b577e09d..e0a15a5a7f89 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -19,6 +19,7 @@ package android.app; import static android.app.ActivityThread.isSystem; import static android.app.WindowConfigurationProto.ACTIVITY_TYPE; import static android.app.WindowConfigurationProto.APP_BOUNDS; +import static android.app.WindowConfigurationProto.BOUNDS; import static android.app.WindowConfigurationProto.WINDOWING_MODE; import static android.view.Surface.rotationToString; @@ -585,6 +586,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu } protoOutputStream.write(WINDOWING_MODE, mWindowingMode); protoOutputStream.write(ACTIVITY_TYPE, mActivityType); + if (mBounds != null) { + mBounds.writeToProto(protoOutputStream, BOUNDS); + } protoOutputStream.end(token); } @@ -606,6 +610,10 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu mAppBounds = new Rect(); mAppBounds.readFromProto(proto, APP_BOUNDS); break; + case (int) BOUNDS: + mBounds = new Rect(); + mBounds.readFromProto(proto, BOUNDS); + break; case (int) WINDOWING_MODE: mWindowingMode = proto.readInt(WINDOWING_MODE); break; diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java index 2d630a61a1c3..d73e73f442d6 100644 --- a/core/java/android/app/role/RoleManager.java +++ b/core/java/android/app/role/RoleManager.java @@ -67,6 +67,56 @@ public final class RoleManager { private static final String LOG_TAG = RoleManager.class.getSimpleName(); /** + * The name of the proxy calling role. + * <p> + * A proxy calling app implements the {@link android.telecom.CallRedirectionService} API and + * provides a means to re-write the phone number for an outgoing call to place the call through + * a proxy calling service. + * <p> + * A single app may fill this role at any one time. + * @hide + */ + public static final String ROLE_PROXY_CALLING_APP = "android.app.role.PROXY_CALLING_APP"; + + /** + * The name of the call screening and caller id role. + * <p> + * A call screening and caller id app implements the + * {@link android.telecom.CallScreeningService} API. + * <p> + * A single app may fill this role at any one time. + * @hide + */ + public static final String ROLE_CALL_SCREENING_APP = "android.app.role.CALL_SCREENING_APP"; + + /** + * The name of the call companion app role. + * <p> + * A call companion app provides no user interface for calls, but will be bound to by Telecom + * when there are active calls on the device. Companion apps for wearable devices are an + * acceptable use-case. A call companion app implements the + * {@link android.telecom.InCallService} API. + * <p> + * Multiple apps app may fill this role at any one time. + * @hide + */ + public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP"; + + /** + * The name of the car mode dialer app role. + * <p> + * Similar to the {@link #ROLE_DIALER} role, this role determines which app is responsible for + * showing the user interface for ongoing calls on the device. This app filling this role is + * only used when the device is in car mode (see + * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information). An app + * filling this role must implement the {@link android.telecom.InCallService} API. + * <p> + * A single app may fill this role at any one time. + * @hide + */ + public static final String ROLE_CAR_MODE_DIALER_APP = "android.app.role.CAR_MODE_DIALER_APP"; + + /** * The name of the dialer role. */ public static final String ROLE_DIALER = "android.app.role.DIALER"; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 2b266b730485..c9846510be4f 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1621,7 +1621,7 @@ public class PackageParser { } final AttributeSet attrs = parser; - return parseApkLite(apkPath, parser, attrs, signingDetails); + return parseApkLite(apkPath, parser, attrs, signingDetails, flags); } catch (XmlPullParserException | IOException | RuntimeException e) { Slog.w(TAG, "Failed to parse " + apkPath, e); @@ -1708,7 +1708,7 @@ public class PackageParser { } private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs, - SigningDetails signingDetails) + SigningDetails signingDetails, int flags) throws IOException, XmlPullParserException, PackageParserException { final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs); @@ -1716,11 +1716,12 @@ public class PackageParser { int versionCode = 0; int versionCodeMajor = 0; int revisionCode = 0; + int targetSdkVersion = 0; boolean coreApp = false; boolean debuggable = false; boolean multiArch = false; boolean use32bitAbi = false; - boolean extractNativeLibs = true; + Boolean extractNativeLibsProvided = null; boolean isolatedSplits = false; boolean isFeatureSplit = false; boolean isSplitRequired = false; @@ -1785,7 +1786,8 @@ public class PackageParser { use32bitAbi = attrs.getAttributeBooleanValue(i, false); } if ("extractNativeLibs".equals(attr)) { - extractNativeLibs = attrs.getAttributeBooleanValue(i, true); + extractNativeLibsProvided = Boolean.valueOf( + attrs.getAttributeBooleanValue(i, true)); } if ("preferCodeIntegrity".equals(attr)) { preferCodeIntegrity = attrs.getAttributeBooleanValue(i, false); @@ -1803,9 +1805,51 @@ public class PackageParser { PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "<uses-split> tag requires 'android:name' attribute"); } + } else if (TAG_USES_SDK.equals(parser.getName())) { + final String[] errorMsg = new String[1]; + Pair<Integer, Integer> versions = deriveSdkVersions(new AbstractVersionsAccessor() { + @Override public String getMinSdkVersionCode() { + return getAttributeAsString("minSdkVersion"); + } + + @Override public int getMinSdkVersion() { + return getAttributeAsInt("minSdkVersion"); + } + + @Override public String getTargetSdkVersionCode() { + return getAttributeAsString("targetSdkVersion"); + } + + @Override public int getTargetSdkVersion() { + return getAttributeAsInt("targetSdkVersion"); + } + + private String getAttributeAsString(String name) { + return attrs.getAttributeValue(ANDROID_RESOURCES, name); + } + + private int getAttributeAsInt(String name) { + try { + return attrs.getAttributeIntValue(ANDROID_RESOURCES, name, -1); + } catch (NumberFormatException e) { + return -1; + } + } + }, flags, errorMsg); + + if (versions == null) { + throw new PackageParserException( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, errorMsg[0]); + } + + targetSdkVersion = versions.second; } } + final boolean extractNativeLibsDefault = targetSdkVersion < Build.VERSION_CODES.Q; + final boolean extractNativeLibs = (extractNativeLibsProvided != null) + ? extractNativeLibsProvided : extractNativeLibsDefault; + if (preferCodeIntegrity && extractNativeLibs) { throw new PackageParserException( PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, @@ -2215,65 +2259,60 @@ public class PackageParser { } else if (tagName.equals(TAG_USES_SDK)) { if (SDK_VERSION > 0) { - sa = res.obtainAttributes(parser, - com.android.internal.R.styleable.AndroidManifestUsesSdk); - - int minVers = 1; - String minCode = null; - int targetVers = 0; - String targetCode = null; - - TypedValue val = sa.peekValue( - com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion); - if (val != null) { - if (val.type == TypedValue.TYPE_STRING && val.string != null) { - minCode = val.string.toString(); - } else { - // If it's not a string, it's an integer. - minVers = val.data; - } - } - - val = sa.peekValue( - com.android.internal.R.styleable.AndroidManifestUsesSdk_targetSdkVersion); - if (val != null) { - if (val.type == TypedValue.TYPE_STRING && val.string != null) { - targetCode = val.string.toString(); - if (minCode == null) { - minCode = targetCode; - } - } else { - // If it's not a string, it's an integer. - targetVers = val.data; - } - } else { - targetVers = minVers; - targetCode = minCode; - } - - sa.recycle(); - - final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode, - SDK_VERSION, SDK_CODENAMES, outError); - if (minSdkVersion < 0) { + sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk); + final TypedArray saFinal = sa; + Pair<Integer, Integer> versions = deriveSdkVersions( + new AbstractVersionsAccessor() { + @Override public String getMinSdkVersionCode() { + return getAttributeAsString( + R.styleable.AndroidManifestUsesSdk_minSdkVersion); + } + + @Override public int getMinSdkVersion() { + return getAttributeAsInt( + R.styleable.AndroidManifestUsesSdk_minSdkVersion); + } + + @Override public String getTargetSdkVersionCode() { + return getAttributeAsString( + R.styleable.AndroidManifestUsesSdk_targetSdkVersion); + } + + @Override public int getTargetSdkVersion() { + return getAttributeAsInt( + R.styleable.AndroidManifestUsesSdk_targetSdkVersion); + } + + private String getAttributeAsString(int index) { + TypedValue val = saFinal.peekValue(index); + if (val != null && val.type == TypedValue.TYPE_STRING + && val.string != null) { + return val.string.toString(); + } + return null; + } + + private int getAttributeAsInt(int index) { + TypedValue val = saFinal.peekValue(index); + if (val != null && val.type != TypedValue.TYPE_STRING) { + // If it's not a string, it's an integer. + return val.data; + } + return -1; + } + }, flags, outError); + + if (versions == null) { mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; return null; } - boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0; - final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers, - targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch); - if (targetSdkVersion < 0) { - mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; - return null; - } + pkg.applicationInfo.minSdkVersion = versions.first; + pkg.applicationInfo.targetSdkVersion = versions.second; - pkg.applicationInfo.minSdkVersion = minSdkVersion; - pkg.applicationInfo.targetSdkVersion = targetSdkVersion; + sa.recycle(); } - XmlUtils.skipCurrentTag(parser); - } else if (tagName.equals(TAG_SUPPORT_SCREENS)) { sa = res.obtainAttributes(parser, com.android.internal.R.styleable.AndroidManifestSupportsScreens); @@ -2675,6 +2714,67 @@ public class PackageParser { return -1; } + private interface AbstractVersionsAccessor { + /** Returns minimum SDK version code string, or null if absent. */ + String getMinSdkVersionCode(); + + /** Returns minimum SDK version code, or -1 if absent. */ + int getMinSdkVersion(); + + /** Returns target SDK version code string, or null if absent. */ + String getTargetSdkVersionCode(); + + /** Returns target SDK version code, or -1 if absent. */ + int getTargetSdkVersion(); + } + + private static @Nullable Pair<Integer, Integer> deriveSdkVersions( + @NonNull AbstractVersionsAccessor accessor, int flags, String[] outError) { + int minVers = 1; + String minCode = null; + int targetVers = 0; + String targetCode = null; + + String code = accessor.getMinSdkVersionCode(); + int version = accessor.getMinSdkVersion(); + // Check integer first since code is almost never a null string (e.g. "28"). + if (version >= 0) { + minVers = version; + } else if (code != null) { + minCode = code; + } + + code = accessor.getTargetSdkVersionCode(); + version = accessor.getTargetSdkVersion(); + // Check integer first since code is almost never a null string (e.g. "28"). + if (version >= 0) { + targetVers = version; + } else if (code != null) { + targetCode = code; + if (minCode == null) { + minCode = targetCode; + } + } else { + targetVers = minVers; + targetCode = minCode; + } + + final int minSdkVersion = computeMinSdkVersion(minVers, minCode, + SDK_VERSION, SDK_CODENAMES, outError); + if (minSdkVersion < 0) { + return null; + } + + boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0; + final int targetSdkVersion = computeTargetSdkVersion(targetVers, + targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch); + if (targetSdkVersion < 0) { + return null; + } + + return Pair.create(minSdkVersion, targetSdkVersion); + } + /** * Computes the minSdkVersion to use at runtime. If the package is not * compatible with this platform, populates {@code outError[0]} with an diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index b238d778f55a..f652f85c153b 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -73,6 +73,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ public static final String KEY_NEGATIVE_TEXT = "negative_text"; + /** + * @hide + */ + public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation"; /** * Error/help message will show for this amount of time. @@ -215,6 +219,30 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Optional: A hint to the system to require user confirmation after a biometric has been + * authenticated. For example, implicit modalities like Face and Iris authentication are + * passive, meaning they don't require an explicit user action to complete. When set to + * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt + * will require confirmation by default. + * + * A typical use case for not requiring confirmation would be for low-risk transactions, + * such as re-authenticating a recently authenticated application. A typical use case for + * requiring confirmation would be for authorizing a purchase. + * + * Note that this is a hint to the system. The system may choose to ignore the flag. For + * example, if the user disables implicit authentication in Settings, or if it does not + * apply to a modality (e.g. Fingerprint). When ignored, the system will default to + * requiring confirmation. + * + * @param requireConfirmation + * @hide + */ + public Builder setRequireConfirmation(boolean requireConfirmation) { + mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation); + return this; + } + + /** * Creates a {@link BiometricPrompt}. * @return a {@link BiometricPrompt} * @throws IllegalArgumentException if any of the required fields are not set. diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 980140675959..68d6d85775e4 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -16,6 +16,8 @@ package android.os; +import android.annotation.NonNull; + import com.android.internal.os.Zygote; import dalvik.annotation.optimization.FastNative; @@ -313,7 +315,7 @@ public final class Trace { * @param sectionName The name of the code section to appear in the trace. This may be at * most 127 Unicode code units long. */ - public static void beginSection(String sectionName) { + public static void beginSection(@NonNull String sectionName) { if (isTagEnabled(TRACE_TAG_APP)) { if (sectionName.length() > MAX_SECTION_NAME_LEN) { throw new IllegalArgumentException("sectionName is too long"); @@ -345,7 +347,7 @@ public final class Trace { * @param methodName The method name to appear in the trace. * @param cookie Unique identifier for distinguishing simultaneous events */ - public static void beginAsyncSection(String methodName, int cookie) { + public static void beginAsyncSection(@NonNull String methodName, int cookie) { asyncTraceBegin(TRACE_TAG_APP, methodName, cookie); } @@ -357,7 +359,7 @@ public final class Trace { * @param methodName The method name to appear in the trace. * @param cookie Unique identifier for distinguishing simultaneous events */ - public static void endAsyncSection(String methodName, int cookie) { + public static void endAsyncSection(@NonNull String methodName, int cookie) { asyncTraceEnd(TRACE_TAG_APP, methodName, cookie); } @@ -367,7 +369,7 @@ public final class Trace { * @param counterName The counter name to appear in the trace. * @param counterValue The counter value. */ - public static void setCounter(String counterName, long counterValue) { + public static void setCounter(@NonNull String counterName, long counterValue) { if (isTagEnabled(TRACE_TAG_APP)) { nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 16f9a43de28d..a9b76c3b4024 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5030,10 +5030,6 @@ public final class Settings { public static boolean putStringForUser(@NonNull ContentResolver resolver, @NonNull String name, @Nullable String value, @Nullable String tag, boolean makeDefault, @UserIdInt int userHandle) { - if (LOCATION_MODE.equals(name)) { - // Map LOCATION_MODE to underlying location provider storage API - return setLocationModeForUser(resolver, Integer.parseInt(value), userHandle); - } if (MOVED_TO_GLOBAL.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure" + " to android.provider.Settings.Global"); @@ -5186,10 +5182,6 @@ public final class Settings { /** @hide */ @UnsupportedAppUsage public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) { - if (LOCATION_MODE.equals(name)) { - // Map from to underlying location provider storage API to location mode - return getLocationModeForUser(cr, userHandle); - } String v = getStringForUser(cr, name, userHandle); try { return v != null ? Integer.parseInt(v) : def; @@ -5224,10 +5216,6 @@ public final class Settings { /** @hide */ public static int getIntForUser(ContentResolver cr, String name, int userHandle) throws SettingNotFoundException { - if (LOCATION_MODE.equals(name)) { - // Map from to underlying location provider storage API to location mode - return getLocationModeForUser(cr, userHandle); - } String v = getStringForUser(cr, name, userHandle); try { return Integer.parseInt(v); @@ -5810,9 +5798,8 @@ public final class Settings { * this value being present in settings.db or on ContentObserver notifications on the * corresponding Uri. * - * @deprecated use {@link #LOCATION_MODE} and - * {@link LocationManager#MODE_CHANGED_ACTION} (or - * {@link LocationManager#PROVIDERS_CHANGED_ACTION}) + * @deprecated Providers should not be controlled individually. See {@link #LOCATION_MODE} + * documentation for information on reading/writing location information. */ @Deprecated public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; @@ -5830,9 +5817,7 @@ public final class Settings { * notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION} * to receive changes in this value. * - * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To - * get the status of a location provider, use - * {@link LocationManager#isProviderEnabled(String)}. + * @deprecated To check location mode, use {@link LocationManager#isLocationEnabled()}. */ @Deprecated public static final String LOCATION_MODE = "location_mode"; @@ -5861,9 +5846,7 @@ public final class Settings { /** * Location access disabled. * - * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To - * get the status of a location provider, use - * {@link LocationManager#isProviderEnabled(String)}. + * @deprecated See {@link #LOCATION_MODE}. */ @Deprecated public static final int LOCATION_MODE_OFF = 0; @@ -5883,9 +5866,7 @@ public final class Settings { * with {@link android.location.Criteria#POWER_HIGH} may be downgraded to * {@link android.location.Criteria#POWER_MEDIUM}. * - * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To - * get the status of a location provider, use - * {@link LocationManager#isProviderEnabled(String)}. + * @deprecated See {@link #LOCATION_MODE}. */ @Deprecated public static final int LOCATION_MODE_BATTERY_SAVING = 2; @@ -5893,9 +5874,7 @@ public final class Settings { /** * Best-effort location computation allowed. * - * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To - * get the status of a location provider, use - * {@link LocationManager#isProviderEnabled(String)}. + * @deprecated See {@link #LOCATION_MODE}. */ @Deprecated public static final int LOCATION_MODE_HIGH_ACCURACY = 3; @@ -8778,84 +8757,6 @@ public final class Settings { userId); } } - - /** - * Thread-safe method for setting the location mode to one of - * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, - * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. - * Necessary because the mode is a composite of the underlying location provider - * settings. - * - * @param cr the content resolver to use - * @param mode such as {@link #LOCATION_MODE_HIGH_ACCURACY} - * @param userId the userId for which to change mode - * @return true if the value was set, false on database errors - * - * @throws IllegalArgumentException if mode is not one of the supported values - * - * @deprecated To enable/disable location, use - * {@link LocationManager#setLocationEnabledForUser(boolean, int)}. - * To enable/disable a specific location provider, use - * {@link LocationManager#setProviderEnabledForUser(String, boolean, int)}. - */ - @Deprecated - private static boolean setLocationModeForUser( - ContentResolver cr, int mode, int userId) { - synchronized (mLocationSettingsLock) { - boolean gps = false; - boolean network = false; - switch (mode) { - case LOCATION_MODE_OFF: - break; - case LOCATION_MODE_SENSORS_ONLY: - gps = true; - break; - case LOCATION_MODE_BATTERY_SAVING: - network = true; - break; - case LOCATION_MODE_HIGH_ACCURACY: - gps = true; - network = true; - break; - default: - throw new IllegalArgumentException("Invalid location mode: " + mode); - } - - boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser( - cr, LocationManager.NETWORK_PROVIDER, network, userId); - boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser( - cr, LocationManager.GPS_PROVIDER, gps, userId); - return gpsSuccess && nlpSuccess; - } - } - - /** - * Thread-safe method for reading the location mode, returns one of - * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, - * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. Necessary - * because the mode is a composite of the underlying location provider settings. - * - * @param cr the content resolver to use - * @param userId the userId for which to read the mode - * @return the location mode - */ - private static final int getLocationModeForUser(ContentResolver cr, int userId) { - synchronized (mLocationSettingsLock) { - boolean gpsEnabled = Settings.Secure.isLocationProviderEnabledForUser( - cr, LocationManager.GPS_PROVIDER, userId); - boolean networkEnabled = Settings.Secure.isLocationProviderEnabledForUser( - cr, LocationManager.NETWORK_PROVIDER, userId); - if (gpsEnabled && networkEnabled) { - return LOCATION_MODE_HIGH_ACCURACY; - } else if (gpsEnabled) { - return LOCATION_MODE_SENSORS_ONLY; - } else if (networkEnabled) { - return LOCATION_MODE_BATTERY_SAVING; - } else { - return LOCATION_MODE_OFF; - } - } - } } /** @@ -13835,6 +13736,7 @@ public final class Settings { * enabled (boolean) * requires_targeting_p (boolean) * max_squeeze_remeasure_attempts (int) + * edit_choices_before_sending (boolean) * </pre> * @see com.android.systemui.statusbar.policy.SmartReplyConstants * @hide diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 20febc2f21cd..afe467012307 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -837,8 +837,8 @@ public final class Magnifier { mContentWidth = width; mContentHeight = height; - mOffsetX = (int) (0.1f * width); - mOffsetY = (int) (0.1f * height); + mOffsetX = (int) (1.05f * elevation); + mOffsetY = (int) (1.05f * elevation); // Setup the surface we will use for drawing the content and shadow. mSurfaceWidth = mContentWidth + 2 * mOffsetX; mSurfaceHeight = mContentHeight + 2 * mOffsetY; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index af2c17b80280..51b8734fdcc1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -3520,7 +3520,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @android.view.RemotableViewMethod public void setTextSelectHandle(@DrawableRes int textSelectHandle) { - Preconditions.checkArgumentPositive(textSelectHandle, + Preconditions.checkArgument(textSelectHandle != 0, "The text select handle should be a valid drawable resource id."); setTextSelectHandle(mContext.getDrawable(textSelectHandle)); } @@ -3577,7 +3577,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @android.view.RemotableViewMethod public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) { - Preconditions.checkArgumentPositive(textSelectHandleLeft, + Preconditions.checkArgument(textSelectHandleLeft != 0, "The text select left handle should be a valid drawable resource id."); setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft)); } @@ -3634,7 +3634,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @android.view.RemotableViewMethod public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) { - Preconditions.checkArgumentPositive(textSelectHandleRight, + Preconditions.checkArgument(textSelectHandleRight != 0, "The text select right handle should be a valid drawable resource id."); setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight)); } @@ -3667,9 +3667,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see #setTextCursorDrawable(int) * @attr ref android.R.styleable#TextView_textCursorDrawable */ - public void setTextCursorDrawable(@NonNull Drawable textCursorDrawable) { - Preconditions.checkNotNull(textCursorDrawable, - "The cursor drawable should not be null."); + public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) { mCursorDrawable = textCursorDrawable; mCursorDrawableRes = 0; if (mEditor != null) { @@ -3687,8 +3685,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_textCursorDrawable */ public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) { - Preconditions.checkArgumentPositive(textCursorDrawable, - "The cursor drawable should be a valid drawable resource id."); setTextCursorDrawable(mContext.getDrawable(textCursorDrawable)); } diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java index 100c6ee6763b..adf7692adc56 100644 --- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java +++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java @@ -16,10 +16,10 @@ package com.android.internal.policy; -import com.android.internal.R; - import android.content.res.Resources; +import com.android.internal.R; + /** * Utility functions for screen decorations used by both window manager and System UI. */ @@ -31,15 +31,19 @@ public class ScreenDecorationsUtils { * scaling, this means that we don't have to reload them on config changes. */ public static float getWindowCornerRadius(Resources resources) { + if (!supportsRoundedCornersOnWindows(resources)) { + return 0f; + } + // Radius that should be used in case top or bottom aren't defined. float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius); float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top); - if (topRadius == 0) { + if (topRadius == 0f) { topRadius = defaultRadius; } float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom); - if (bottomRadius == 0) { + if (bottomRadius == 0f) { bottomRadius = defaultRadius; } @@ -47,4 +51,11 @@ public class ScreenDecorationsUtils { // completely cover the display. return Math.min(topRadius, bottomRadius); } + + /** + * If live rounded corners are supported on windows. + */ + public static boolean supportsRoundedCornersOnWindows(Resources resources) { + return resources.getBoolean(R.bool.config_supportsRoundedCornersOnWindows); + } } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 53b56f2e937a..6a28059d3fd0 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -153,13 +153,11 @@ oneway interface IStatusBar void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId); // Used to hide the dialog when a biometric is authenticated - void onBiometricAuthenticated(); + void onBiometricAuthenticated(boolean authenticated); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc void onBiometricHelp(String message); // Used to set a message - the dialog will dismiss after a certain amount of time void onBiometricError(String error); // Used to hide the biometric dialog when the AuthenticationClient is stopped void hideBiometricDialog(); - // Used to request the "try again" button for authentications which requireConfirmation=true - void showBiometricTryAgain(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 9087dd219d97..197e873a18bc 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -97,13 +97,11 @@ interface IStatusBarService void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId); // Used to hide the dialog when a biometric is authenticated - void onBiometricAuthenticated(); + void onBiometricAuthenticated(boolean authenticated); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc void onBiometricHelp(String message); // Used to set a message - the dialog will dismiss after a certain amount of time void onBiometricError(String error); // Used to hide the biometric dialog when the AuthenticationClient is stopped void hideBiometricDialog(); - // Used to request the "try again" button for authentications which requireConfirmation=true - void showBiometricTryAgain(); } diff --git a/core/proto/android/app/window_configuration.proto b/core/proto/android/app/window_configuration.proto index 2d1555298ffb..6cc1a40de873 100644 --- a/core/proto/android/app/window_configuration.proto +++ b/core/proto/android/app/window_configuration.proto @@ -29,4 +29,5 @@ message WindowConfigurationProto { optional .android.graphics.RectProto app_bounds = 1; optional int32 windowing_mode = 2; optional int32 activity_type = 3; + optional .android.graphics.RectProto bounds = 4; } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1c98c66b1f48..140225e62850 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3648,4 +3648,8 @@ set in AndroidManifest. {@see android.view.Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} --> <string name="config_secondaryHomeComponent" translatable="false">com.android.launcher3/com.android.launcher3.SecondaryDisplayLauncher</string> + + <!-- If device supports corner radius on windows. + This should be turned off on low-end devices to improve animation performance. --> + <bool name="config_supportsRoundedCornersOnWindows">true</bool> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 01fbf80f4e63..f8c9037d5ab0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3515,6 +3515,7 @@ <java-symbol type="dimen" name="rounded_corner_radius" /> <java-symbol type="dimen" name="rounded_corner_radius_top" /> <java-symbol type="dimen" name="rounded_corner_radius_bottom" /> + <java-symbol type="bool" name="config_supportsRoundedCornersOnWindows" /> <java-symbol type="string" name="config_defaultModuleMetadataProvider" /> diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 0a90f85cda0e..b9b1cdc6cbc9 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -89,10 +89,6 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mLocked.animationPending = false; - mLocked.displayWidth = -1; - mLocked.displayHeight = -1; - mLocked.displayOrientation = DISPLAY_ORIENTATION_0; - mLocked.presentation = PRESENTATION_POINTER; mLocked.presentationChanged = false; @@ -110,15 +106,6 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mLocked.lastFrameUpdatedTime = 0; mLocked.buttonState = 0; - - mPolicy->loadPointerIcon(&mLocked.pointerIcon); - - loadResources(); - - if (mLocked.pointerIcon.isValid()) { - mLocked.pointerIconChanged = true; - updatePointerLocked(); - } } PointerController::~PointerController() { @@ -144,23 +131,15 @@ bool PointerController::getBounds(float* outMinX, float* outMinY, bool PointerController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const { - if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) { + + if (!mLocked.viewport.isValid()) { return false; } - *outMinX = 0; - *outMinY = 0; - switch (mLocked.displayOrientation) { - case DISPLAY_ORIENTATION_90: - case DISPLAY_ORIENTATION_270: - *outMaxX = mLocked.displayHeight - 1; - *outMaxY = mLocked.displayWidth - 1; - break; - default: - *outMaxX = mLocked.displayWidth - 1; - *outMaxY = mLocked.displayHeight - 1; - break; - } + *outMinX = mLocked.viewport.logicalLeft; + *outMinY = mLocked.viewport.logicalTop; + *outMaxX = mLocked.viewport.logicalRight - 1; + *outMaxY = mLocked.viewport.logicalBottom - 1; return true; } @@ -231,6 +210,12 @@ void PointerController::getPosition(float* outX, float* outY) const { *outY = mLocked.pointerY; } +int32_t PointerController::getDisplayId() const { + AutoMutex _l(mLock); + + return mLocked.viewport.displayId; +} + void PointerController::fade(Transition transition) { AutoMutex _l(mLock); @@ -355,48 +340,56 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout void PointerController::reloadPointerResources() { AutoMutex _l(mLock); - loadResources(); + loadResourcesLocked(); + updatePointerLocked(); +} - if (mLocked.presentation == PRESENTATION_POINTER) { - mLocked.additionalMouseResources.clear(); - mLocked.animationResources.clear(); - mPolicy->loadPointerIcon(&mLocked.pointerIcon); - mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, - &mLocked.animationResources); - } +/** + * The viewport values for deviceHeight and deviceWidth have already been adjusted for rotation, + * so here we are getting the dimensions in the original, unrotated orientation (orientation 0). + */ +static void getNonRotatedSize(const DisplayViewport& viewport, int32_t& width, int32_t& height) { + width = viewport.deviceWidth; + height = viewport.deviceHeight; - mLocked.presentationChanged = true; - updatePointerLocked(); + if (viewport.orientation == DISPLAY_ORIENTATION_90 + || viewport.orientation == DISPLAY_ORIENTATION_270) { + std::swap(width, height); + } } -void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) { +void PointerController::setDisplayViewport(const DisplayViewport& viewport) { AutoMutex _l(mLock); - - // Adjust to use the display's unrotated coordinate frame. - if (orientation == DISPLAY_ORIENTATION_90 - || orientation == DISPLAY_ORIENTATION_270) { - int32_t temp = height; - height = width; - width = temp; + if (viewport == mLocked.viewport) { + return; } - if (mLocked.displayWidth != width || mLocked.displayHeight != height) { - mLocked.displayWidth = width; - mLocked.displayHeight = height; + const DisplayViewport oldViewport = mLocked.viewport; + mLocked.viewport = viewport; + + int32_t oldDisplayWidth, oldDisplayHeight; + getNonRotatedSize(oldViewport, oldDisplayWidth, oldDisplayHeight); + int32_t newDisplayWidth, newDisplayHeight; + getNonRotatedSize(viewport, newDisplayWidth, newDisplayHeight); + + // Reset cursor position to center if size or display changed. + if (oldViewport.displayId != viewport.displayId + || oldDisplayWidth != newDisplayWidth + || oldDisplayHeight != newDisplayHeight) { float minX, minY, maxX, maxY; if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { mLocked.pointerX = (minX + maxX) * 0.5f; mLocked.pointerY = (minY + maxY) * 0.5f; + // Reload icon resources for density may be changed. + loadResourcesLocked(); } else { mLocked.pointerX = 0; mLocked.pointerY = 0; } fadeOutAndReleaseAllSpotsLocked(); - } - - if (mLocked.displayOrientation != orientation) { + } else if (oldViewport.orientation != viewport.orientation) { // Apply offsets to convert from the pixel top-left corner position to the pixel center. // This creates an invariant frame of reference that we can easily rotate when // taking into account that the pointer may be located at fractional pixel offsets. @@ -405,37 +398,37 @@ void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_ float temp; // Undo the previous rotation. - switch (mLocked.displayOrientation) { + switch (oldViewport.orientation) { case DISPLAY_ORIENTATION_90: temp = x; - x = mLocked.displayWidth - y; + x = oldViewport.deviceHeight - y; y = temp; break; case DISPLAY_ORIENTATION_180: - x = mLocked.displayWidth - x; - y = mLocked.displayHeight - y; + x = oldViewport.deviceWidth - x; + y = oldViewport.deviceHeight - y; break; case DISPLAY_ORIENTATION_270: temp = x; x = y; - y = mLocked.displayHeight - temp; + y = oldViewport.deviceWidth - temp; break; } // Perform the new rotation. - switch (orientation) { + switch (viewport.orientation) { case DISPLAY_ORIENTATION_90: temp = x; x = y; - y = mLocked.displayWidth - temp; + y = viewport.deviceHeight - temp; break; case DISPLAY_ORIENTATION_180: - x = mLocked.displayWidth - x; - y = mLocked.displayHeight - y; + x = viewport.deviceWidth - x; + y = viewport.deviceHeight - y; break; case DISPLAY_ORIENTATION_270: temp = x; - x = mLocked.displayHeight - y; + x = viewport.deviceWidth - y; y = temp; break; } @@ -444,7 +437,6 @@ void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_ // and save the results. mLocked.pointerX = x - 0.5f; mLocked.pointerY = y - 0.5f; - mLocked.displayOrientation = orientation; } updatePointerLocked(); @@ -614,11 +606,16 @@ void PointerController::removeInactivityTimeoutLocked() { mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); } -void PointerController::updatePointerLocked() { +void PointerController::updatePointerLocked() REQUIRES(mLock) { + if (!mLocked.viewport.isValid()) { + return; + } + mSpriteController->openTransaction(); mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); + mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId); if (mLocked.pointerAlpha > 0) { mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha); @@ -729,8 +726,18 @@ void PointerController::fadeOutAndReleaseAllSpotsLocked() { } } -void PointerController::loadResources() { +void PointerController::loadResourcesLocked() REQUIRES(mLock) { mPolicy->loadPointerResources(&mResources); + + if (mLocked.presentation == PRESENTATION_POINTER) { + mLocked.additionalMouseResources.clear(); + mLocked.animationResources.clear(); + mPolicy->loadPointerIcon(&mLocked.pointerIcon); + mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, + &mLocked.animationResources); + } + + mLocked.pointerIconChanged = true; } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 7f4e5a59c9b6..a32cc42a3342 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -23,6 +23,7 @@ #include <vector> #include <ui/DisplayInfo.h> +#include <input/DisplayViewport.h> #include <input/Input.h> #include <PointerControllerInterface.h> #include <utils/BitSet.h> @@ -96,6 +97,7 @@ public: virtual int32_t getButtonState() const; virtual void setPosition(float x, float y); virtual void getPosition(float* outX, float* outY) const; + virtual int32_t getDisplayId() const; virtual void fade(Transition transition); virtual void unfade(Transition transition); @@ -106,7 +108,7 @@ public: void updatePointerIcon(int32_t iconId); void setCustomPointerIcon(const SpriteIcon& icon); - void setDisplayViewport(int32_t width, int32_t height, int32_t orientation); + void setDisplayViewport(const DisplayViewport& viewport); void setInactivityTimeout(InactivityTimeout inactivityTimeout); void reloadPointerResources(); @@ -156,9 +158,7 @@ private: size_t animationFrameIndex; nsecs_t lastFrameUpdatedTime; - int32_t displayWidth; - int32_t displayHeight; - int32_t displayOrientation; + DisplayViewport viewport; InactivityTimeout inactivityTimeout; @@ -182,7 +182,7 @@ private: Vector<Spot*> spots; Vector<sp<Sprite> > recycledSprites; - } mLocked; + } mLocked GUARDED_BY(mLock); bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; void setPositionLocked(float x, float y); @@ -207,7 +207,7 @@ private: void fadeOutAndReleaseSpotLocked(Spot* spot); void fadeOutAndReleaseAllSpotsLocked(); - void loadResources(); + void loadResourcesLocked(); }; } // namespace android diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index eb2bc98ec9e9..c1868d3a94d6 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -144,13 +144,16 @@ void SpriteController::doUpdateSprites() { } } - // Resize sprites if needed. + // Resize and/or reparent sprites if needed. SurfaceComposerClient::Transaction t; bool needApplyTransaction = false; for (size_t i = 0; i < numSprites; i++) { SpriteUpdate& update = updates.editItemAt(i); + if (update.state.surfaceControl == nullptr) { + continue; + } - if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) { + if (update.state.wantSurfaceVisible()) { int32_t desiredWidth = update.state.icon.bitmap.width(); int32_t desiredHeight = update.state.icon.bitmap.height(); if (update.state.surfaceWidth < desiredWidth @@ -170,6 +173,12 @@ void SpriteController::doUpdateSprites() { } } } + + // If surface is a new one, we have to set right layer stack. + if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) { + t.setLayerStack(update.state.surfaceControl, update.state.displayId); + needApplyTransaction = true; + } } if (needApplyTransaction) { t.apply(); @@ -236,7 +245,7 @@ void SpriteController::doUpdateSprites() { if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER - | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) { + | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID))))) { needApplyTransaction = true; if (wantSurfaceVisibleAndDrawn @@ -445,6 +454,15 @@ void SpriteController::SpriteImpl::setTransformationMatrix( } } +void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.displayId != displayId) { + mLocked.state.displayId = displayId; + invalidateLocked(DIRTY_DISPLAY_ID); + } +} + void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) { bool wasDirty = mLocked.state.dirty; mLocked.state.dirty |= dirty; diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 31e43e9b99e5..5b216f50d113 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -125,6 +125,9 @@ public: /* Sets the sprite transformation matrix. */ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0; + + /* Sets the id of the display where the sprite should be shown. */ + virtual void setDisplayId(int32_t displayId) = 0; }; /* @@ -170,6 +173,7 @@ private: DIRTY_LAYER = 1 << 4, DIRTY_VISIBILITY = 1 << 5, DIRTY_HOTSPOT = 1 << 6, + DIRTY_DISPLAY_ID = 1 << 7, }; /* Describes the state of a sprite. @@ -180,7 +184,7 @@ private: struct SpriteState { inline SpriteState() : dirty(0), visible(false), - positionX(0), positionY(0), layer(0), alpha(1.0f), + positionX(0), positionY(0), layer(0), alpha(1.0f), displayId(ADISPLAY_ID_DEFAULT), surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) { } @@ -193,6 +197,7 @@ private: int32_t layer; float alpha; SpriteTransformationMatrix transformationMatrix; + int32_t displayId; sp<SurfaceControl> surfaceControl; int32_t surfaceWidth; @@ -225,6 +230,7 @@ private: virtual void setLayer(int32_t layer); virtual void setAlpha(float alpha); virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix); + virtual void setDisplayId(int32_t displayId); inline const SpriteState& getStateLocked() const { return mLocked.state; diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index bdc84da57799..e795b8119760 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -89,7 +89,6 @@ interface ILocationManager List<String> getAllProviders(); List<String> getProviders(in Criteria criteria, boolean enabledOnly); String getBestProvider(in Criteria criteria, boolean enabledOnly); - boolean providerMeetsCriteria(String provider, in Criteria criteria); ProviderProperties getProviderProperties(String provider); String getNetworkProviderPackage(); void setLocationControllerExtraPackage(String packageName); @@ -98,9 +97,7 @@ interface ILocationManager boolean isLocationControllerExtraPackageEnabled(); boolean isProviderEnabledForUser(String provider, int userId); - boolean setProviderEnabledForUser(String provider, boolean enabled, int userId); boolean isLocationEnabledForUser(int userId); - void setLocationEnabledForUser(boolean enabled, int userId); void addTestProvider(String name, in ProviderProperties properties, String opPackageName); void removeTestProvider(String provider, String opPackageName); void setTestProviderLocation(String provider, in Location loc, String opPackageName); @@ -114,10 +111,6 @@ interface ILocationManager // --- internal --- - // --- deprecated --- - void reportLocation(in Location location, boolean passive); - void reportLocationBatch(in List<Location> locations); - // for reporting callback completion void locationCallbackFinished(ILocationListener listener); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index d96597bb3b69..59c6a0a21cd1 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -43,6 +43,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import com.android.internal.location.ProviderProperties; @@ -1282,11 +1283,13 @@ public class LocationManager { @SystemApi @RequiresPermission(WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) { - try { - mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, + enabled + ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY + : Settings.Secure.LOCATION_MODE_OFF, + userHandle.getIdentifier()); } /** @@ -1372,20 +1375,22 @@ public class LocationManager { * @return true if the value was set, false on database errors * * @throws IllegalArgumentException if provider is null + * @deprecated Do not manipulate providers individually, use + * {@link #setLocationEnabledForUser(boolean, UserHandle)} instead. * @hide */ + @Deprecated @SystemApi @RequiresPermission(WRITE_SECURE_SETTINGS) public boolean setProviderEnabledForUser( String provider, boolean enabled, UserHandle userHandle) { checkProvider(provider); - try { - return mService.setProviderEnabledForUser( - provider, enabled, userHandle.getIdentifier()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return Settings.Secure.setLocationProviderEnabledForUser( + mContext.getContentResolver(), + provider, + enabled, + userHandle.getIdentifier()); } /** @@ -1578,7 +1583,7 @@ public class LocationManager { */ @Deprecated public void clearTestProviderEnabled(String provider) { - setTestProviderEnabled(provider, true); + setTestProviderEnabled(provider, false); } /** diff --git a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml index d04155618781..0c6d57dd6183 100644 --- a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml +++ b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml @@ -18,7 +18,7 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="?android:attr/colorBackgroundFloating" /> - <corners android:radius="1dp" + <corners android:topLeftRadius="@dimen/biometric_dialog_corner_size" android:topRightRadius="@dimen/biometric_dialog_corner_size" android:bottomLeftRadius="@dimen/biometric_dialog_corner_size" diff --git a/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml new file mode 100644 index 000000000000..26bf981f9625 --- /dev/null +++ b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml @@ -0,0 +1,32 @@ +<?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 + --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item> + <shape android:shape="rectangle"> + <solid android:color="?android:attr/colorBackgroundFloating"/> + <corners + android:topLeftRadius="@dimen/corner_size" + android:topRightRadius="@dimen/corner_size"/> + </shape> + </item> + <item android:gravity="bottom"> + <shape> + <size android:height="1dp"/> + <solid android:color="?android:attr/textColorSecondary" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml index e52aa14abfa9..b9b1bb1f4330 100644 --- a/packages/SystemUI/res/layout/battery_percentage_view.xml +++ b/packages/SystemUI/res/layout/battery_percentage_view.xml @@ -22,7 +22,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:singleLine="true" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" + android:textAppearance="@style/TextAppearance.StatusBar.Clock" android:textColor="?android:attr/textColorPrimary" android:gravity="center_vertical|start" android:paddingStart="@dimen/battery_level_padding_start" diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml index b2307e71e3ce..1aeb52cdce69 100644 --- a/packages/SystemUI/res/layout/bubble_expanded_view.xml +++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml @@ -20,11 +20,23 @@ android:layout_width="match_parent" android:id="@+id/bubble_expanded_view"> - <!-- TODO: header --> - <View android:id="@+id/pointer_view" android:layout_width="@dimen/bubble_pointer_width" android:layout_height="@dimen/bubble_pointer_height" /> + + <TextView + android:id="@+id/bubble_content_header" + android:background="@drawable/bubble_expanded_header_bg" + android:textAppearance="@*android:style/TextAppearance.Material.Title" + android:textSize="18sp" + android:layout_width="match_parent" + android:layout_height="@dimen/bubble_expanded_header_height" + android:gravity="start|center_vertical" + android:singleLine="true" + android:paddingLeft="@dimen/bubble_expanded_header_horizontal_padding" + android:paddingRight="@dimen/bubble_expanded_header_horizontal_padding" + /> + </com.android.systemui.bubbles.BubbleExpandedViewContainer> diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml index 307b5389521a..5bcc1b3b49bf 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml @@ -39,7 +39,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/monitoring_title_device_owned" - style="@android:style/TextAppearance.Material.Title" + style="@style/TextAppearance.DeviceManagementDialog.Title" android:textColor="?android:attr/textColorPrimary" android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding" /> @@ -64,7 +64,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/monitoring_subtitle_ca_certificate" - style="@android:style/TextAppearance.Material.Title" + style="@style/TextAppearance.DeviceManagementDialog.Title" android:textColor="?android:attr/textColorPrimary" android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding" /> @@ -89,7 +89,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/monitoring_subtitle_network_logging" - style="@android:style/TextAppearance.Material.Title" + style="@style/TextAppearance.DeviceManagementDialog.Title" android:textColor="?android:attr/textColorPrimary" android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding" /> @@ -114,7 +114,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/monitoring_subtitle_vpn" - style="@android:style/TextAppearance.Material.Title" + style="@style/TextAppearance.DeviceManagementDialog.Title" android:textColor="?android:attr/textColorPrimary" android:paddingBottom="@dimen/qs_footer_dialog_subtitle_padding" /> 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 22b8d2ff4db0..4b65b6a013f8 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 @@ -24,6 +24,7 @@ android:clipToPadding="false" android:gravity="center" android:orientation="horizontal" + android:clickable="true" android:paddingStart="@dimen/status_bar_padding_start" android:paddingEnd="@dimen/status_bar_padding_end" > diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 889db66526b8..7cc552441602 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -449,6 +449,11 @@ better (narrower) line-break for a double-line smart reply button. --> <integer name="config_smart_replies_in_notifications_max_squeeze_remeasure_attempts">3</integer> + <!-- Smart replies in notifications: Whether by default tapping on a choice should let the user + edit the input before it is sent to the app. Developers can override this via + RemoteInput.Builder.setEditChoicesBeforeSending. --> + <bool name="config_smart_replies_in_notifications_edit_choices_before_sending">false</bool> + <!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents. Blank sends the user to the Chooser first. This name is in the ComponentName flattened format (package/class) --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 06df0e763498..10e5f74983a3 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -997,4 +997,8 @@ <dimen name="bubble_pointer_width">6dp</dimen> <!-- Extra padding around the dismiss target for bubbles --> <dimen name="bubble_dismiss_slop">16dp</dimen> + <!-- Height of the header within the expanded view. --> + <dimen name="bubble_expanded_header_height">48dp</dimen> + <!-- Left and right padding applied to the header. --> + <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 8a5a69b61a43..22a0b3642a35 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -125,7 +125,7 @@ <style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon"> <item name="android:textSize">@dimen/status_bar_clock_size</item> - <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> <item name="android:textColor">@color/status_bar_clock_color</item> </style> @@ -265,6 +265,10 @@ <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> </style> + <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"> + <item name="android:gravity">center</item> + </style> + <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">wrap_content</item> 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 f3bdbae97e42..078947ca0468 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 @@ -76,4 +76,10 @@ interface ISystemUiProxy { */ float getWindowCornerRadius() = 10; + /** + * If device supports live rounded corners on windows. + * This might be turned off for performance reasons + */ + boolean supportsRoundedCornersOnWindows() = 11; + } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 27d624ab0b58..1c8a672faf87 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -14,13 +14,13 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.plugins.ClockPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.policy.ExtensionController; +import com.android.systemui.statusbar.policy.ExtensionController.Extension; -import java.util.Objects; import java.util.TimeZone; +import java.util.function.Consumer; /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. @@ -47,43 +47,15 @@ public class KeyguardClockSwitch extends RelativeLayout { * or not to show it below the alternate clock. */ private View mKeyguardStatusArea; + /** + * Used to select between plugin or default implementations of ClockPlugin interface. + */ + private Extension<ClockPlugin> mClockExtension; + /** + * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads. + */ + private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin); - private final PluginListener<ClockPlugin> mClockPluginListener = - new PluginListener<ClockPlugin>() { - @Override - public void onPluginConnected(ClockPlugin plugin, Context pluginContext) { - disconnectPlugin(); - View smallClockView = plugin.getView(); - if (smallClockView != null) { - // For now, assume that the most recently connected plugin is the - // selected clock face. In the future, the user should be able to - // pick a clock face from the available plugins. - mSmallClockFrame.addView(smallClockView, -1, - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - initPluginParams(); - mClockView.setVisibility(View.GONE); - } - View bigClockView = plugin.getBigClockView(); - if (bigClockView != null && mBigClockContainer != null) { - mBigClockContainer.addView(bigClockView); - mBigClockContainer.setVisibility(View.VISIBLE); - } - if (!plugin.shouldShowStatusArea()) { - mKeyguardStatusArea.setVisibility(View.GONE); - } - mClockPlugin = plugin; - } - - @Override - public void onPluginDisconnected(ClockPlugin plugin) { - if (Objects.equals(plugin, mClockPlugin)) { - disconnectPlugin(); - mClockView.setVisibility(View.VISIBLE); - mKeyguardStatusArea.setVisibility(View.VISIBLE); - } - } - }; private final StatusBarStateController.StateListener mStateListener = new StatusBarStateController.StateListener() { @Override @@ -122,18 +94,61 @@ public class KeyguardClockSwitch extends RelativeLayout { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - Dependency.get(PluginManager.class).addPluginListener(mClockPluginListener, - ClockPlugin.class); + mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class) + .withPlugin(ClockPlugin.class) + .withCallback(mClockPluginConsumer) + .build(); Dependency.get(StatusBarStateController.class).addCallback(mStateListener); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - Dependency.get(PluginManager.class).removePluginListener(mClockPluginListener); + mClockExtension.destroy(); Dependency.get(StatusBarStateController.class).removeCallback(mStateListener); } + private void setClockPlugin(ClockPlugin plugin) { + // Disconnect from existing plugin. + if (mClockPlugin != null) { + View smallClockView = mClockPlugin.getView(); + if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) { + mSmallClockFrame.removeView(smallClockView); + } + if (mBigClockContainer != null) { + mBigClockContainer.removeAllViews(); + mBigClockContainer.setVisibility(View.GONE); + } + mClockPlugin = null; + } + if (plugin == null) { + mClockView.setVisibility(View.VISIBLE); + mKeyguardStatusArea.setVisibility(View.VISIBLE); + return; + } + // Attach small and big clock views to hierarchy. + View smallClockView = plugin.getView(); + if (smallClockView != null) { + mSmallClockFrame.addView(smallClockView, -1, + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + mClockView.setVisibility(View.GONE); + } + View bigClockView = plugin.getBigClockView(); + if (bigClockView != null && mBigClockContainer != null) { + mBigClockContainer.addView(bigClockView); + mBigClockContainer.setVisibility(View.VISIBLE); + } + // Hide default clock. + if (!plugin.shouldShowStatusArea()) { + mKeyguardStatusArea.setVisibility(View.GONE); + } + // Initialize plugin parameters. + mClockPlugin = plugin; + mClockPlugin.setStyle(getPaint().getStyle()); + mClockPlugin.setTextColor(getCurrentTextColor()); + } + /** * Set container for big clock face appearing behind NSSL and KeyguardStatusView. */ @@ -232,33 +247,9 @@ public class KeyguardClockSwitch extends RelativeLayout { } } - /** - * When plugin changes, set all kept parameters into newer plugin. - */ - private void initPluginParams() { - if (mClockPlugin != null) { - mClockPlugin.setStyle(getPaint().getStyle()); - mClockPlugin.setTextColor(getCurrentTextColor()); - } - } - - private void disconnectPlugin() { - if (mClockPlugin != null) { - View smallClockView = mClockPlugin.getView(); - if (smallClockView != null) { - mSmallClockFrame.removeView(smallClockView); - } - if (mBigClockContainer != null) { - mBigClockContainer.removeAllViews(); - mBigClockContainer.setVisibility(View.GONE); - } - mClockPlugin = null; - } - } - @VisibleForTesting (otherwise = VisibleForTesting.NONE) - PluginListener getClockPluginListener() { - return mClockPluginListener; + Consumer<ClockPlugin> getClockPluginConsumer() { + return mClockPluginConsumer; } @VisibleForTesting (otherwise = VisibleForTesting.NONE) diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java index 9f363f681b04..7e5b42653210 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java @@ -25,12 +25,20 @@ public class AppOpItem { private int mUid; private String mPackageName; private long mTimeStarted; + private String mState; public AppOpItem(int code, int uid, String packageName, long timeStarted) { this.mCode = code; this.mUid = uid; this.mPackageName = packageName; this.mTimeStarted = timeStarted; + mState = new StringBuilder() + .append("AppOpItem(") + .append("Op code=").append(code).append(", ") + .append("UID=").append(uid).append(", ") + .append("Package name=").append(packageName) + .append(")") + .toString(); } public int getCode() { @@ -48,4 +56,9 @@ public class AppOpItem { public long getTimeStarted() { return mTimeStarted; } + + @Override + public String toString() { + return mState; + } } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index e3bcb37474db..c013df385987 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -29,7 +29,10 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dumpable; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -47,7 +50,7 @@ import javax.inject.Singleton; @Singleton public class AppOpsControllerImpl implements AppOpsController, AppOpsManager.OnOpActiveChangedListener, - AppOpsManager.OnOpNotedListener { + AppOpsManager.OnOpNotedListener, Dumpable { private static final long NOTED_OP_TIME_DELAY_MS = 5000; private static final String TAG = "AppOpsControllerImpl"; @@ -271,6 +274,22 @@ public class AppOpsControllerImpl implements AppOpsController, } } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("AppOpsController state:"); + pw.println(" Active Items:"); + for (int i = 0; i < mActiveItems.size(); i++) { + final AppOpItem item = mActiveItems.get(i); + pw.print(" "); pw.println(item.toString()); + } + pw.println(" Noted Items:"); + for (int i = 0; i < mNotedItems.size(); i++) { + final AppOpItem item = mNotedItems.get(i); + pw.print(" "); pw.println(item.toString()); + } + + } + protected final class H extends Handler { H(Looper looper) { super(looper); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java index 3167b9e0a458..016b8fe93093 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java @@ -33,9 +33,6 @@ import com.android.internal.os.SomeArgs; import com.android.systemui.SystemUI; import com.android.systemui.statusbar.CommandQueue; -import java.util.HashMap; -import java.util.Map; - /** * Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g. * BiometricDialogView). @@ -52,10 +49,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private static final int MSG_BUTTON_NEGATIVE = 6; private static final int MSG_USER_CANCELED = 7; private static final int MSG_BUTTON_POSITIVE = 8; - private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9; - private static final int MSG_TRY_AGAIN_PRESSED = 10; + private static final int MSG_TRY_AGAIN_PRESSED = 9; - private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view private SomeArgs mCurrentDialogArgs; private BiometricDialogView mCurrentDialog; private WindowManager mWindowManager; @@ -63,21 +58,22 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private boolean mDialogShowing; private Callback mCallback = new Callback(); - private boolean mTryAgainShowing; // No good place to save state before config change :/ - private boolean mConfirmShowing; // No good place to save state before config change :/ - private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch(msg.what) { case MSG_SHOW_DIALOG: - handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */); + handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */, + null /* savedState */); break; case MSG_BIOMETRIC_AUTHENTICATED: - handleBiometricAuthenticated(); + handleBiometricAuthenticated((boolean) msg.obj); break; case MSG_BIOMETRIC_HELP: - handleBiometricHelp((String) msg.obj); + SomeArgs args = (SomeArgs) msg.obj; + handleBiometricHelp((String) args.arg1 /* message */, + (boolean) args.arg2 /* requireTryAgain */); + args.recycle(); break; case MSG_BIOMETRIC_ERROR: handleBiometricError((String) msg.obj); @@ -94,9 +90,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba case MSG_BUTTON_POSITIVE: handleButtonPositive(); break; - case MSG_BIOMETRIC_SHOW_TRY_AGAIN: - handleShowTryAgain(); - break; case MSG_TRY_AGAIN_PRESSED: handleTryAgainPressed(); break; @@ -137,30 +130,22 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba @Override public void start() { - createDialogs(); - - if (!mDialogs.isEmpty()) { + final PackageManager pm = mContext.getPackageManager(); + if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT) + || pm.hasSystemFeature(PackageManager.FEATURE_FACE) + || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) { getComponent(CommandQueue.class).addCallback(this); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } } - private void createDialogs() { - final PackageManager pm = mContext.getPackageManager(); - mDialogs = new HashMap<>(); - if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) { - mDialogs.put(BiometricAuthenticator.TYPE_FACE, new FaceDialogView(mContext, mCallback)); - } - if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { - mDialogs.put(BiometricAuthenticator.TYPE_FINGERPRINT, - new FingerprintDialogView(mContext, mCallback)); - } - } - @Override public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId) { - if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type); + if (DEBUG) { + Log.d(TAG, "showBiometricDialog, type: " + type + + ", requireConfirmation: " + requireConfirmation); + } // Remove these messages as they are part of the previous client mHandler.removeMessages(MSG_BIOMETRIC_ERROR); mHandler.removeMessages(MSG_BIOMETRIC_HELP); @@ -176,15 +161,18 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } @Override - public void onBiometricAuthenticated() { - if (DEBUG) Log.d(TAG, "onBiometricAuthenticated"); - mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget(); + public void onBiometricAuthenticated(boolean authenticated) { + if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated); + mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget(); } @Override public void onBiometricHelp(String message) { if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message); - mHandler.obtainMessage(MSG_BIOMETRIC_HELP, message).sendToTarget(); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = message; + args.arg2 = false; // requireTryAgain + mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget(); } @Override @@ -199,16 +187,21 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget(); } - @Override - public void showBiometricTryAgain() { - if (DEBUG) Log.d(TAG, "showBiometricTryAgain"); - mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget(); - } - - private void handleShowDialog(SomeArgs args, boolean skipAnimation) { + private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { mCurrentDialogArgs = args; final int type = args.argi1; - mCurrentDialog = mDialogs.get(type); + + if (type == BiometricAuthenticator.TYPE_FINGERPRINT) { + mCurrentDialog = new FingerprintDialogView(mContext, mCallback); + } else if (type == BiometricAuthenticator.TYPE_FACE) { + mCurrentDialog = new FaceDialogView(mContext, mCallback); + } else { + Log.e(TAG, "Unsupported type: " + type); + } + + if (savedState != null) { + mCurrentDialog.restoreState(savedState); + } if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: " + mCurrentDialog.isAnimatingAway() + " type: " + type); @@ -224,32 +217,36 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mCurrentDialog.setRequireConfirmation((boolean) args.arg3); mCurrentDialog.setUserId(args.argi2); mCurrentDialog.setSkipIntro(skipAnimation); - mCurrentDialog.setPendingTryAgain(mTryAgainShowing); - mCurrentDialog.setPendingConfirm(mConfirmShowing); mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams()); mDialogShowing = true; } - private void handleBiometricAuthenticated() { - if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated"); - - mCurrentDialog.announceForAccessibility( - mContext.getResources() - .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); - if (mCurrentDialog.requiresConfirmation()) { - mConfirmShowing = true; - mCurrentDialog.showConfirmationButton(true /* show */); + private void handleBiometricAuthenticated(boolean authenticated) { + if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated); + + if (authenticated) { + mCurrentDialog.announceForAccessibility( + mContext.getResources() + .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId())); + if (mCurrentDialog.requiresConfirmation()) { + mCurrentDialog.showConfirmationButton(true /* show */); + } else { + mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED); + mHandler.postDelayed(() -> { + handleHideDialog(false /* userCanceled */); + }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs()); + } } else { - mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED); - mHandler.postDelayed(() -> { - handleHideDialog(false /* userCanceled */); - }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs()); + handleBiometricHelp(mContext.getResources() + .getString(com.android.internal.R.string.biometric_not_recognized), + true /* requireTryAgain */); + mCurrentDialog.showTryAgainButton(true /* show */); } } - private void handleBiometricHelp(String message) { + private void handleBiometricHelp(String message, boolean requireTryAgain) { if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message); - mCurrentDialog.showHelpMessage(message); + mCurrentDialog.showHelpMessage(message, requireTryAgain); } private void handleBiometricError(String error) { @@ -258,7 +255,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba if (DEBUG) Log.d(TAG, "Dialog already dismissed"); return; } - mTryAgainShowing = false; mCurrentDialog.showErrorMessage(error); } @@ -279,8 +275,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } mReceiver = null; mDialogShowing = false; - mConfirmShowing = false; - mTryAgainShowing = false; mCurrentDialog.startDismiss(); } @@ -294,7 +288,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } catch (RemoteException e) { Log.e(TAG, "Remote exception when handling negative button", e); } - mTryAgainShowing = false; handleHideDialog(false /* userCanceled */); } @@ -308,25 +301,16 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } catch (RemoteException e) { Log.e(TAG, "Remote exception when handling positive button", e); } - mConfirmShowing = false; handleHideDialog(false /* userCanceled */); } private void handleUserCanceled() { - mTryAgainShowing = false; - mConfirmShowing = false; handleHideDialog(true /* userCanceled */); } - private void handleShowTryAgain() { - mCurrentDialog.showTryAgainButton(true /* show */); - mTryAgainShowing = true; - } - private void handleTryAgainPressed() { try { mCurrentDialog.clearTemporaryMessage(); - mTryAgainShowing = false; mReceiver.onTryAgainPressed(); } catch (RemoteException e) { Log.e(TAG, "RemoteException when handling try again", e); @@ -337,13 +321,20 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); final boolean wasShowing = mDialogShowing; + + // Save the state of the current dialog (buttons showing, etc) + final Bundle savedState = new Bundle(); + if (mCurrentDialog != null) { + mCurrentDialog.onSaveState(savedState); + } + if (mDialogShowing) { mCurrentDialog.forceRemove(); mDialogShowing = false; } - createDialogs(); + if (wasShowing) { - handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */); + handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState); } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index 9934bfd11f12..b8c69c8003c4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -56,12 +56,15 @@ public abstract class BiometricDialogView extends LinearLayout { private static final String TAG = "BiometricDialogView"; + private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility"; + private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility"; + private static final int ANIMATION_DURATION_SHOW = 250; // ms private static final int ANIMATION_DURATION_AWAY = 350; // ms private static final int MSG_CLEAR_MESSAGE = 1; - protected static final int STATE_NONE = 0; + protected static final int STATE_IDLE = 0; protected static final int STATE_AUTHENTICATING = 1; protected static final int STATE_ERROR = 2; protected static final int STATE_PENDING_CONFIRMATION = 3; @@ -78,12 +81,19 @@ public abstract class BiometricDialogView extends LinearLayout { private final float mDialogWidth; private final DialogViewCallback mCallback; - private ViewGroup mLayout; - private final Button mPositiveButton; - private final Button mNegativeButton; - private final TextView mErrorText; + protected final ViewGroup mLayout; + protected final LinearLayout mDialog; + protected final TextView mTitleText; + protected final TextView mSubtitleText; + protected final TextView mDescriptionText; + protected final ImageView mBiometricIcon; + protected final TextView mErrorText; + protected final Button mPositiveButton; + protected final Button mNegativeButton; + protected final Button mTryAgainButton; + private Bundle mBundle; - private final LinearLayout mDialog; + private int mLastState; private boolean mAnimatingAway; private boolean mWasForceRemoved; @@ -91,15 +101,13 @@ public abstract class BiometricDialogView extends LinearLayout { protected boolean mRequireConfirmation; private int mUserId; // used to determine if we should show work background - private boolean mPendingShowTryAgain; - private boolean mPendingShowConfirm; - protected abstract int getHintStringResourceId(); protected abstract int getAuthenticatedAccessibilityResourceId(); protected abstract int getIconDescriptionResourceId(); protected abstract Drawable getAnimationForTransition(int oldState, int newState); protected abstract boolean shouldAnimateForTransition(int oldState, int newState); protected abstract int getDelayAfterAuthenticatedDurationMs(); + protected abstract boolean shouldGrayAreaDismissDialog(); private final Runnable mShowAnimationRunnable = new Runnable() { @Override @@ -124,7 +132,7 @@ public abstract class BiometricDialogView extends LinearLayout { public void handleMessage(Message msg) { switch(msg.what) { case MSG_CLEAR_MESSAGE: - handleClearMessage(); + handleClearMessage((boolean) msg.obj /* requireTryAgain */); break; default: Log.e(TAG, "Unhandled message: " + msg.what); @@ -158,10 +166,6 @@ public abstract class BiometricDialogView extends LinearLayout { mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false); addView(mLayout); - mDialog = mLayout.findViewById(R.id.dialog); - - mErrorText = mLayout.findViewById(R.id.error); - mLayout.setOnKeyListener(new View.OnKeyListener() { boolean downPressed = false; @Override @@ -184,12 +188,19 @@ public abstract class BiometricDialogView extends LinearLayout { final View space = mLayout.findViewById(R.id.space); final View leftSpace = mLayout.findViewById(R.id.left_space); final View rightSpace = mLayout.findViewById(R.id.right_space); - final ImageView icon = mLayout.findViewById(R.id.biometric_icon); - final Button tryAgain = mLayout.findViewById(R.id.button_try_again); + + mDialog = mLayout.findViewById(R.id.dialog); + mTitleText = mLayout.findViewById(R.id.title); + mSubtitleText = mLayout.findViewById(R.id.subtitle); + mDescriptionText = mLayout.findViewById(R.id.description); + mBiometricIcon = mLayout.findViewById(R.id.biometric_icon); + mErrorText = mLayout.findViewById(R.id.error); mNegativeButton = mLayout.findViewById(R.id.button2); mPositiveButton = mLayout.findViewById(R.id.button1); + mTryAgainButton = mLayout.findViewById(R.id.button_try_again); - icon.setContentDescription(getResources().getString(getIconDescriptionResourceId())); + mBiometricIcon.setContentDescription( + getResources().getString(getIconDescriptionResourceId())); setDismissesDialog(space); setDismissesDialog(leftSpace); @@ -206,8 +217,9 @@ public abstract class BiometricDialogView extends LinearLayout { }, getDelayAfterAuthenticatedDurationMs()); }); - tryAgain.setOnClickListener((View v) -> { + mTryAgainButton.setOnClickListener((View v) -> { showTryAgainButton(false /* show */); + handleClearMessage(false /* requireTryAgain */); mCallback.onTryAgainPressed(); }); @@ -215,15 +227,17 @@ public abstract class BiometricDialogView extends LinearLayout { mLayout.requestFocus(); } + public void onSaveState(Bundle bundle) { + bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility()); + bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility()); + } + @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mErrorText.setText(getHintStringResourceId()); - final TextView title = mLayout.findViewById(R.id.title); - final TextView subtitle = mLayout.findViewById(R.id.subtitle); - final TextView description = mLayout.findViewById(R.id.description); final ImageView backgroundView = mLayout.findViewById(R.id.background); if (mUserManager.isManagedProfile(mUserId)) { @@ -244,36 +258,34 @@ public abstract class BiometricDialogView extends LinearLayout { mDialog.getLayoutParams().width = (int) mDialogWidth; } - mLastState = STATE_NONE; + mLastState = STATE_IDLE; updateState(STATE_AUTHENTICATING); CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE); - title.setText(titleText); - title.setSelected(true); + mTitleText.setVisibility(View.VISIBLE); + mTitleText.setText(titleText); + mTitleText.setSelected(true); final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); if (TextUtils.isEmpty(subtitleText)) { - subtitle.setVisibility(View.GONE); + mSubtitleText.setVisibility(View.GONE); } else { - subtitle.setVisibility(View.VISIBLE); - subtitle.setText(subtitleText); + mSubtitleText.setVisibility(View.VISIBLE); + mSubtitleText.setText(subtitleText); } final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); if (TextUtils.isEmpty(descriptionText)) { - description.setVisibility(View.GONE); + mDescriptionText.setVisibility(View.GONE); } else { - description.setVisibility(View.VISIBLE); - description.setText(descriptionText); + mDescriptionText.setVisibility(View.VISIBLE); + mDescriptionText.setText(descriptionText); } mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT)); - showTryAgainButton(mPendingShowTryAgain); - showConfirmationButton(mPendingShowConfirm); - if (mWasForceRemoved || mSkipIntro) { // Show the dialog immediately mLayout.animate().cancel(); @@ -302,8 +314,7 @@ public abstract class BiometricDialogView extends LinearLayout { ? (AnimatedVectorDrawable) icon : null; - final ImageView imageView = getLayout().findViewById(R.id.biometric_icon); - imageView.setImageDrawable(icon); + mBiometricIcon.setImageDrawable(icon); if (animation != null && shouldAnimateForTransition(lastState, newState)) { animation.forceAnimationOnUI(); @@ -314,7 +325,7 @@ public abstract class BiometricDialogView extends LinearLayout { private void setDismissesDialog(View v) { v.setClickable(true); v.setOnTouchListener((View view, MotionEvent event) -> { - if (mLastState != STATE_AUTHENTICATED) { + if (mLastState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) { mCallback.onUserCanceled(); } return true; @@ -331,11 +342,9 @@ public abstract class BiometricDialogView extends LinearLayout { mWindowManager.removeView(BiometricDialogView.this); mAnimatingAway = false; // Set the icons / text back to normal state - handleClearMessage(); + handleClearMessage(false /* requireTryAgain */); showTryAgainButton(false /* show */); - mPendingShowTryAgain = false; - mPendingShowConfirm = false; - updateState(STATE_NONE); + updateState(STATE_IDLE); } }; @@ -412,35 +421,42 @@ public abstract class BiometricDialogView extends LinearLayout { return mLayout; } - // Clears the temporary message and shows the help message. - private void handleClearMessage() { - updateState(STATE_AUTHENTICATING); - mErrorText.setText(getHintStringResourceId()); - mErrorText.setTextColor(mTextColor); + // Clears the temporary message and shows the help message. If requireTryAgain is true, + // we will start the authenticating state again. + private void handleClearMessage(boolean requireTryAgain) { + if (!requireTryAgain) { + updateState(STATE_AUTHENTICATING); + mErrorText.setText(getHintStringResourceId()); + mErrorText.setTextColor(mTextColor); + mErrorText.setVisibility(View.VISIBLE); + } else { + updateState(STATE_IDLE); + mErrorText.setVisibility(View.INVISIBLE); + } } // Shows an error/help message - private void showTemporaryMessage(String message) { + private void showTemporaryMessage(String message, boolean requireTryAgain) { mHandler.removeMessages(MSG_CLEAR_MESSAGE); updateState(STATE_ERROR); mErrorText.setText(message); mErrorText.setTextColor(mErrorColor); mErrorText.setContentDescription(message); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE), + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE, requireTryAgain), BiometricPrompt.HIDE_DIALOG_DELAY); } public void clearTemporaryMessage() { mHandler.removeMessages(MSG_CLEAR_MESSAGE); - mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget(); + mHandler.obtainMessage(MSG_CLEAR_MESSAGE, false /* requireTryAgain */).sendToTarget(); } - public void showHelpMessage(String message) { - showTemporaryMessage(message); + public void showHelpMessage(String message, boolean requireTryAgain) { + showTemporaryMessage(message, requireTryAgain); } public void showErrorMessage(String error) { - showTemporaryMessage(error); + showTemporaryMessage(error, false /* requireTryAgain */); showTryAgainButton(false /* show */); mCallback.onErrorShown(); } @@ -459,22 +475,16 @@ public abstract class BiometricDialogView extends LinearLayout { } public void showTryAgainButton(boolean show) { - final Button tryAgain = mLayout.findViewById(R.id.button_try_again); if (show) { - tryAgain.setVisibility(View.VISIBLE); + mTryAgainButton.setVisibility(View.VISIBLE); } else { - tryAgain.setVisibility(View.GONE); + mTryAgainButton.setVisibility(View.GONE); } } - // Set the state before the window is attached, so we know if the dialog should be started - // with or without the button. This is because there's no good onPause signal - public void setPendingTryAgain(boolean show) { - mPendingShowTryAgain = show; - } - - public void setPendingConfirm(boolean show) { - mPendingShowConfirm = show; + public void restoreState(Bundle bundle) { + mTryAgainButton.setVisibility(bundle.getInt(KEY_TRY_AGAIN_VISIBILITY)); + mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY)); } public WindowManager.LayoutParams getLayoutParams() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java index de3f9471a6ba..359cb047c84d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java @@ -16,8 +16,18 @@ package com.android.systemui.biometrics; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Outline; import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewOutlineProvider; import com.android.systemui.R; @@ -28,13 +38,243 @@ import com.android.systemui.R; */ public class FaceDialogView extends BiometricDialogView { + private static final String TAG = "FaceDialogView"; + private static final String KEY_DIALOG_SIZE = "key_dialog_size"; + private static final int HIDE_DIALOG_DELAY = 500; // ms + private static final int IMPLICIT_Y_PADDING = 16; // dp + private static final int GROW_DURATION = 150; // ms + private static final int TEXT_ANIMATE_DISTANCE = 32; // dp + + private static final int SIZE_UNKNOWN = 0; + private static final int SIZE_SMALL = 1; + private static final int SIZE_GROWING = 2; + private static final int SIZE_BIG = 3; + + private int mSize; + private float mIconOriginalY; + private DialogOutlineProvider mOutlineProvider = new DialogOutlineProvider(); + + private final class DialogOutlineProvider extends ViewOutlineProvider { + + float mY; + + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect( + 0 /* left */, + (int) mY, /* top */ + mDialog.getWidth() /* right */, + mDialog.getBottom(), /* bottom */ + getResources().getDimension(R.dimen.biometric_dialog_corner_size)); + } + + int calculateSmall() { + final float padding = dpToPixels(IMPLICIT_Y_PADDING); + return mDialog.getHeight() - mBiometricIcon.getHeight() - 2 * (int) padding; + } + + void setOutlineY(float y) { + mY = y; + } + } public FaceDialogView(Context context, DialogViewCallback callback) { super(context, callback); } + private void updateSize(int newSize) { + final float padding = dpToPixels(IMPLICIT_Y_PADDING); + final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding; + + if (newSize == SIZE_SMALL) { + // These fields are required and/or always hold a spot on the UI, so should be set to + // INVISIBLE so they keep their position + mTitleText.setVisibility(View.INVISIBLE); + mErrorText.setVisibility(View.INVISIBLE); + mNegativeButton.setVisibility(View.INVISIBLE); + + // These fields are optional, so set them to gone or invisible depending on their + // usage. If they're empty, they're already set to GONE in BiometricDialogView. + if (!TextUtils.isEmpty(mSubtitleText.getText())) { + mSubtitleText.setVisibility(View.INVISIBLE); + } + if (!TextUtils.isEmpty(mDescriptionText.getText())) { + mDescriptionText.setVisibility(View.INVISIBLE); + } + + // Move the biometric icon to the small spot + mBiometricIcon.setY(iconSmallPositionY); + + // Clip the dialog to the small size + mDialog.setOutlineProvider(mOutlineProvider); + mOutlineProvider.setOutlineY(mOutlineProvider.calculateSmall()); + + mDialog.setClipToOutline(true); + mDialog.invalidateOutline(); + + mSize = newSize; + } else if (mSize == SIZE_SMALL && newSize == SIZE_BIG) { + mSize = SIZE_GROWING; + + // Animate the outline + final ValueAnimator outlineAnimator = + ValueAnimator.ofFloat(mOutlineProvider.calculateSmall(), 0); + outlineAnimator.addUpdateListener((animation) -> { + final float y = (float) animation.getAnimatedValue(); + mOutlineProvider.setOutlineY(y); + mDialog.invalidateOutline(); + }); + + // Animate the icon back to original big position + final ValueAnimator iconAnimator = + ValueAnimator.ofFloat(iconSmallPositionY, mIconOriginalY); + iconAnimator.addUpdateListener((animation) -> { + final float y = (float) animation.getAnimatedValue(); + mBiometricIcon.setY(y); + }); + + // Animate the error text so it slides up with the icon + final ValueAnimator textSlideAnimator = + ValueAnimator.ofFloat(dpToPixels(TEXT_ANIMATE_DISTANCE), 0); + textSlideAnimator.addUpdateListener((animation) -> { + final float y = (float) animation.getAnimatedValue(); + mErrorText.setTranslationY(y); + }); + + // Opacity animator for things that should fade in (title, subtitle, details, negative + // button) + final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1); + opacityAnimator.addUpdateListener((animation) -> { + final float opacity = (float) animation.getAnimatedValue(); + + // These fields are required and/or always hold a spot on the UI + mTitleText.setAlpha(opacity); + mErrorText.setAlpha(opacity); + mNegativeButton.setAlpha(opacity); + mTryAgainButton.setAlpha(opacity); + + // These fields are optional, so only animate them if they're supposed to be showing + if (!TextUtils.isEmpty(mSubtitleText.getText())) { + mSubtitleText.setAlpha(opacity); + } + if (!TextUtils.isEmpty(mDescriptionText.getText())) { + mDescriptionText.setAlpha(opacity); + } + }); + + // Choreograph together + final AnimatorSet as = new AnimatorSet(); + as.setDuration(GROW_DURATION); + as.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + // Set the visibility of opacity-animating views back to VISIBLE + mTitleText.setVisibility(View.VISIBLE); + mErrorText.setVisibility(View.VISIBLE); + mNegativeButton.setVisibility(View.VISIBLE); + mTryAgainButton.setVisibility(View.VISIBLE); + + if (!TextUtils.isEmpty(mSubtitleText.getText())) { + mSubtitleText.setVisibility(View.VISIBLE); + } + if (!TextUtils.isEmpty(mDescriptionText.getText())) { + mDescriptionText.setVisibility(View.VISIBLE); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mSize = SIZE_BIG; + } + }); + as.play(outlineAnimator).with(iconAnimator).with(opacityAnimator) + .with(textSlideAnimator); + as.start(); + } else if (mSize == SIZE_BIG) { + mDialog.setClipToOutline(false); + mDialog.invalidateOutline(); + + mBiometricIcon.setY(mIconOriginalY); + + mSize = newSize; + } + } + + @Override + public void onSaveState(Bundle bundle) { + super.onSaveState(bundle); + bundle.putInt(KEY_DIALOG_SIZE, mSize); + } + + @Override + public void restoreState(Bundle bundle) { + super.restoreState(bundle); + // Keep in mind that this happens before onAttachedToWindow() + mSize = bundle.getInt(KEY_DIALOG_SIZE); + } + + /** + * Do small/big layout here instead of onAttachedToWindow, since: + * 1) We need the big layout to be measured, etc for small -> big animation + * 2) We need the dialog measurements to know where to move the biometric icon to + * + * BiometricDialogView already sets the views to their default big state, so here we only + * need to hide the ones that are unnecessary. + */ + @Override + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (mIconOriginalY == 0) { + mIconOriginalY = mBiometricIcon.getY(); + } + + // UNKNOWN means size hasn't been set yet. First time we create the dialog. + // onLayout can happen when visibility of views change (during animation, etc). + if (mSize != SIZE_UNKNOWN) { + // Probably not the cleanest way to do this, but since dialog is big by default, + // and small dialogs can persist across orientation changes, we need to set it to + // small size here again. + if (mSize == SIZE_SMALL) { + updateSize(SIZE_SMALL); + } + return; + } + + // If we don't require confirmation, show the small dialog first (until errors occur). + if (!requiresConfirmation()) { + updateSize(SIZE_SMALL); + } else { + updateSize(SIZE_BIG); + } + } + + @Override + public void showErrorMessage(String error) { + super.showErrorMessage(error); + + // All error messages will cause the dialog to go from small -> big. Error messages + // are messages such as lockout, auth failed, etc. + if (mSize == SIZE_SMALL) { + updateSize(SIZE_BIG); + } + } + + @Override + public void showTryAgainButton(boolean show) { + if (show && mSize == SIZE_SMALL) { + // Do not call super, we will nicely animate the alpha together with the rest + // of the elements in here. + updateSize(SIZE_BIG); + } else { + super.showTryAgainButton(show); + } + } + @Override protected int getHintStringResourceId() { return R.string.face_dialog_looking_for_face; @@ -56,7 +296,9 @@ public class FaceDialogView extends BiometricDialogView { @Override protected boolean shouldAnimateForTransition(int oldState, int newState) { - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_ERROR && newState == STATE_IDLE) { + return true; + } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { return false; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { return true; @@ -78,9 +320,19 @@ public class FaceDialogView extends BiometricDialogView { } @Override + protected boolean shouldGrayAreaDismissDialog() { + if (mSize == SIZE_SMALL) { + return false; + } + return true; + } + + @Override protected Drawable getAnimationForTransition(int oldState, int newState) { int iconRes; - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_ERROR && newState == STATE_IDLE) { + iconRes = R.drawable.face_dialog_error_to_face; + } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { iconRes = R.drawable.face_dialog_face_to_error; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { iconRes = R.drawable.face_dialog_face_to_error; @@ -97,4 +349,14 @@ public class FaceDialogView extends BiometricDialogView { } return mContext.getDrawable(iconRes); } + + private float dpToPixels(float dp) { + return dp * ((float) mContext.getResources().getDisplayMetrics().densityDpi + / DisplayMetrics.DENSITY_DEFAULT); + } + + private float pixelsToDp(float pixels) { + return pixels / ((float) mContext.getResources().getDisplayMetrics().densityDpi + / DisplayMetrics.DENSITY_DEFAULT); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java index 1a6cee281c84..d63836b2207e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java @@ -49,7 +49,7 @@ public class FingerprintDialogView extends BiometricDialogView { @Override protected boolean shouldAnimateForTransition(int oldState, int newState) { - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { return false; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { return true; @@ -68,9 +68,15 @@ public class FingerprintDialogView extends BiometricDialogView { } @Override + protected boolean shouldGrayAreaDismissDialog() { + // Fingerprint dialog always dismisses when region outside the dialog is tapped + return true; + } + + @Override protected Drawable getAnimationForTransition(int oldState, int newState) { int iconRes; - if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) { + if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) { iconRes = R.drawable.fingerprint_dialog_fp_to_error; } else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) { iconRes = R.drawable.fingerprint_dialog_fp_to_error; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 75776098decf..9f3ff782211d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -23,15 +23,20 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP; import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; +import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; import android.provider.Settings; import android.service.notification.StatusBarNotification; +import android.util.Log; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -68,6 +73,8 @@ public class BubbleController { private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging"; private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing"; private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all"; + private static final String ENABLE_BUBBLE_ACTIVITY_VIEW = "experiment_bubble_activity_view"; + private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent"; private final Context mContext; private final NotificationEntryManager mNotificationEntryManager; @@ -189,6 +196,9 @@ public class BubbleController { // It's new BubbleView bubble = new BubbleView(mContext); bubble.setNotif(notif); + if (shouldUseActivityView(mContext)) { + bubble.setAppOverlayIntent(getAppOverlayIntent(notif)); + } mBubbles.put(bubble.getKey(), bubble); boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE; @@ -216,6 +226,21 @@ public class BubbleController { } } + @Nullable + private PendingIntent getAppOverlayIntent(NotificationEntry notif) { + Notification notification = notif.notification.getNotification(); + if (canLaunchInActivityView(notification.getAppOverlayIntent())) { + return notification.getAppOverlayIntent(); + } else if (shouldUseContentIntent(mContext) + && canLaunchInActivityView(notification.contentIntent)) { + Log.d(TAG, "[addBubble " + notif.key + + "]: No appOverlayIntent, using contentIntent."); + return notification.contentIntent; + } + Log.d(TAG, "[addBubble " + notif.key + "]: No supported intent for ActivityView."); + return null; + } + /** * Removes the bubble associated with the {@param uri}. */ @@ -223,6 +248,7 @@ public class BubbleController { BubbleView bv = mBubbles.get(key); if (mStackView != null && bv != null) { mStackView.removeBubble(bv); + bv.destroyActivityView(mStackView); bv.getEntry().setBubbleDismissed(true); } @@ -282,9 +308,10 @@ public class BubbleController { } } } - for (BubbleView view : viewsToRemove) { - mBubbles.remove(view.getKey()); - mStackView.removeBubble(view); + for (BubbleView bubbleView : viewsToRemove) { + mBubbles.remove(bubbleView.getKey()); + mStackView.removeBubble(bubbleView); + bubbleView.destroyActivityView(mStackView); } if (mStackView != null) { mStackView.setVisibility(visible ? VISIBLE : INVISIBLE); @@ -306,6 +333,17 @@ public class BubbleController { return mTempRect; } + private boolean canLaunchInActivityView(PendingIntent intent) { + if (intent == null) { + return false; + } + ActivityInfo info = + intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0); + return info != null + && ActivityInfo.isResizeableMode(info.resizeMode) + && (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0; + } + @VisibleForTesting BubbleStackView getStackView() { return mStackView; @@ -378,4 +416,14 @@ public class BubbleController { return Settings.Secure.getInt(context.getContentResolver(), ENABLE_AUTO_BUBBLE_ALL, 0) != 0; } + + private static boolean shouldUseActivityView(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + ENABLE_BUBBLE_ACTIVITY_VIEW, 0) != 0; + } + + private static boolean shouldUseContentIntent(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java index e28d96b2def9..badefe182bdd 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java @@ -24,6 +24,7 @@ import android.graphics.drawable.ShapeDrawable; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; +import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; @@ -35,6 +36,8 @@ public class BubbleExpandedViewContainer extends LinearLayout { // The triangle pointing to the expanded view private View mPointerView; + // The view displayed between the pointer and the expanded view + private TextView mHeaderView; // The view that is being displayed for the expanded state private View mExpandedView; @@ -68,6 +71,7 @@ public class BubbleExpandedViewContainer extends LinearLayout { TriangleShape.create(width, height, true /* pointUp */)); triangleDrawable.setTint(Color.WHITE); // TODO: dark mode mPointerView.setBackground(triangleDrawable); + mHeaderView = findViewById(R.id.bubble_content_header); } /** @@ -80,9 +84,19 @@ public class BubbleExpandedViewContainer extends LinearLayout { } /** + * Set the text displayed within the header. + */ + public void setHeaderText(CharSequence text) { + mHeaderView.setText(text); + } + + /** * Set the view to display for the expanded state. Passing null will clear the view. */ public void setExpandedView(View view) { + if (mExpandedView == view) { + return; + } if (mExpandedView != null) { removeView(mExpandedView); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index d69e7b3ae924..3280a331a5c7 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -21,10 +21,13 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.app.ActivityView; +import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.RectF; +import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -50,6 +53,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState; */ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView { + private static final String TAG = "BubbleStackView"; private Point mDisplaySize; private FrameLayout mBubbleContainer; @@ -59,6 +63,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private int mBubblePadding; private boolean mIsExpanded; + private int mExpandedBubbleHeight; private BubbleView mExpandedBubble; private Point mCollapsedPosition; private BubbleTouchHandler mTouchHandler; @@ -106,6 +111,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mDisplaySize = new Point(); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getSize(mDisplaySize); @@ -389,27 +395,63 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } private void updateExpandedBubble() { - if (mExpandedBubble != null) { + if (mExpandedBubble == null) { + return; + } + + if (mExpandedBubble.hasAppOverlayIntent()) { + ActivityView expandedView = mExpandedBubble.getActivityView(); + expandedView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight)); + + final PendingIntent intent = mExpandedBubble.getAppOverlayIntent(); + mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString()); + mExpandedViewContainer.setExpandedView(expandedView); + expandedView.setCallback(new ActivityView.StateCallback() { + @Override + public void onActivityViewReady(ActivityView view) { + Log.d(TAG, "onActivityViewReady(" + + mExpandedBubble.getEntry().key + "): " + view); + view.startActivity(intent); + } + + @Override + public void onActivityViewDestroyed(ActivityView view) { + NotificationEntry entry = mExpandedBubble.getEntry(); + Log.d(TAG, "onActivityViewDestroyed(key=" + + ((entry != null) ? entry.key : "(none)") + "): " + view); + } + }); + } else { ExpandableNotificationRow row = mExpandedBubble.getRowView(); - if (!row.equals(mExpandedViewContainer.getChildAt(0))) { + if (!row.equals(mExpandedViewContainer.getExpandedView())) { // Different expanded view than what we have mExpandedViewContainer.setExpandedView(null); } - int pointerPosition = mExpandedBubble.getPosition().x - + (mExpandedBubble.getWidth() / 2); - mExpandedViewContainer.setPointerPosition(pointerPosition); mExpandedViewContainer.setExpandedView(row); + mExpandedViewContainer.setHeaderText(null); } + int pointerPosition = mExpandedBubble.getPosition().x + + (mExpandedBubble.getWidth() / 2); + mExpandedViewContainer.setPointerPosition(pointerPosition); } private void applyCurrentState() { + Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded); + mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); if (!mIsExpanded) { mExpandedViewContainer.setExpandedView(null); } else { mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight()); - ExpandableNotificationRow row = mExpandedBubble.getRowView(); - applyRowState(row); + View expandedView = mExpandedViewContainer.getExpandedView(); + if (expandedView instanceof ActivityView) { + if (expandedView.isAttachedToWindow()) { + ((ActivityView) expandedView).onLocationChanged(); + } + } else { + applyRowState(mExpandedBubble.getRowView()); + } } int bubbsCount = mBubbleContainer.getChildCount(); for (int i = 0; i < bubbsCount; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 88030eefdf54..96b2dbab9bdf 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -33,7 +33,7 @@ import com.android.systemui.pip.phone.PipDismissViewController; * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing, * dismissing, and flings. */ -public class BubbleTouchHandler implements View.OnTouchListener { +class BubbleTouchHandler implements View.OnTouchListener { private BubbleController mController = Dependency.get(BubbleController.class); private PipDismissViewController mDismissViewController; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index 3307992e779a..c1bbb9379e9c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -16,7 +16,9 @@ package com.android.systemui.bubbles; +import android.app.ActivityView; import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; import android.graphics.Point; @@ -25,7 +27,9 @@ import android.graphics.drawable.Icon; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.util.AttributeSet; +import android.util.Log; import android.view.View; +import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; @@ -37,7 +41,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow /** * A floating object on the screen that has a collapsed and expanded state. */ -public class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView { +class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView { private static final String TAG = "BubbleView"; private Context mContext; @@ -46,6 +50,8 @@ public class BubbleView extends LinearLayout implements BubbleTouchHandler.Float private NotificationEntry mEntry; private int mBubbleSize; private int mIconSize; + private PendingIntent mAppOverlayIntent; + private ActivityView mActivityView; public BubbleView(Context context) { this(context, null); @@ -117,12 +123,51 @@ public class BubbleView extends LinearLayout implements BubbleTouchHandler.Float } /** - * @return the view to display when the bubble is expanded. + * @return the view to display notification content when the bubble is expanded. */ public ExpandableNotificationRow getRowView() { return mEntry.getRow(); } + /** + * @return a view used to display app overlay content when expanded. + */ + public ActivityView getActivityView() { + if (mActivityView == null) { + mActivityView = new ActivityView(mContext); + Log.d(TAG, "[getActivityView] created: " + mActivityView); + } + return mActivityView; + } + + /** + * Removes and releases an ActivityView if one was previously created for this bubble. + */ + public void destroyActivityView(ViewGroup tmpParent) { + if (mActivityView == null) { + return; + } + // HACK: Only release if initialized. There's no way to know if the ActivityView has + // been initialized. Calling release() if it hasn't been initialized will crash. + + if (!mActivityView.isAttachedToWindow()) { + // HACK: release() will crash if the view is not attached. + + mActivityView.setVisibility(View.GONE); + tmpParent.addView(mActivityView, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + } + try { + mActivityView.release(); + } catch (IllegalStateException ex) { + Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex); + } + + ((ViewGroup) mActivityView.getParent()).removeView(mActivityView); + mActivityView = null; + } + @Override public void setPosition(int x, int y) { setTranslationX(x); @@ -162,4 +207,20 @@ public class BubbleView extends LinearLayout implements BubbleTouchHandler.Float lp.height = mBubbleSize; v.setLayoutParams(lp); } + + /** + * @return whether an ActivityView should be used to display the content of this Bubble + */ + public boolean hasAppOverlayIntent() { + return mAppOverlayIntent != null; + } + + public PendingIntent getAppOverlayIntent() { + return mAppOverlayIntent; + + } + + public void setAppOverlayIntent(PendingIntent intent) { + mAppOverlayIntent = intent; + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 323cf1fe7022..f14495bb959d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1843,6 +1843,13 @@ public class KeyguardViewMediator extends SystemUI { */ private void handleHide() { Trace.beginSection("KeyguardViewMediator#handleHide"); + + // It's possible that the device was unlocked in a dream state. It's time to wake up. + if (mAodShowing) { + PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:BOUNCER_DOZING"); + } + synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleHide"); diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt index 01ee5cabe5a0..4388200709fc 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt @@ -52,7 +52,8 @@ class OngoingPrivacyDialog constructor( @Suppress("DEPRECATION") override fun onClick(dialog: DialogInterface?, which: Int) { - Dependency.get(ActivityStarter::class.java).startActivity(intent, false) + Dependency.get(ActivityStarter::class.java) + .postStartActivityDismissingKeyguard(intent, 0) } }) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index e030e404af6a..e63f88a898cc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -284,10 +284,17 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, public void updateEverything() { post(() -> { updateVisibilities(); + updateClickabilities(); setClickable(false); }); } + private void updateClickabilities() { + mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE); + mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE); + mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE); + } + private void updateVisibilities() { mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE); mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 40c60396e40d..28285e14ef4b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -699,7 +699,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements } public void updateEverything() { - post(() -> setClickable(false)); + post(() -> setClickable(!mExpanded)); } public void setQSPanel(final QSPanel qsPanel) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 81757d0aadd4..c474faf6b1e0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -101,6 +101,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private float mBackButtonAlpha; private MotionEvent mStatusBarGestureDownEvent; private float mWindowCornerRadius; + private boolean mSupportsRoundedCornersOnWindows; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -244,6 +245,18 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + public boolean supportsRoundedCornersOnWindows() { + if (!verifyCaller("supportsRoundedCornersOnWindows")) { + return false; + } + long token = Binder.clearCallingIdentity(); + try { + return mSupportsRoundedCornersOnWindows; + } finally { + Binder.restoreCallingIdentity(token); + } + } + private boolean verifyCaller(String reason) { final int callerId = Binder.getCallingUserHandle().getIdentifier(); if (callerId != mCurrentBoundedUserId) { @@ -353,6 +366,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS, getDefaultInteractionFlags()); mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources()); + mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils + .supportsRoundedCornersOnWindows(mContext.getResources()); // Listen for the package update changes. if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 6a015630a076..904478efb568 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -111,7 +111,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT; private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT; - private static final int MSG_BIOMETRIC_TRY_AGAIN = 47 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -271,11 +270,10 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver, int type, boolean requireConfirmation, int userId) { } - default void onBiometricAuthenticated() { } + default void onBiometricAuthenticated(boolean authenticated) { } default void onBiometricHelp(String message) { } default void onBiometricError(String error) { } default void hideBiometricDialog() { } - default void showBiometricTryAgain() { } } @VisibleForTesting @@ -736,9 +734,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } @Override - public void onBiometricAuthenticated() { + public void onBiometricAuthenticated(boolean authenticated) { synchronized (mLock) { - mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget(); + mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget(); } } @@ -763,13 +761,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } } - @Override - public void showBiometricTryAgain() { - synchronized (mLock) { - mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget(); - } - } - private final class H extends Handler { private H(Looper l) { super(l); @@ -991,7 +982,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< break; case MSG_BIOMETRIC_AUTHENTICATED: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onBiometricAuthenticated(); + mCallbacks.get(i).onBiometricAuthenticated((boolean) msg.obj); } break; case MSG_BIOMETRIC_HELP: @@ -1024,11 +1015,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< mCallbacks.get(i).showPinningEscapeToast(); } break; - case MSG_BIOMETRIC_TRY_AGAIN: - for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).showBiometricTryAgain(); - } - break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index e698e643534b..45db00210a2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification; import android.annotation.Nullable; import android.app.Notification; import android.content.Context; -import android.os.Handler; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -51,7 +50,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.concurrent.TimeUnit; /** * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. @@ -64,11 +62,10 @@ public class NotificationEntryManager implements NotificationUpdateHandler, VisualStabilityManager.Callback { private static final String TAG = "NotificationEntryMgr"; - protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); - - protected final Context mContext; + private final Context mContext; + @VisibleForTesting protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>(); private final DeviceProvisionedController mDeviceProvisionedController = @@ -80,13 +77,11 @@ public class NotificationEntryManager implements private NotificationRemoteInputManager mRemoteInputManager; private NotificationRowBinder mNotificationRowBinder; - private final Handler mDeferredNotificationViewUpdateHandler; - private Runnable mUpdateNotificationViewsCallback; - private NotificationPresenter mPresenter; private NotificationListenerService.RankingMap mLatestRankingMap; + @VisibleForTesting protected NotificationData mNotificationData; - protected NotificationListContainer mListContainer; + private NotificationListContainer mListContainer; @VisibleForTesting final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders = new ArrayList<>(); @@ -121,7 +116,6 @@ public class NotificationEntryManager implements public NotificationEntryManager(Context context) { mContext = context; mNotificationData = new NotificationData(); - mDeferredNotificationViewUpdateHandler = new Handler(); } /** Adds a {@link NotificationEntryListener}. */ @@ -156,7 +150,6 @@ public class NotificationEntryManager implements NotificationListContainer listContainer, HeadsUpManager headsUpManager) { mPresenter = presenter; - mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews; mNotificationData.setHeadsUpManager(headsUpManager); mListContainer = listContainer; @@ -180,14 +173,6 @@ public class NotificationEntryManager implements return mNotificationData; } - protected Context getContext() { - return mContext; - } - - protected NotificationPresenter getPresenter() { - return mPresenter; - } - @Override public void onReorderingAllowed() { updateNotifications(); @@ -229,15 +214,6 @@ public class NotificationEntryManager implements } } - private void maybeScheduleUpdateNotificationViews(NotificationEntry entry) { - long audibleAlertTimeout = RECENTLY_ALERTED_THRESHOLD_MS - - (System.currentTimeMillis() - entry.lastAudiblyAlertedMs); - if (audibleAlertTimeout > 0) { - mDeferredNotificationViewUpdateHandler.postDelayed( - mUpdateNotificationViewsCallback, audibleAlertTimeout); - } - } - @Override public void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags) { @@ -256,7 +232,6 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onNotificationAdded(entry); } - maybeScheduleUpdateNotificationViews(entry); } else { for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onEntryReinflated(entry); @@ -463,8 +438,6 @@ public class NotificationEntryManager implements for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPostEntryUpdated(entry); } - - maybeScheduleUpdateNotificationViews(entry); } @Override 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 9e0dd8461d21..95bd1ce1f9a1 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 @@ -86,7 +86,6 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -106,6 +105,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import java.util.function.Consumer; @@ -122,6 +122,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private static final int MENU_VIEW_INDEX = 0; private static final String TAG = "ExpandableNotifRow"; public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f; + private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30); private boolean mUpdateBackgroundOnUpdate; /** @@ -1693,17 +1694,31 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** Sets the last time the notification being displayed audibly alerted the user. */ public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) { if (NotificationUtils.useNewInterruptionModel(mContext)) { - boolean recentlyAudiblyAlerted = System.currentTimeMillis() - lastAudiblyAlertedMs - < NotificationEntryManager.RECENTLY_ALERTED_THRESHOLD_MS; - if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { - mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted( - recentlyAudiblyAlerted); + long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs; + boolean alertedRecently = + timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS; + + applyAudiblyAlertedRecently(alertedRecently); + + removeCallbacks(mExpireRecentlyAlertedFlag); + if (alertedRecently) { + long timeUntilNoLongerRecent = + RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly; + postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent); } - mPrivateLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted); - mPublicLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted); } } + private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false); + + private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) { + if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { + mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(audiblyAlertedRecently); + } + mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); + mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently); + } + public View.OnClickListener getAppOpsOnClickListener() { return mOnAppOpsClickListener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index d873b0cd1f26..75adf50c092e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -956,7 +956,7 @@ public class NotificationPanelView extends PanelView implements handled = true; } handled |= super.onTouchEvent(event); - return mDozing ? handled : true; + return !mDozing || mPulsing || handled; } private boolean handleQsTouch(MotionEvent event) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index e25c8292b637..4bece48e2a35 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -364,7 +364,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo mExpansionFraction = fraction; final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED - || mState == ScrimState.KEYGUARD; + || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING; if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) { return; } @@ -409,7 +409,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo behindFraction = (float) Math.pow(behindFraction, 0.8f); mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY; mCurrentInFrontAlpha = 0; - } else if (mState == ScrimState.KEYGUARD) { + } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) { // Either darken of make the scrim transparent when you // pull down the shade float interpolatedFract = getInterpolatedFraction(); @@ -562,8 +562,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo if (alpha == 0f) { scrim.setClickable(false); } else { - // Eat touch events (unless dozing or pulsing). - scrim.setClickable(mState != ScrimState.AOD && mState != ScrimState.PULSING); + // Eat touch events (unless dozing). + scrim.setClickable(mState != ScrimState.AOD); } updateScrim(scrim, alpha); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 6d78f7204163..6f877ba90cfa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -3573,10 +3573,7 @@ public class StatusBar extends SystemUI implements DemoMode, mVisualStabilityManager.setScreenOn(false); updateVisibleToUser(); - // We need to disable touch events because these might - // collapse the panel after we expanded it, and thus we would end up with a blank - // Keyguard. - mNotificationPanel.setTouchAndAnimationDisabled(true); + updateNotificationPanelTouchState(); mStatusBarWindow.cancelCurrentTouch(); if (mLaunchCameraOnFinishedGoingToSleep) { mLaunchCameraOnFinishedGoingToSleep = false; @@ -3599,13 +3596,22 @@ public class StatusBar extends SystemUI implements DemoMode, mDeviceInteractive = true; mAmbientPulseManager.releaseAllImmediately(); mVisualStabilityManager.setScreenOn(true); - mNotificationPanel.setTouchAndAnimationDisabled(false); + updateNotificationPanelTouchState(); updateVisibleToUser(); updateIsKeyguard(); mDozeServiceHost.stopDozing(); } }; + /** + * We need to disable touch events because these might + * collapse the panel after we expanded it, and thus we would end up with a blank + * Keyguard. + */ + private void updateNotificationPanelTouchState() { + mNotificationPanel.setTouchAndAnimationDisabled(!mDeviceInteractive && !mPulsing); + } + final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurningOn() { @@ -3871,17 +3877,15 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPulseStarted() { callback.onPulseStarted(); - if (mAmbientPulseManager.hasNotifications()) { - // Only pulse the stack scroller if there's actually something to show. - // Otherwise just show the always-on screen. - setPulsing(true); - } + updateNotificationPanelTouchState(); + setPulsing(true); } @Override public void onPulseFinished() { mPulsing = false; callback.onPulseFinished(); + updateNotificationPanelTouchState(); setPulsing(false); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 0f8970f1069f..bb23608799f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -187,7 +187,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } else if (bouncerNeedsScrimming()) { mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); - } else if (mShowing && !mDozing) { + } else if (mShowing) { if (!isWakeAndUnlocking() && !mStatusBar.isInLaunchTransition()) { mBouncer.setExpansion(expansion); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 53e461db3dd1..8b25c3469abe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -339,7 +339,7 @@ public class StatusBarWindowView extends FrameLayout { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout(); - if (mService.isDozing() && !stackScrollLayout.hasPulsingNotifications()) { + if (mService.isDozing() && !mService.isPulsing()) { // Capture all touch events in always-on. return true; } @@ -347,8 +347,7 @@ public class StatusBarWindowView extends FrameLayout { if (mNotificationPanel.isFullyExpanded() && stackScrollLayout.getVisibility() == View.VISIBLE && mStatusBarStateController.getState() == StatusBarState.KEYGUARD - && !mService.isBouncerShowing() - && !mService.isDozing()) { + && !mService.isBouncerShowing()) { intercept = mDragDownHelper.onInterceptTouchEvent(ev); } if (!intercept) { @@ -369,7 +368,7 @@ public class StatusBarWindowView extends FrameLayout { boolean handled = false; if (mService.isDozing()) { mDoubleTapHelper.onTouchEvent(ev); - handled = true; + handled = !mService.isPulsing(); } if ((mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !handled) || mDragDownHelper.isDraggingDown()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java index 6193159ec0a5..0c63e2969e01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.policy; import static com.android.systemui.Dependency.MAIN_HANDLER_NAME; +import android.app.RemoteInput; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; @@ -42,14 +43,18 @@ public final class SmartReplyConstants extends ContentObserver { private static final String KEY_REQUIRES_TARGETING_P = "requires_targeting_p"; private static final String KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS = "max_squeeze_remeasure_attempts"; + private static final String KEY_EDIT_CHOICES_BEFORE_SENDING = + "edit_choices_before_sending"; private final boolean mDefaultEnabled; private final boolean mDefaultRequiresP; private final int mDefaultMaxSqueezeRemeasureAttempts; + private final boolean mDefaultEditChoicesBeforeSending; private boolean mEnabled; private boolean mRequiresTargetingP; private int mMaxSqueezeRemeasureAttempts; + private boolean mEditChoicesBeforeSending; private final Context mContext; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -66,6 +71,8 @@ public final class SmartReplyConstants extends ContentObserver { R.bool.config_smart_replies_in_notifications_requires_targeting_p); mDefaultMaxSqueezeRemeasureAttempts = resources.getInteger( R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts); + mDefaultEditChoicesBeforeSending = resources.getBoolean( + R.bool.config_smart_replies_in_notifications_edit_choices_before_sending); mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS), @@ -90,6 +97,8 @@ public final class SmartReplyConstants extends ContentObserver { mRequiresTargetingP = mParser.getBoolean(KEY_REQUIRES_TARGETING_P, mDefaultRequiresP); mMaxSqueezeRemeasureAttempts = mParser.getInt( KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts); + mEditChoicesBeforeSending = mParser.getBoolean( + KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending); } } @@ -113,4 +122,24 @@ public final class SmartReplyConstants extends ContentObserver { public int getMaxSqueezeRemeasureAttempts() { return mMaxSqueezeRemeasureAttempts; } + + /** + * Returns whether by tapping on a choice should let the user edit the input before it + * is sent to the app. + * + * @param remoteInputEditChoicesBeforeSending The value from + * {@link RemoteInput#getEditChoicesBeforeSending()} + */ + public boolean getEffectiveEditChoicesBeforeSending( + @RemoteInput.EditChoicesBeforeSending int remoteInputEditChoicesBeforeSending) { + switch (remoteInputEditChoicesBeforeSending) { + case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED: + return false; + case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED: + return true; + case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO: + default: + return mEditChoicesBeforeSending; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 68b172d397c4..a76cf16bc0a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -242,9 +242,8 @@ public class SmartReplyView extends ViewGroup { b.setText(choice); OnDismissAction action = () -> { - // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags. - if (smartReplies.remoteInput.getEditChoicesBeforeSending() - == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) { + if (mConstants.getEffectiveEditChoicesBeforeSending( + smartReplies.remoteInput.getEditChoicesBeforeSending())) { entry.remoteInputText = choice; mRemoteInputManager.activateRemoteInput(b, new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 1844df5c070a..8e02f578f6fd 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -21,7 +21,6 @@ import static android.view.View.VISIBLE; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -42,19 +41,18 @@ import android.widget.TextClock; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ClockPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.function.Consumer; + @SmallTest @RunWith(AndroidTestingRunner.class) // Need to run on the main thread because KeyguardSliceView$Row init checks for @@ -62,7 +60,6 @@ import org.mockito.MockitoAnnotations; // the keyguard_clcok_switch layout is inflated. @RunWithLooper(setAsMainLooper = true) public class KeyguardClockSwitchTest extends SysuiTestCase { - private PluginManager mPluginManager; private FrameLayout mClockContainer; private StatusBarStateController.StateListener mStateListener; @@ -73,7 +70,6 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Before public void setUp() { - mPluginManager = mDependency.injectMockDependency(PluginManager.class); LayoutInflater layoutInflater = LayoutInflater.from(getContext()); mKeyguardClockSwitch = (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null); @@ -84,29 +80,12 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { } @Test - public void onAttachToWindow_addPluginListener() { - mKeyguardClockSwitch.onAttachedToWindow(); - - ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class); - verify(mPluginManager).addPluginListener(listener.capture(), eq(ClockPlugin.class)); - } - - @Test - public void onDetachToWindow_removePluginListener() { - mKeyguardClockSwitch.onDetachedFromWindow(); - - ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class); - verify(mPluginManager).removePluginListener(listener.capture()); - } - - @Test public void onPluginConnected_showPluginClock() { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); verify(mClockView).setVisibility(GONE); assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer); @@ -122,9 +101,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getBigClockView()).thenReturn(pluginView); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); // WHEN the plugin is connected - listener.onPluginConnected(plugin, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); // THEN the big clock container is visible and it is the parent of the // big clock view. assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE); @@ -134,8 +112,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Test public void onPluginConnected_nullView() { ClockPlugin plugin = mock(ClockPlugin.class); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); verify(mClockView, never()).setVisibility(GONE); } @@ -144,12 +121,11 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { // GIVEN a plugin has already connected ClockPlugin plugin1 = mock(ClockPlugin.class); when(plugin1.getView()).thenReturn(new TextClock(getContext())); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin1, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1); // WHEN a second plugin is connected ClockPlugin plugin2 = mock(ClockPlugin.class); when(plugin2.getView()).thenReturn(new TextClock(getContext())); - listener.onPluginConnected(plugin2, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin2); // THEN only the view from the second plugin should be a child of KeyguardClockSwitch. assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer); assertThat(plugin1.getView().getParent()).isNull(); @@ -161,10 +137,9 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); mClockView.setVisibility(GONE); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); - listener.onPluginDisconnected(plugin); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockPluginConsumer().accept(null); verify(mClockView).setVisibility(VISIBLE); assertThat(plugin.getView().getParent()).isNull(); @@ -180,10 +155,9 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getBigClockView()).thenReturn(pluginView); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); - // WHEN the plugin is disconnected - listener.onPluginDisconnected(plugin); + // WHEN the plugin is connected and then disconnected + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockPluginConsumer().accept(null); // THEN the big lock container is GONE and the big clock view doesn't have // a parent. assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE); @@ -193,41 +167,23 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Test public void onPluginDisconnected_nullView() { ClockPlugin plugin = mock(ClockPlugin.class); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); - listener.onPluginDisconnected(plugin); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockPluginConsumer().accept(null); verify(mClockView, never()).setVisibility(GONE); } @Test - public void onPluginDisconnected_firstOfTwoDisconnected() { - // GIVEN two plugins are connected - ClockPlugin plugin1 = mock(ClockPlugin.class); - when(plugin1.getView()).thenReturn(new TextClock(getContext())); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin1, null); - ClockPlugin plugin2 = mock(ClockPlugin.class); - when(plugin2.getView()).thenReturn(new TextClock(getContext())); - listener.onPluginConnected(plugin2, null); - // WHEN the first plugin is disconnected - listener.onPluginDisconnected(plugin1); - // THEN the view from the second plugin is still a child of KeyguardClockSwitch. - assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer); - assertThat(plugin1.getView().getParent()).isNull(); - } - - @Test public void onPluginDisconnected_secondOfTwoDisconnected() { // GIVEN two plugins are connected ClockPlugin plugin1 = mock(ClockPlugin.class); when(plugin1.getView()).thenReturn(new TextClock(getContext())); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin1, null); + Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer(); + consumer.accept(plugin1); ClockPlugin plugin2 = mock(ClockPlugin.class); when(plugin2.getView()).thenReturn(new TextClock(getContext())); - listener.onPluginConnected(plugin2, null); + consumer.accept(plugin2); // WHEN the second plugin is disconnected - listener.onPluginDisconnected(plugin2); + consumer.accept(null); // THEN the default clock should be shown. verify(mClockView).setVisibility(VISIBLE); assertThat(plugin1.getView().getParent()).isNull(); @@ -246,8 +202,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); mKeyguardClockSwitch.setTextColor(Color.WHITE); @@ -271,8 +226,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); Style style = mock(Style.class); - PluginListener listener = mKeyguardClockSwitch.getClockPluginListener(); - listener.onPluginConnected(plugin, null); + mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); mKeyguardClockSwitch.setStyle(style); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 146c5d647198..cc5f50a08a58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -54,7 +54,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.function.Consumer; @@ -578,7 +578,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testEatsTouchEvent() { HashSet<ScrimState> eatsTouches = - new HashSet<>(Arrays.asList(ScrimState.AOD, ScrimState.PULSING)); + new HashSet<>(Collections.singletonList(ScrimState.AOD)); for (ScrimState state : ScrimState.values()) { if (state == ScrimState.UNINITIALIZED) { continue; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java index 2266b479e7ec..37a56a3cbded 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java @@ -20,6 +20,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import android.app.RemoteInput; import android.os.Handler; import android.os.Looper; import android.provider.Settings; @@ -51,6 +52,8 @@ public class SmartReplyConstantsTest extends SysuiTestCase { resources.addOverride(R.bool.config_smart_replies_in_notifications_enabled, true); resources.addOverride( R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts, 7); + resources.addOverride( + R.bool.config_smart_replies_in_notifications_edit_choices_before_sending, false); mConstants = new SmartReplyConstants(Handler.createAsync(Looper.myLooper()), mContext); } @@ -104,6 +107,51 @@ public class SmartReplyConstantsTest extends SysuiTestCase { assertEquals(5, mConstants.getMaxSqueezeRemeasureAttempts()); } + @Test + public void testGetEffectiveEditChoicesBeforeSendingWithNoConfig() { + overrideSetting("enabled=true"); + triggerConstantsOnChange(); + assertFalse( + mConstants.getEffectiveEditChoicesBeforeSending( + RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO)); + assertTrue( + mConstants.getEffectiveEditChoicesBeforeSending( + RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED)); + assertFalse( + mConstants.getEffectiveEditChoicesBeforeSending( + RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED)); + } + + @Test + public void testGetEffectiveEditChoicesBeforeSendingWithEnabledConfig() { + overrideSetting("enabled=true,edit_choices_before_sending=true"); + triggerConstantsOnChange(); + assertTrue( + mConstants.getEffectiveEditChoicesBeforeSending( + RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO)); + assertTrue( + mConstants.getEffectiveEditChoicesBeforeSending( + RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED)); + assertFalse( + mConstants.getEffectiveEditChoicesBeforeSending( + RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED)); + } + + @Test + public void testGetEffectiveEditChoicesBeforeSendingWithDisabledConfig() { + overrideSetting("enabled=true,edit_choices_before_sending=false"); + triggerConstantsOnChange(); + assertFalse( + mConstants.getEffectiveEditChoicesBeforeSending( + RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO)); + assertTrue( + mConstants.getEffectiveEditChoicesBeforeSending( + RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED)); + assertFalse( + mConstants.getEffectiveEditChoicesBeforeSending( + RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED)); + } + private void overrideSetting(String flags) { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, flags); diff --git a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp index a40cf1c06d07..d969c69cfaf1 100644 --- a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp +++ b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp @@ -16,6 +16,9 @@ #define LOG_TAG "PacProcessor" +#include <stdlib.h> +#include <string> + #include <utils/Log.h> #include <utils/Mutex.h> #include "android_runtime/AndroidRuntime.h" @@ -23,24 +26,24 @@ #include "jni.h" #include <nativehelper/JNIHelp.h> -#include "proxy_resolver_v8.h" +#include "proxy_resolver_v8_wrapper.h" namespace android { -net::ProxyResolverV8* proxyResolver = NULL; +ProxyResolverV8Handle* proxyResolver = NULL; bool pacSet = false; -String16 jstringToString16(JNIEnv* env, jstring jstr) { +std::u16string jstringToString16(JNIEnv* env, jstring jstr) { const jchar* str = env->GetStringCritical(jstr, 0); - String16 str16(reinterpret_cast<const char16_t*>(str), + std::u16string str16(reinterpret_cast<const char16_t*>(str), env->GetStringLength(jstr)); env->ReleaseStringCritical(jstr, str); return str16; } -jstring string16ToJstring(JNIEnv* env, String16 string) { - const char16_t* str = string.string(); - size_t len = string.size(); +jstring string16ToJstring(JNIEnv* env, std::u16string string) { + const char16_t* str = string.data(); + size_t len = string.length(); return env->NewString(reinterpret_cast<const jchar*>(str), len); } @@ -48,7 +51,7 @@ jstring string16ToJstring(JNIEnv* env, String16 string) { static jboolean com_android_pacprocessor_PacNative_createV8ParserNativeLocked(JNIEnv* /* env */, jobject) { if (proxyResolver == NULL) { - proxyResolver = new net::ProxyResolverV8(net::ProxyResolverJSBindings::CreateDefault()); + proxyResolver = ProxyResolverV8Handle_new(); pacSet = false; return JNI_FALSE; } @@ -58,7 +61,7 @@ static jboolean com_android_pacprocessor_PacNative_createV8ParserNativeLocked(JN static jboolean com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked(JNIEnv* /* env */, jobject) { if (proxyResolver != NULL) { - delete proxyResolver; + ProxyResolverV8Handle_delete(proxyResolver); proxyResolver = NULL; return JNI_FALSE; } @@ -67,14 +70,14 @@ static jboolean com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked(J static jboolean com_android_pacprocessor_PacNative_setProxyScriptNativeLocked(JNIEnv* env, jobject, jstring script) { - String16 script16 = jstringToString16(env, script); + std::u16string script16 = jstringToString16(env, script); if (proxyResolver == NULL) { ALOGE("V8 Parser not started when setting PAC script"); return JNI_TRUE; } - if (proxyResolver->SetPacScript(script16) != OK) { + if (ProxyResolverV8Handle_SetPacScript(proxyResolver, script16.data()) != OK) { ALOGE("Unable to set PAC script"); return JNI_TRUE; } @@ -85,9 +88,8 @@ static jboolean com_android_pacprocessor_PacNative_setProxyScriptNativeLocked(JN static jstring com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked(JNIEnv* env, jobject, jstring url, jstring host) { - String16 url16 = jstringToString16(env, url); - String16 host16 = jstringToString16(env, host); - String16 ret; + std::u16string url16 = jstringToString16(env, url); + std::u16string host16 = jstringToString16(env, host); if (proxyResolver == NULL) { ALOGE("V8 Parser not initialized when running PAC script"); @@ -99,12 +101,14 @@ static jstring com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked(J return NULL; } - if (proxyResolver->GetProxyForURL(url16, host16, &ret) != OK) { - String8 ret8(ret); - ALOGE("Error Running PAC: %s", ret8.string()); + std::unique_ptr<char16_t, decltype(&free)> result = std::unique_ptr<char16_t, decltype(&free)>( + ProxyResolverV8Handle_GetProxyForURL(proxyResolver, url16.data(), host16.data()), &free); + if (result.get() == NULL) { + ALOGE("Error Running PAC"); return NULL; } + std::u16string ret(result.get()); jstring jret = string16ToJstring(env, ret); return jret; diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index d7a23650d811..6b0adfb44906 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -850,19 +850,29 @@ public class UserBackupManagerService { mFullBackupQueue = readFullBackupSchedule(); } - // Register for broadcasts about package install, etc., so we can - // update the provider list. + // Register for broadcasts about package changes. IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); - mContext.registerReceiver(mBroadcastReceiver, filter); + mContext.registerReceiverAsUser( + mBroadcastReceiver, + UserHandle.of(mUserId), + filter, + /* broadcastPermission */ null, + /* scheduler */ null); + // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(mBroadcastReceiver, sdFilter); + mContext.registerReceiverAsUser( + mBroadcastReceiver, + UserHandle.of(mUserId), + sdFilter, + /* broadcastPermission */ null, + /* scheduler */ null); } private ArrayList<FullBackupEntry> readFullBackupSchedule() { @@ -1127,17 +1137,23 @@ public class UserBackupManagerService { } } - // ----- Track installation/removal of packages ----- + /** + * A {@link BroadcastReceiver} tracking changes to packages and sd cards in order to update our + * internal bookkeeping. + */ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { - if (MORE_DEBUG) Slog.d(TAG, "Received broadcast " + intent); + if (MORE_DEBUG) { + Slog.d(TAG, "Received broadcast " + intent); + } String action = intent.getAction(); boolean replacing = false; boolean added = false; boolean changed = false; Bundle extras = intent.getExtras(); - String[] pkgList = null; + String[] packageList = null; + if (Intent.ACTION_PACKAGE_ADDED.equals(action) || Intent.ACTION_PACKAGE_REMOVED.equals(action) || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { @@ -1145,69 +1161,70 @@ public class UserBackupManagerService { if (uri == null) { return; } - final String pkgName = uri.getSchemeSpecificPart(); - if (pkgName != null) { - pkgList = new String[]{pkgName}; + + String packageName = uri.getSchemeSpecificPart(); + if (packageName != null) { + packageList = new String[]{packageName}; } - changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); - // At package-changed we only care about looking at new transport states + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); if (changed) { - final String[] components = + // Look at new transport states for package changed events. + String[] components = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); if (MORE_DEBUG) { - Slog.i(TAG, "Package " + pkgName + " changed; rechecking"); + Slog.i(TAG, "Package " + packageName + " changed"); for (int i = 0; i < components.length; i++) { Slog.i(TAG, " * " + components[i]); } } mBackupHandler.post( - () -> mTransportManager.onPackageChanged(pkgName, components)); - return; // nothing more to do in the PACKAGE_CHANGED case + () -> mTransportManager.onPackageChanged(packageName, components)); + return; } added = Intent.ACTION_PACKAGE_ADDED.equals(action); replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { added = true; - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { added = false; - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + packageList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); } - if (pkgList == null || pkgList.length == 0) { + if (packageList == null || packageList.length == 0) { return; } - final int uid = extras.getInt(Intent.EXTRA_UID); + int uid = extras.getInt(Intent.EXTRA_UID); if (added) { synchronized (mBackupParticipants) { if (replacing) { - // This is the package-replaced case; we just remove the entry - // under the old uid and fall through to re-add. If an app - // just added key/value backup participation, this picks it up - // as a known participant. - removePackageParticipantsLocked(pkgList, uid); + // Remove the entry under the old uid and fall through to re-add. If an app + // just opted into key/value backup, add it as a known participant. + removePackageParticipantsLocked(packageList, uid); } - addPackageParticipantsLocked(pkgList); + addPackageParticipantsLocked(packageList); } - // If they're full-backup candidates, add them there instead - final long now = System.currentTimeMillis(); - for (final String packageName : pkgList) { + + long now = System.currentTimeMillis(); + for (String packageName : packageList) { try { - PackageInfo app = mPackageManager.getPackageInfo(packageName, 0); + PackageInfo app = + mPackageManager.getPackageInfoAsUser( + packageName, /* flags */ 0, mUserId); if (AppBackupUtils.appGetsFullBackup(app) && AppBackupUtils.appIsEligibleForBackup( app.applicationInfo, mPackageManager)) { enqueueFullBackup(packageName, now); scheduleNextFullBackupJob(0); } else { - // The app might have just transitioned out of full-data into - // doing key/value backups, or might have just disabled backups - // entirely. Make sure it is no longer in the full-data queue. + // The app might have just transitioned out of full-data into doing + // key/value backups, or might have just disabled backups entirely. Make + // sure it is no longer in the full-data queue. synchronized (mQueueLock) { dequeueFullBackupLocked(packageName); } @@ -1216,32 +1233,28 @@ public class UserBackupManagerService { mBackupHandler.post( () -> mTransportManager.onPackageAdded(packageName)); - } catch (NameNotFoundException e) { - // doesn't really exist; ignore it if (DEBUG) { Slog.w(TAG, "Can't resolve new app " + packageName); } } } - // Whenever a package is added or updated we need to update - // the package metadata bookkeeping. + // Whenever a package is added or updated we need to update the package metadata + // bookkeeping. dataChangedImpl(PACKAGE_MANAGER_SENTINEL); } else { - if (replacing) { - // The package is being updated. We'll receive a PACKAGE_ADDED shortly. - } else { - // Outright removal. In the full-data case, the app will be dropped - // from the queue when its (now obsolete) name comes up again for - // backup. + if (!replacing) { + // Outright removal. In the full-data case, the app will be dropped from the + // queue when its (now obsolete) name comes up again for backup. synchronized (mBackupParticipants) { - removePackageParticipantsLocked(pkgList, uid); + removePackageParticipantsLocked(packageList, uid); } } - for (final String pkgName : pkgList) { + + for (String packageName : packageList) { mBackupHandler.post( - () -> mTransportManager.onPackageRemoved(pkgName)); + () -> mTransportManager.onPackageRemoved(packageName)); } } } diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index 3cdf09e967fe..466fb4e71496 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -3391,6 +3391,8 @@ public class AppOpsService extends IAppOpsService.Stub { pw.println(" Limit output to data associated with the given app op mode."); pw.println(" --package [PACKAGE]"); pw.println(" Limit output to data associated with the given package name."); + pw.println(" --watchers"); + pw.println(" Only output the watcher sections."); } private void dumpTimesLocked(PrintWriter pw, String firstPrefix, String prefix, long[] times, @@ -3429,6 +3431,7 @@ public class AppOpsService extends IAppOpsService.Stub { String dumpPackage = null; int dumpUid = -1; int dumpMode = -1; + boolean dumpWatchers = false; if (args != null) { for (int i=0; i<args.length; i++) { @@ -3476,6 +3479,8 @@ public class AppOpsService extends IAppOpsService.Stub { if (dumpMode < 0) { return; } + } else if ("--watchers".equals(arg)) { + dumpWatchers = true; } else if (arg.length() > 0 && arg.charAt(0) == '-'){ pw.println("Unknown option: " + arg); return; @@ -3496,7 +3501,8 @@ public class AppOpsService extends IAppOpsService.Stub { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); final Date date = new Date(); boolean needSep = false; - if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null) { + if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null + && !dumpWatchers) { pw.println(" Profile owners:"); for (int poi = 0; poi < mProfileOwners.size(); poi++) { pw.print(" User #"); @@ -3517,7 +3523,7 @@ public class AppOpsService extends IAppOpsService.Stub { ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i); for (int j=0; j<callbacks.size(); j++) { final ModeCallback cb = callbacks.valueAt(j); - if (dumpPackage != null && cb.mWatchingUid >= 0 + if (dumpPackage != null && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { continue; } @@ -3561,7 +3567,7 @@ public class AppOpsService extends IAppOpsService.Stub { boolean printedHeader = false; for (int i=0; i<mModeWatchers.size(); i++) { final ModeCallback cb = mModeWatchers.valueAt(i); - if (dumpPackage != null && cb.mWatchingUid >= 0 + if (dumpPackage != null && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { continue; } @@ -3587,7 +3593,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) { continue; } - if (dumpPackage != null && cb.mWatchingUid >= 0 + if (dumpPackage != null && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { continue; } @@ -3627,7 +3633,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) { continue; } - if (dumpPackage != null && cb.mWatchingUid >= 0 + if (dumpPackage != null && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { continue; } @@ -3655,7 +3661,7 @@ public class AppOpsService extends IAppOpsService.Stub { pw.println(cb); } } - if (mClients.size() > 0 && dumpMode < 0) { + if (mClients.size() > 0 && dumpMode < 0 && !dumpWatchers) { needSep = true; boolean printedHeader = false; for (int i=0; i<mClients.size(); i++) { @@ -3692,7 +3698,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } if (mAudioRestrictions.size() > 0 && dumpOp < 0 && dumpPackage != null - && dumpMode < 0) { + && dumpMode < 0 && !dumpWatchers) { boolean printedHeader = false; for (int o=0; o<mAudioRestrictions.size(); o++) { final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o)); @@ -3725,6 +3731,9 @@ public class AppOpsService extends IAppOpsService.Stub { final SparseIntArray opModes = uidState.opModes; final ArrayMap<String, Ops> pkgOps = uidState.pkgOps; + if (dumpWatchers) { + continue; + } if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) { boolean hasOp = dumpOp < 0 || (uidState.opModes != null && uidState.opModes.indexOfKey(dumpOp) >= 0); @@ -3880,18 +3889,34 @@ public class AppOpsService extends IAppOpsService.Stub { for (int i = 0; i < userRestrictionCount; i++) { IBinder token = mOpUserRestrictions.keyAt(i); ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i); - pw.println(" User restrictions for token " + token + ":"); + boolean printedTokenHeader = false; + + if (dumpMode >= 0 || dumpWatchers) { + continue; + } final int restrictionCount = restrictionState.perUserRestrictions != null ? restrictionState.perUserRestrictions.size() : 0; - if (restrictionCount > 0) { - pw.println(" Restricted ops:"); + if (restrictionCount > 0 && dumpPackage == null) { + boolean printedOpsHeader = false; for (int j = 0; j < restrictionCount; j++) { int userId = restrictionState.perUserRestrictions.keyAt(j); boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j); if (restrictedOps == null) { continue; } + if (dumpOp >= 0 && (dumpOp >= restrictedOps.length + || !restrictedOps[dumpOp])) { + continue; + } + if (!printedTokenHeader) { + pw.println(" User restrictions for token " + token + ":"); + printedTokenHeader = true; + } + if (!printedOpsHeader) { + pw.println(" Restricted ops:"); + printedOpsHeader = true; + } StringBuilder restrictedOpsValue = new StringBuilder(); restrictedOpsValue.append("["); final int restrictedOpCount = restrictedOps.length; @@ -3911,11 +3936,37 @@ public class AppOpsService extends IAppOpsService.Stub { final int excludedPackageCount = restrictionState.perUserExcludedPackages != null ? restrictionState.perUserExcludedPackages.size() : 0; - if (excludedPackageCount > 0) { - pw.println(" Excluded packages:"); + if (excludedPackageCount > 0 && dumpOp < 0) { + boolean printedPackagesHeader = false; for (int j = 0; j < excludedPackageCount; j++) { int userId = restrictionState.perUserExcludedPackages.keyAt(j); String[] packageNames = restrictionState.perUserExcludedPackages.valueAt(j); + if (packageNames == null) { + continue; + } + boolean hasPackage; + if (dumpPackage != null) { + hasPackage = false; + for (String pkg : packageNames) { + if (dumpPackage.equals(pkg)) { + hasPackage = true; + break; + } + } + } else { + hasPackage = true; + } + if (!hasPackage) { + continue; + } + if (!printedTokenHeader) { + pw.println(" User restrictions for token " + token + ":"); + printedTokenHeader = true; + } + if (!printedPackagesHeader) { + pw.println(" Excluded packages:"); + printedPackagesHeader = true; + } pw.print(" "); pw.print("user: "); pw.print(userId); pw.print(" packages: "); pw.println(Arrays.toString(packageNames)); } diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 2346cfc07a83..869d564b4329 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -17,9 +17,14 @@ package com.android.server; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.location.LocationManager.FUSED_PROVIDER; +import static android.location.LocationManager.GPS_PROVIDER; +import static android.location.LocationManager.NETWORK_PROVIDER; +import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.location.LocationProvider.AVAILABLE; import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS; +import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkState; import android.Manifest; @@ -29,7 +34,6 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -64,7 +68,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -81,12 +84,14 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.Preconditions; import com.android.server.location.AbstractLocationProvider; import com.android.server.location.ActivityRecognitionProxy; import com.android.server.location.GeocoderProxy; @@ -116,7 +121,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; -import java.util.Set; /** * The service class that manages LocationProviders and issues location @@ -137,16 +141,12 @@ public class LocationManagerService extends ILocationManager.Stub { private static final String ACCESS_LOCATION_EXTRA_COMMANDS = android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS; - private static final String INSTALL_LOCATION_PROVIDER = - android.Manifest.permission.INSTALL_LOCATION_PROVIDER; private static final String NETWORK_LOCATION_SERVICE_ACTION = "com.android.location.service.v3.NetworkLocationProvider"; private static final String FUSED_LOCATION_SERVICE_ACTION = "com.android.location.service.FusedLocationProvider"; - private static final int MSG_LOCATION_CHANGED = 1; - private static final long NANOS_PER_MILLI = 1000000L; // The maximum interval a location request can have and still be considered "high power". @@ -170,75 +170,62 @@ public class LocationManagerService extends ILocationManager.Stub { private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest(); - private final Context mContext; - private final AppOpsManager mAppOps; - - // used internally for synchronization private final Object mLock = new Object(); + private final Context mContext; + private final Handler mHandler; - // --- fields below are final after systemRunning() --- - private LocationFudger mLocationFudger; - private GeofenceManager mGeofenceManager; + private AppOpsManager mAppOps; private PackageManager mPackageManager; private PowerManager mPowerManager; private ActivityManager mActivityManager; private UserManager mUserManager; + + private GeofenceManager mGeofenceManager; + private LocationFudger mLocationFudger; private GeocoderProxy mGeocodeProvider; private GnssStatusListenerHelper mGnssStatusProvider; private INetInitiatedListener mNetInitiatedListener; - private LocationWorkerHandler mLocationHandler; private PassiveProvider mPassiveProvider; // track passive provider for special cases private LocationBlacklist mBlacklist; private GnssMeasurementsProvider mGnssMeasurementsProvider; private GnssNavigationMessageProvider mGnssNavigationMessageProvider; + @GuardedBy("mLock") private String mLocationControllerExtraPackage; private boolean mLocationControllerExtraPackageEnabled; private IGpsGeofenceHardware mGpsGeofenceProxy; - // --- fields below are protected by mLock --- + // list of currently active providers + @GuardedBy("mLock") + private final ArrayList<LocationProvider> mProviders = new ArrayList<>(); - // Mock (test) providers - private final HashMap<String, MockProvider> mMockProviders = - new HashMap<>(); + // list of non-mock providers, so that when mock providers replace real providers, they can be + // later re-replaced + @GuardedBy("mLock") + private final ArrayList<LocationProvider> mRealProviders = new ArrayList<>(); - // all receivers + @GuardedBy("mLock") private final HashMap<Object, Receiver> mReceivers = new HashMap<>(); - - // currently installed providers (with mocks replacing real providers) - private final ArrayList<LocationProvider> mProviders = - new ArrayList<>(); - - // real providers, saved here when mocked out - private final HashMap<String, LocationProvider> mRealProviders = - new HashMap<>(); - - // mapping from provider name to provider - private final HashMap<String, LocationProvider> mProvidersByName = - new HashMap<>(); - - // mapping from provider name to all its UpdateRecords private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider = new HashMap<>(); private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics(); // mapping from provider name to last known location + @GuardedBy("mLock") private final HashMap<String, Location> mLastLocation = new HashMap<>(); // same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS. // locations stored here are not fudged for coarse permissions. + @GuardedBy("mLock") private final HashMap<String, Location> mLastLocationCoarseInterval = new HashMap<>(); - // all providers that operate over proxy, for authorizing incoming location and whitelisting - // throttling - private final ArrayList<LocationProviderProxy> mProxyProviders = - new ArrayList<>(); - private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>(); + @GuardedBy("mLock") private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>(); + @GuardedBy("mLock") private final ArrayMap<IBinder, Identity> mGnssNavigationMessageListeners = new ArrayMap<>(); @@ -246,22 +233,22 @@ public class LocationManagerService extends ILocationManager.Stub { private int mCurrentUserId = UserHandle.USER_SYSTEM; private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM}; - // Maximum age of last location returned to clients with foreground-only location permissions. - private long mLastLocationMaxAgeMs; - private GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider; private GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider; private GnssBatchingProvider mGnssBatchingProvider; + @GuardedBy("mLock") private IBatchedLocationCallback mGnssBatchingCallback; + @GuardedBy("mLock") private LinkedCallback mGnssBatchingDeathCallback; + @GuardedBy("mLock") private boolean mGnssBatchingInProgress = false; public LocationManagerService(Context context) { super(); mContext = context; - mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + mHandler = BackgroundThread.getHandler(); // Let the package manager query which are the default location // providers as they get certain permissions granted by default. @@ -271,134 +258,110 @@ public class LocationManagerService extends ILocationManager.Stub { userId -> mContext.getResources().getStringArray( com.android.internal.R.array.config_locationProviderPackageNames)); - if (D) Log.d(TAG, "Constructed"); - // most startup is deferred until systemRunning() } public void systemRunning() { synchronized (mLock) { - if (D) Log.d(TAG, "systemRunning()"); - - // fetch package manager - mPackageManager = mContext.getPackageManager(); - - // fetch power manager - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - - // fetch activity manager - mActivityManager - = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); - - // prepare worker thread - mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper()); - - // prepare mLocationHandler's dependents - mLocationFudger = new LocationFudger(mContext, mLocationHandler); - mBlacklist = new LocationBlacklist(mContext, mLocationHandler); - mBlacklist.init(); - mGeofenceManager = new GeofenceManager(mContext, mBlacklist); - - // Monitor for app ops mode changes. - AppOpsManager.OnOpChangedListener callback - = new AppOpsManager.OnOpChangedInternalListener() { - public void onOpChanged(int op, String packageName) { - mLocationHandler.post(() -> { - synchronized (mLock) { - for (Receiver receiver : mReceivers.values()) { - receiver.updateMonitoring(true); - } - applyAllProviderRequirementsLocked(); - } - }); - } - }; - mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null, - AppOpsManager.WATCH_FOREGROUND_CHANGES, callback); - - PackageManager.OnPermissionsChangedListener permissionListener = uid -> { - synchronized (mLock) { - applyAllProviderRequirementsLocked(); - } - }; - mPackageManager.addOnPermissionsChangeListener(permissionListener); + initializeLocked(); + } + } - // listen for background/foreground changes - ActivityManager.OnUidImportanceListener uidImportanceListener = - (uid, importance) -> mLocationHandler.post( - () -> onUidImportanceChanged(uid, importance)); - mActivityManager.addOnUidImportanceListener(uidImportanceListener, - FOREGROUND_IMPORTANCE_CUTOFF); + @GuardedBy("mLock") + private void initializeLocked() { + mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mPackageManager = mContext.getPackageManager(); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - updateUserProfiles(mCurrentUserId); + mLocationFudger = new LocationFudger(mContext, mHandler); + mBlacklist = new LocationBlacklist(mContext, mHandler); + mBlacklist.init(); + mGeofenceManager = new GeofenceManager(mContext, mBlacklist); - updateBackgroundThrottlingWhitelistLocked(); - updateLastLocationMaxAgeLocked(); + // prepare providers + initializeProvidersLocked(); - // prepare providers - loadProvidersLocked(); - updateProvidersSettingsLocked(); - for (LocationProvider provider : mProviders) { - applyRequirementsLocked(provider.getName()); - } - } + // add listeners + mAppOps.startWatchingMode( + AppOpsManager.OP_COARSE_LOCATION, + null, + AppOpsManager.WATCH_FOREGROUND_CHANGES, + new AppOpsManager.OnOpChangedInternalListener() { + public void onOpChanged(int op, String packageName) { + synchronized (mLock) { + onAppOpChangedLocked(); + } + } + }); + mPackageManager.addOnPermissionsChangeListener( + uid -> { + synchronized (mLock) { + onPermissionsChangedLocked(); + } + }); - // listen for settings changes + mActivityManager.addOnUidImportanceListener( + (uid, importance) -> { + synchronized (mLock) { + onUidImportanceChangedLocked(uid, importance); + } + }, + FOREGROUND_IMPORTANCE_CUTOFF); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true, - new ContentObserver(mLocationHandler) { + Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE), true, + new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - updateProvidersSettingsLocked(); + onLocationModeChangedLocked(true); } } }, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS), - true, - new ContentObserver(mLocationHandler) { + Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true, + new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - for (LocationProvider provider : mProviders) { - applyRequirementsLocked(provider.getName()); - } + onProviderAllowedChangedLocked(true); } } }, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS), + Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS), true, - new ContentObserver(mLocationHandler) { + new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - updateLastLocationMaxAgeLocked(); + onBackgroundThrottleIntervalChangedLocked(); } } - } - ); + }, UserHandle.USER_ALL); mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor( Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST), true, - new ContentObserver(mLocationHandler) { + new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { synchronized (mLock) { - updateBackgroundThrottlingWhitelistLocked(); - for (LocationProvider provider : mProviders) { - applyRequirementsLocked(provider.getName()); - } + onBackgroundThrottleWhitelistChangedLocked(); } } }, UserHandle.USER_ALL); - mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true); + new PackageMonitor() { + @Override + public void onPackageDisappeared(String packageName, int reason) { + synchronized (mLock) { + LocationManagerService.this.onPackageDisappearedLocked(packageName); + } + } + }.register(mContext, mHandler.getLooper(), true); - // listen for user change IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_SWITCHED); intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); @@ -407,81 +370,152 @@ public class LocationManagerService extends ILocationManager.Stub { mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); - } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action) - || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { - updateUserProfiles(mCurrentUserId); + synchronized (mLock) { + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + onUserChangedLocked(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action) + || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { + onUserProfilesChangedLocked(); + } } } - }, UserHandle.ALL, intentFilter, null, mLocationHandler); + }, UserHandle.ALL, intentFilter, null, mHandler); + + // switching the user from null to system here performs the bulk of the initialization work. + // the user being changed will cause a reload of all user specific settings, which causes + // provider initialization, and propagates changes until a steady state is reached + mCurrentUserId = UserHandle.USER_NULL; + onUserChangedLocked(UserHandle.USER_SYSTEM); + + // initialize in-memory settings values + onBackgroundThrottleWhitelistChangedLocked(); } - private void onUidImportanceChanged(int uid, int importance) { - boolean foreground = isImportanceForeground(importance); - HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size()); - synchronized (mLock) { - for (Entry<String, ArrayList<UpdateRecord>> entry - : mRecordsByProvider.entrySet()) { - String provider = entry.getKey(); - for (UpdateRecord record : entry.getValue()) { - if (record.mReceiver.mIdentity.mUid == uid - && record.mIsForegroundUid != foreground) { - if (D) { - Log.d(TAG, "request from uid " + uid + " is now " - + (foreground ? "foreground" : "background)")); - } - record.updateForeground(foreground); + @GuardedBy("mLock") + private void onAppOpChangedLocked() { + for (Receiver receiver : mReceivers.values()) { + receiver.updateMonitoring(true); + } + for (LocationProvider p : mProviders) { + applyRequirementsLocked(p); + } + } - if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) { - affectedProviders.add(provider); - } - } + @GuardedBy("mLock") + private void onPermissionsChangedLocked() { + for (LocationProvider p : mProviders) { + applyRequirementsLocked(p); + } + } + + @GuardedBy("mLock") + private void onLocationModeChangedLocked(boolean broadcast) { + for (LocationProvider p : mProviders) { + p.onLocationModeChangedLocked(); + } + + if (broadcast) { + mContext.sendBroadcastAsUser( + new Intent(LocationManager.MODE_CHANGED_ACTION), + UserHandle.ALL); + } + } + + @GuardedBy("mLock") + private void onProviderAllowedChangedLocked(boolean broadcast) { + for (LocationProvider p : mProviders) { + p.onAllowedChangedLocked(); + } + + if (broadcast) { + mContext.sendBroadcastAsUser( + new Intent(LocationManager.PROVIDERS_CHANGED_ACTION), + UserHandle.ALL); + } + } + + @GuardedBy("mLock") + private void onPackageDisappearedLocked(String packageName) { + ArrayList<Receiver> deadReceivers = null; + + for (Receiver receiver : mReceivers.values()) { + if (receiver.mIdentity.mPackageName.equals(packageName)) { + if (deadReceivers == null) { + deadReceivers = new ArrayList<>(); } + deadReceivers.add(receiver); } - for (String provider : affectedProviders) { - applyRequirementsLocked(provider); + } + + // perform removal outside of mReceivers loop + if (deadReceivers != null) { + for (Receiver receiver : deadReceivers) { + removeUpdatesLocked(receiver); } + } + } - for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) { - Identity callerIdentity = entry.getValue(); - if (callerIdentity.mUid == uid) { + @GuardedBy("mLock") + private void onUidImportanceChangedLocked(int uid, int importance) { + boolean foreground = isImportanceForeground(importance); + HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size()); + for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) { + String provider = entry.getKey(); + for (UpdateRecord record : entry.getValue()) { + if (record.mReceiver.mIdentity.mUid == uid + && record.mIsForegroundUid != foreground) { if (D) { - Log.d(TAG, "gnss measurements listener from uid " + uid - + " is now " + (foreground ? "foreground" : "background)")); + Log.d(TAG, "request from uid " + uid + " is now " + + (foreground ? "foreground" : "background)")); } - if (foreground || isThrottlingExemptLocked(entry.getValue())) { - mGnssMeasurementsProvider.addListener( - IGnssMeasurementsListener.Stub.asInterface(entry.getKey()), - callerIdentity.mUid, callerIdentity.mPackageName); - } else { - mGnssMeasurementsProvider.removeListener( - IGnssMeasurementsListener.Stub.asInterface(entry.getKey())); + record.updateForeground(foreground); + + if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) { + affectedProviders.add(provider); } } } + } + for (String provider : affectedProviders) { + applyRequirementsLocked(provider); + } - for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) { - Identity callerIdentity = entry.getValue(); - if (callerIdentity.mUid == uid) { - if (D) { - Log.d(TAG, "gnss navigation message listener from uid " - + uid + " is now " - + (foreground ? "foreground" : "background)")); - } - if (foreground || isThrottlingExemptLocked(entry.getValue())) { - mGnssNavigationMessageProvider.addListener( - IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()), - callerIdentity.mUid, callerIdentity.mPackageName); - } else { - mGnssNavigationMessageProvider.removeListener( - IGnssNavigationMessageListener.Stub.asInterface(entry.getKey())); - } + for (Entry<IBinder, Identity> entry : mGnssMeasurementsListeners.entrySet()) { + Identity callerIdentity = entry.getValue(); + if (callerIdentity.mUid == uid) { + if (D) { + Log.d(TAG, "gnss measurements listener from uid " + uid + + " is now " + (foreground ? "foreground" : "background)")); + } + if (foreground || isThrottlingExemptLocked(entry.getValue())) { + mGnssMeasurementsProvider.addListener( + IGnssMeasurementsListener.Stub.asInterface(entry.getKey()), + callerIdentity.mUid, callerIdentity.mPackageName); + } else { + mGnssMeasurementsProvider.removeListener( + IGnssMeasurementsListener.Stub.asInterface(entry.getKey())); } } + } - // TODO(b/120449926): The GNSS status listeners should be handled similar to the above. + for (Entry<IBinder, Identity> entry : mGnssNavigationMessageListeners.entrySet()) { + Identity callerIdentity = entry.getValue(); + if (callerIdentity.mUid == uid) { + if (D) { + Log.d(TAG, "gnss navigation message listener from uid " + + uid + " is now " + + (foreground ? "foreground" : "background)")); + } + if (foreground || isThrottlingExemptLocked(entry.getValue())) { + mGnssNavigationMessageProvider.addListener( + IGnssNavigationMessageListener.Stub.asInterface(entry.getKey()), + callerIdentity.mUid, callerIdentity.mPackageName); + } else { + mGnssNavigationMessageProvider.removeListener( + IGnssNavigationMessageListener.Stub.asInterface(entry.getKey())); + } + } } } @@ -489,30 +523,43 @@ public class LocationManagerService extends ILocationManager.Stub { return importance <= FOREGROUND_IMPORTANCE_CUTOFF; } - /** - * Makes a list of userids that are related to the current user. This is - * relevant when using managed profiles. Otherwise the list only contains - * the current user. - * - * @param currentUserId the current user, who might have an alter-ego. - */ - private void updateUserProfiles(int currentUserId) { - int[] profileIds = mUserManager.getProfileIdsWithDisabled(currentUserId); - synchronized (mLock) { - mCurrentUserProfiles = profileIds; + @GuardedBy("mLock") + private void onBackgroundThrottleIntervalChangedLocked() { + for (LocationProvider provider : mProviders) { + applyRequirementsLocked(provider); } } - /** - * Checks if the specified userId matches any of the current foreground - * users stored in mCurrentUserProfiles. - */ - private boolean isCurrentProfile(int userId) { - synchronized (mLock) { - return ArrayUtils.contains(mCurrentUserProfiles, userId); + @GuardedBy("mLock") + private void onBackgroundThrottleWhitelistChangedLocked() { + String setting = Settings.Global.getString( + mContext.getContentResolver(), + Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST); + if (setting == null) { + setting = ""; + } + + mBackgroundThrottlePackageWhitelist.clear(); + mBackgroundThrottlePackageWhitelist.addAll( + SystemConfig.getInstance().getAllowUnthrottledLocation()); + mBackgroundThrottlePackageWhitelist.addAll(Arrays.asList(setting.split(","))); + + for (LocationProvider p : mProviders) { + applyRequirementsLocked(p); } } + @GuardedBy("mLock") + private void onUserProfilesChangedLocked() { + mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId); + } + + @GuardedBy("mLock") + private boolean isCurrentProfileLocked(int userId) { + return ArrayUtils.contains(mCurrentUserProfiles, userId); + } + + @GuardedBy("mLock") private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) { PackageManager pm = mContext.getPackageManager(); String systemPackageName = mContext.getPackageName(); @@ -583,30 +630,30 @@ public class LocationManagerService extends ILocationManager.Stub { + "partition. The fallback must also be marked coreApp=\"true\" in the manifest"); } - private void loadProvidersLocked() { + @GuardedBy("mLock") + private void initializeProvidersLocked() { // create a passive location provider, which is always enabled - LocationProvider passiveProviderManager = new LocationProvider( - LocationManager.PASSIVE_PROVIDER); - PassiveProvider passiveProvider = new PassiveProvider(passiveProviderManager); - + LocationProvider passiveProviderManager = new LocationProvider(PASSIVE_PROVIDER); addProviderLocked(passiveProviderManager); - mPassiveProvider = passiveProvider; + mPassiveProvider = new PassiveProvider(passiveProviderManager); + passiveProviderManager.attachLocked(mPassiveProvider); if (GnssLocationProvider.isSupported()) { // Create a gps location provider - LocationProvider gnssProviderManager = new LocationProvider( - LocationManager.GPS_PROVIDER); + LocationProvider gnssProviderManager = new LocationProvider(GPS_PROVIDER, true); + mRealProviders.add(gnssProviderManager); + addProviderLocked(gnssProviderManager); + GnssLocationProvider gnssProvider = new GnssLocationProvider(mContext, gnssProviderManager, - mLocationHandler.getLooper()); + mHandler.getLooper()); + gnssProviderManager.attachLocked(gnssProvider); mGnssSystemInfoProvider = gnssProvider.getGnssSystemInfoProvider(); mGnssBatchingProvider = gnssProvider.getGnssBatchingProvider(); mGnssMetricsProvider = gnssProvider.getGnssMetricsProvider(); mGnssStatusProvider = gnssProvider.getGnssStatusProvider(); mNetInitiatedListener = gnssProvider.getNetInitiatedListener(); - addProviderLocked(gnssProviderManager); - mRealProviders.put(LocationManager.GPS_PROVIDER, gnssProviderManager); mGnssMeasurementsProvider = gnssProvider.getGnssMeasurementsProvider(); mGnssNavigationMessageProvider = gnssProvider.getGnssNavigationMessageProvider(); mGpsGeofenceProxy = gnssProvider.getGpsGeofenceProxy(); @@ -634,9 +681,7 @@ public class LocationManagerService extends ILocationManager.Stub { ensureFallbackFusedProviderPresentLocked(pkgs); // bind to network provider - - LocationProvider networkProviderManager = new LocationProvider( - LocationManager.NETWORK_PROVIDER); + LocationProvider networkProviderManager = new LocationProvider(NETWORK_PROVIDER, true); LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( mContext, networkProviderManager, @@ -645,16 +690,15 @@ public class LocationManagerService extends ILocationManager.Stub { com.android.internal.R.string.config_networkLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (networkProvider != null) { - mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProviderManager); - mProxyProviders.add(networkProvider); + mRealProviders.add(networkProviderManager); addProviderLocked(networkProviderManager); + networkProviderManager.attachLocked(networkProvider); } else { Slog.w(TAG, "no network location provider found"); } // bind to fused provider - LocationProvider fusedProviderManager = new LocationProvider( - LocationManager.FUSED_PROVIDER); + LocationProvider fusedProviderManager = new LocationProvider(FUSED_PROVIDER); LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind( mContext, fusedProviderManager, @@ -663,9 +707,9 @@ public class LocationManagerService extends ILocationManager.Stub { com.android.internal.R.string.config_fusedLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (fusedProvider != null) { + mRealProviders.add(fusedProviderManager); addProviderLocked(fusedProviderManager); - mProxyProviders.add(fusedProvider); - mRealProviders.put(LocationManager.FUSED_PROVIDER, fusedProviderManager); + fusedProviderManager.attachLocked(fusedProvider); } else { Slog.e(TAG, "no fused location provider found", new IllegalStateException("Location service needs a fused location provider")); @@ -715,9 +759,6 @@ public class LocationManagerService extends ILocationManager.Stub { for (String testProviderString : testProviderStrings) { String fragments[] = testProviderString.split(","); String name = fragments[0].trim(); - if (mProvidersByName.get(name) != null) { - throw new IllegalArgumentException("Provider \"" + name + "\" already exists"); - } ProviderProperties properties = new ProviderProperties( Boolean.parseBoolean(fragments[1]) /* requiresNetwork */, Boolean.parseBoolean(fragments[2]) /* requiresSatellite */, @@ -728,28 +769,37 @@ public class LocationManagerService extends ILocationManager.Stub { Boolean.parseBoolean(fragments[7]) /* supportsBearing */, Integer.parseInt(fragments[8]) /* powerRequirement */, Integer.parseInt(fragments[9]) /* accuracy */); - addTestProviderLocked(name, properties); + LocationProvider testProviderManager = new LocationProvider(name); + addProviderLocked(testProviderManager); + new MockProvider(testProviderManager, properties); } } - /** - * Called when the device's active user changes. - * - * @param userId the new active user's UserId - */ - private void switchUser(int userId) { + @GuardedBy("mLock") + private void onUserChangedLocked(int userId) { if (mCurrentUserId == userId) { return; } - mBlacklist.switchUser(userId); - mLocationHandler.removeMessages(MSG_LOCATION_CHANGED); - synchronized (mLock) { - mLastLocation.clear(); - mLastLocationCoarseInterval.clear(); - updateUserProfiles(userId); - updateProvidersSettingsLocked(); - mCurrentUserId = userId; + + // this call has the side effect of forcing a write to the LOCATION_MODE setting in an OS + // upgrade case, and ensures that if anyone checks the LOCATION_MODE setting directly, they + // will see it in an appropriate state (at least after that user becomes foreground for the + // first time...) + isLocationEnabledForUser(userId); + + // let providers know the current user is on the way out before changing the user + for (LocationProvider p : mProviders) { + p.onUserChangingLocked(); } + + mCurrentUserId = userId; + onUserProfilesChangedLocked(); + + mBlacklist.switchUser(userId); + + // if the user changes, per-user settings may also have changed + onLocationModeChangedLocked(false); + onProviderAllowedChangedLocked(false); } private static final class Identity { @@ -767,158 +817,380 @@ public class LocationManagerService extends ILocationManager.Stub { private class LocationProvider implements AbstractLocationProvider.LocationProviderManager { private final String mName; - private AbstractLocationProvider mProvider; - // whether the provider is enabled in location settings - private boolean mSettingsEnabled; + // whether this provider should respect LOCATION_PROVIDERS_ALLOWED (ie gps and network) + private final boolean mIsManagedBySettings; - // whether the provider considers itself enabled - private volatile boolean mEnabled; + // remember to clear binder identity before invoking any provider operation + @GuardedBy("mLock") + @Nullable protected AbstractLocationProvider mProvider; - @Nullable - private volatile ProviderProperties mProperties; + @GuardedBy("mLock") + private boolean mUseable; // combined state + @GuardedBy("mLock") + private boolean mAllowed; // state of LOCATION_PROVIDERS_ALLOWED + @GuardedBy("mLock") + private boolean mEnabled; // state of provider + + @GuardedBy("mLock") + @Nullable private ProviderProperties mProperties; private LocationProvider(String name) { + this(name, false); + } + + private LocationProvider(String name, boolean isManagedBySettings) { mName = name; - // TODO: initialize settings enabled? + mIsManagedBySettings = isManagedBySettings; + + mProvider = null; + mUseable = false; + mAllowed = !mIsManagedBySettings; + mEnabled = false; + mProperties = null; } - @Override - public void onAttachProvider(AbstractLocationProvider provider, boolean initiallyEnabled) { + @GuardedBy("mLock") + public void attachLocked(AbstractLocationProvider provider) { + checkNotNull(provider); checkState(mProvider == null); - - // the provider is not yet fully constructed at this point, so we may not do anything - // except save a reference for later use here. do not call any provider methods. mProvider = provider; - mEnabled = initiallyEnabled; - mProperties = null; + + onUseableChangedLocked(); } public String getName() { return mName; } - public boolean isEnabled() { - return mSettingsEnabled && mEnabled; + @GuardedBy("mLock") + @Nullable + public String getPackageLocked() { + if (mProvider == null) { + return null; + } else if (mProvider instanceof LocationProviderProxy) { + // safe to not clear binder context since this doesn't call into the actual provider + return ((LocationProviderProxy) mProvider).getConnectedPackageName(); + } else { + return mContext.getPackageName(); + } + } + + public boolean isMock() { + return false; } + @GuardedBy("mLock") + public boolean isPassiveLocked() { + return mProvider == mPassiveProvider; + } + + @GuardedBy("mLock") @Nullable - public ProviderProperties getProperties() { + public ProviderProperties getPropertiesLocked() { return mProperties; } - public void setRequest(ProviderRequest request, WorkSource workSource) { - mProvider.setRequest(request, workSource); + @GuardedBy("mLock") + public void setRequestLocked(ProviderRequest request, WorkSource workSource) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + mProvider.setRequest(request, workSource); + } finally { + Binder.restoreCallingIdentity(identity); + } + } } - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + @GuardedBy("mLock") + public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(mName + " provider:"); - pw.println(" setting=" + mSettingsEnabled); + if (isMock()) { + pw.println(" mock=true"); + } + pw.println(" attached=" + (mProvider != null)); + if (mIsManagedBySettings) { + pw.println(" allowed=" + mAllowed); + } pw.println(" enabled=" + mEnabled); + pw.println(" useable=" + mUseable); pw.println(" properties=" + mProperties); - mProvider.dump(fd, pw, args); + + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + mProvider.dump(fd, pw, args); + } finally { + Binder.restoreCallingIdentity(identity); + } + } } - public long getStatusUpdateTime() { - return mProvider.getStatusUpdateTime(); + @GuardedBy("mLock") + public long getStatusUpdateTimeLocked() { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + return mProvider.getStatusUpdateTime(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } else { + return 0; + } } - public int getStatus(Bundle extras) { - return mProvider.getStatus(extras); + @GuardedBy("mLock") + public int getStatusLocked(Bundle extras) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + return mProvider.getStatus(extras); + } finally { + Binder.restoreCallingIdentity(identity); + } + } else { + return AVAILABLE; + } } - public void sendExtraCommand(String command, Bundle extras) { - mProvider.sendExtraCommand(command, extras); + @GuardedBy("mLock") + public void sendExtraCommandLocked(String command, Bundle extras) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + mProvider.sendExtraCommand(command, extras); + } finally { + Binder.restoreCallingIdentity(identity); + } + } } // called from any thread @Override public void onReportLocation(Location location) { - runOnHandler(() -> LocationManagerService.this.reportLocation(location, - mProvider == mPassiveProvider)); + // no security check necessary because this is coming from an internal-only interface + // move calls coming from below LMS onto a different thread to avoid deadlock + runInternal(() -> { + synchronized (mLock) { + handleLocationChangedLocked(location, this); + } + }); } // called from any thread @Override public void onReportLocation(List<Location> locations) { - runOnHandler(() -> LocationManagerService.this.reportLocationBatch(locations)); + // move calls coming from below LMS onto a different thread to avoid deadlock + runInternal(() -> { + synchronized (mLock) { + LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER); + if (gpsProvider == null || !gpsProvider.isUseableLocked()) { + Slog.w(TAG, "reportLocationBatch() called without user permission"); + return; + } + + if (mGnssBatchingCallback == null) { + Slog.e(TAG, "reportLocationBatch() called without active Callback"); + return; + } + + try { + mGnssBatchingCallback.onLocationBatch(locations); + } catch (RemoteException e) { + Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e); + } + } + }); } // called from any thread @Override public void onSetEnabled(boolean enabled) { - runOnHandler(() -> { - if (enabled == mEnabled) { - return; - } - - mEnabled = enabled; - - if (!mSettingsEnabled) { - // this provider was disabled in settings anyways, so a change to it's own - // enabled status won't have any affect. - return; - } - - // traditionally clients can listen for changes to the LOCATION_PROVIDERS_ALLOWED - // setting to detect when providers are enabled or disabled (even though they aren't - // supposed to). to continue to support this we must force a change to this setting. - // we use the fused provider because this is forced to be always enabled in settings - // anyways, and so won't have any visible effect beyond triggering content observers - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "+" + LocationManager.FUSED_PROVIDER, mCurrentUserId); - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-" + LocationManager.FUSED_PROVIDER, mCurrentUserId); - + // move calls coming from below LMS onto a different thread to avoid deadlock + runInternal(() -> { synchronized (mLock) { - if (!enabled) { - // If any provider has been disabled, clear all last locations for all - // providers. This is to be on the safe side in case a provider has location - // derived from this disabled provider. - mLastLocation.clear(); - mLastLocationCoarseInterval.clear(); + if (enabled == mEnabled) { + return; } - updateProviderListenersLocked(mName); - } + mEnabled = enabled; + + // update provider allowed settings to reflect enabled status + if (mIsManagedBySettings) { + if (mEnabled && !mAllowed) { + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + "+" + mName, + mCurrentUserId); + } else if (!mEnabled && mAllowed) { + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + "-" + mName, + mCurrentUserId); + } + } - mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION), - UserHandle.ALL); + onUseableChangedLocked(); + } }); } @Override public void onSetProperties(ProviderProperties properties) { - runOnHandler(() -> mProperties = properties); + // move calls coming from below LMS onto a different thread to avoid deadlock + runInternal(() -> { + synchronized (mLock) { + mProperties = properties; + } + }); } - private void setSettingsEnabled(boolean enabled) { - synchronized (mLock) { - if (mSettingsEnabled == enabled) { + @GuardedBy("mLock") + public void onLocationModeChangedLocked() { + onUseableChangedLocked(); + } + + private boolean isAllowed() { + return isAllowedForUser(mCurrentUserId); + } + + private boolean isAllowedForUser(int userId) { + String allowedProviders = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + userId); + return TextUtils.delimitedStringContains(allowedProviders, ',', mName); + } + + @GuardedBy("mLock") + public void onAllowedChangedLocked() { + if (mIsManagedBySettings) { + boolean allowed = isAllowed(); + if (allowed == mAllowed) { return; } + mAllowed = allowed; - mSettingsEnabled = enabled; - if (!mSettingsEnabled) { - // if any provider has been disabled, clear all last locations for all - // providers. this is to be on the safe side in case a provider has location - // derived from this disabled provider. - mLastLocation.clear(); - mLastLocationCoarseInterval.clear(); - updateProviderListenersLocked(mName); - } else if (mEnabled) { - updateProviderListenersLocked(mName); + // make a best effort to keep the setting matching the real enabled state of the + // provider so that legacy applications aren't broken. + if (mAllowed && !mEnabled) { + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + "-" + mName, + mCurrentUserId); } + + onUseableChangedLocked(); + } + } + + @GuardedBy("mLock") + public boolean isUseableLocked() { + return isUseableForUserLocked(mCurrentUserId); + } + + @GuardedBy("mLock") + public boolean isUseableForUserLocked(int userId) { + return userId == mCurrentUserId && mUseable; + } + + @GuardedBy("mLock") + public void onUseableChangedLocked() { + // if any property that contributes to "useability" here changes state, it MUST result + // in a direct or indrect call to onUseableChangedLocked. this allows the provider to + // guarantee that it will always eventually reach the correct state. + boolean useable = mProvider != null + && mProviders.contains(this) && isLocationEnabled() && mAllowed && mEnabled; + if (useable == mUseable) { + return; + } + mUseable = useable; + + if (!mUseable) { + // If any provider has been disabled, clear all last locations for all + // providers. This is to be on the safe side in case a provider has location + // derived from this disabled provider. + mLastLocation.clear(); + mLastLocationCoarseInterval.clear(); } + + updateProviderUseableLocked(this); + } + + @GuardedBy("mLock") + public void onUserChangingLocked() { + // when the user is about to change, we set this provider to un-useable, and notify all + // of the current user clients. when the user is finished changing, useability will be + // updated back via onLocationModeChanged() and onAllowedChanged(). + mUseable = false; + updateProviderUseableLocked(this); } - private void runOnHandler(Runnable runnable) { - if (Looper.myLooper() == mLocationHandler.getLooper()) { + // binder transactions coming from below LMS (ie location providers) need to be moved onto + // a different thread to avoid potential deadlock as code reenters the location providers + private void runInternal(Runnable runnable) { + if (Looper.myLooper() == mHandler.getLooper()) { runnable.run(); } else { - mLocationHandler.post(runnable); + mHandler.post(runnable); + } + } + } + + private class MockLocationProvider extends LocationProvider { + + private MockLocationProvider(String name) { + super(name); + } + + @Override + public void attachLocked(AbstractLocationProvider provider) { + checkState(provider instanceof MockProvider); + super.attachLocked(provider); + } + + public boolean isMock() { + return true; + } + + @GuardedBy("mLock") + public void setEnabledLocked(boolean enabled) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + ((MockProvider) mProvider).setEnabled(enabled); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @GuardedBy("mLock") + public void setLocationLocked(Location location) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + ((MockProvider) mProvider).setLocation(location); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @GuardedBy("mLock") + public void setStatusLocked(int status, Bundle extras, long updateTime) { + if (mProvider != null) { + long identity = Binder.clearCallingIdentity(); + try { + ((MockProvider) mProvider).setStatus(status, extras, updateTime); + } finally { + Binder.restoreCallingIdentity(identity); + } } } } @@ -1022,19 +1294,18 @@ public class LocationManagerService extends ILocationManager.Stub { // See if receiver has any enabled update records. Also note if any update records // are high power (has a high power provider with an interval under a threshold). for (UpdateRecord updateRecord : mUpdateRecords.values()) { - if (isAllowedByUserSettingsLockedForUser(updateRecord.mProvider, - mCurrentUserId)) { - requestingLocation = true; - LocationManagerService.LocationProvider locationProvider - = mProvidersByName.get(updateRecord.mProvider); - ProviderProperties properties = locationProvider != null - ? locationProvider.getProperties() : null; - if (properties != null - && properties.mPowerRequirement == Criteria.POWER_HIGH - && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) { - requestingHighPowerLocation = true; - break; - } + LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider); + if (provider == null || !provider.isUseableLocked()) { + continue; + } + + requestingLocation = true; + ProviderProperties properties = provider.getPropertiesLocked(); + if (properties != null + && properties.mPowerRequirement == Criteria.POWER_HIGH + && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) { + requestingHighPowerLocation = true; + break; } } } @@ -1122,7 +1393,7 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (this) { // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() - mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler, + mPendingIntent.send(mContext, 0, statusChanged, this, mHandler, getResolutionPermission(mAllowedResolutionLevel), PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); // call this after broadcasting so we do not increment @@ -1158,7 +1429,7 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (this) { // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() - mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler, + mPendingIntent.send(mContext, 0, locationChanged, this, mHandler, getResolutionPermission(mAllowedResolutionLevel), PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); // call this after broadcasting so we do not increment @@ -1201,7 +1472,7 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (this) { // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() - mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler, + mPendingIntent.send(mContext, 0, providerIntent, this, mHandler, getResolutionPermission(mAllowedResolutionLevel), PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); // call this after broadcasting so we do not increment @@ -1273,16 +1544,16 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (receiver) { // so wakelock calls will succeed long identity = Binder.clearCallingIdentity(); - receiver.decrementPendingBroadcastsLocked(); - Binder.restoreCallingIdentity(identity); + try { + receiver.decrementPendingBroadcastsLocked(); + } finally { + Binder.restoreCallingIdentity(identity); + } } } } } - /** - * Returns the year of the GNSS hardware. - */ @Override public int getGnssYearOfHardware() { if (mGnssSystemInfoProvider != null) { @@ -1292,10 +1563,6 @@ public class LocationManagerService extends ILocationManager.Stub { } } - - /** - * Returns the model name of the GNSS hardware. - */ @Override @Nullable public String getGnssHardwareModelName() { @@ -1306,32 +1573,24 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Runs some checks for GNSS (FINE) level permissions, used by several methods which directly - * (try to) access GNSS information at this layer. - */ private boolean hasGnssPermissions(String packageName) { - int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - checkResolutionLevelIsSufficientForProviderUse( - allowedResolutionLevel, - LocationManager.GPS_PROVIDER); + synchronized (mLock) { + int allowedResolutionLevel = getCallerAllowedResolutionLevel(); + checkResolutionLevelIsSufficientForProviderUseLocked( + allowedResolutionLevel, + GPS_PROVIDER); - int pid = Binder.getCallingPid(); - int uid = Binder.getCallingUid(); - long identity = Binder.clearCallingIdentity(); - boolean hasLocationAccess; - try { - hasLocationAccess = checkLocationAccess(pid, uid, packageName, allowedResolutionLevel); - } finally { - Binder.restoreCallingIdentity(identity); + int pid = Binder.getCallingPid(); + int uid = Binder.getCallingUid(); + long identity = Binder.clearCallingIdentity(); + try { + return checkLocationAccess(pid, uid, packageName, allowedResolutionLevel); + } finally { + Binder.restoreCallingIdentity(identity); + } } - - return hasLocationAccess; } - /** - * Returns the GNSS batching size, if available. - */ @Override public int getGnssBatchSize(String packageName) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, @@ -1344,10 +1603,6 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Adds a callback for GNSS Batching events, if permissions allow, which are transported - * to potentially multiple listeners by the BatchedLocationCallbackTransport above this. - */ @Override public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, @@ -1357,18 +1612,20 @@ public class LocationManagerService extends ILocationManager.Stub { return false; } - mGnssBatchingCallback = callback; - mGnssBatchingDeathCallback = new LinkedCallback(callback); - try { - callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */); - } catch (RemoteException e) { - // if the remote process registering the listener is already dead, just swallow the - // exception and return - Log.e(TAG, "Remote listener already died.", e); - return false; - } + synchronized (mLock) { + mGnssBatchingCallback = callback; + mGnssBatchingDeathCallback = new LinkedCallback(callback); + try { + callback.asBinder().linkToDeath(mGnssBatchingDeathCallback, 0 /* flags */); + } catch (RemoteException e) { + // if the remote process registering the listener is already dead, just swallow the + // exception and return + Log.e(TAG, "Remote listener already died.", e); + return false; + } - return true; + return true; + } } private class LinkedCallback implements IBinder.DeathRecipient { @@ -1391,27 +1648,22 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Removes callback for GNSS batching - */ @Override public void removeGnssBatchingCallback() { - try { - mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback, - 0 /* flags */); - } catch (NoSuchElementException e) { - // if the death callback isn't connected (it should be...), log error, swallow the - // exception and return - Log.e(TAG, "Couldn't unlink death callback.", e); + synchronized (mLock) { + try { + mGnssBatchingCallback.asBinder().unlinkToDeath(mGnssBatchingDeathCallback, + 0 /* flags */); + } catch (NoSuchElementException e) { + // if the death callback isn't connected (it should be...), log error, swallow the + // exception and return + Log.e(TAG, "Couldn't unlink death callback.", e); + } + mGnssBatchingCallback = null; + mGnssBatchingDeathCallback = null; } - mGnssBatchingCallback = null; - mGnssBatchingDeathCallback = null; } - - /** - * Starts GNSS batching, if available. - */ @Override public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, @@ -1421,20 +1673,20 @@ public class LocationManagerService extends ILocationManager.Stub { return false; } - if (mGnssBatchingInProgress) { - // Current design does not expect multiple starts to be called repeatedly - Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch"); - // Try to clean up anyway, and continue - stopGnssBatch(); - } + synchronized (mLock) { + if (mGnssBatchingInProgress) { + // Current design does not expect multiple starts to be called repeatedly + Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch"); + // Try to clean up anyway, and continue + stopGnssBatch(); + } - mGnssBatchingInProgress = true; - return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull); + mGnssBatchingInProgress = true; + return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull); + } } - /** - * Flushes a GNSS batch in progress - */ + @Override public void flushGnssBatch(String packageName) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, @@ -1445,117 +1697,66 @@ public class LocationManagerService extends ILocationManager.Stub { return; } - if (!mGnssBatchingInProgress) { - Log.w(TAG, "flushGnssBatch called with no batch in progress"); - } + synchronized (mLock) { + if (!mGnssBatchingInProgress) { + Log.w(TAG, "flushGnssBatch called with no batch in progress"); + } - if (mGnssBatchingProvider != null) { - mGnssBatchingProvider.flush(); + if (mGnssBatchingProvider != null) { + mGnssBatchingProvider.flush(); + } } } - /** - * Stops GNSS batching - */ @Override public boolean stopGnssBatch() { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, "Location Hardware permission not granted to access hardware batching"); - if (mGnssBatchingProvider != null) { - mGnssBatchingInProgress = false; - return mGnssBatchingProvider.stop(); - } else { - return false; - } - } - - @Override - public void reportLocationBatch(List<Location> locations) { - checkCallerIsProvider(); - - // Currently used only for GNSS locations - update permissions check if changed - if (isAllowedByUserSettingsLockedForUser(LocationManager.GPS_PROVIDER, mCurrentUserId)) { - if (mGnssBatchingCallback == null) { - Slog.e(TAG, "reportLocationBatch() called without active Callback"); - return; - } - try { - mGnssBatchingCallback.onLocationBatch(locations); - } catch (RemoteException e) { - Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e); + synchronized (mLock) { + if (mGnssBatchingProvider != null) { + mGnssBatchingInProgress = false; + return mGnssBatchingProvider.stop(); + } else { + return false; } - } else { - Slog.w(TAG, "reportLocationBatch() called without user permission, locations blocked"); } } + @GuardedBy("mLock") private void addProviderLocked(LocationProvider provider) { + Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null); + mProviders.add(provider); - mProvidersByName.put(provider.getName(), provider); - } - private void removeProviderLocked(LocationProvider provider) { - mProviders.remove(provider); - mProvidersByName.remove(provider.getName()); + provider.onAllowedChangedLocked(); // allowed state may change while provider was inactive + provider.onUseableChangedLocked(); } - /** - * Returns "true" if access to the specified location provider is allowed by the specified - * user's settings. Access to all location providers is forbidden to non-location-provider - * processes belonging to background users. - * - * @param provider the name of the location provider - * @param userId the user id to query - */ - private boolean isAllowedByUserSettingsLockedForUser(String provider, int userId) { - if (LocationManager.PASSIVE_PROVIDER.equals(provider)) { - return isLocationEnabledForUser(userId); - } - if (LocationManager.FUSED_PROVIDER.equals(provider)) { - return isLocationEnabledForUser(userId); - } - synchronized (mLock) { - if (mMockProviders.containsKey(provider)) { - return isLocationEnabledForUser(userId); + @GuardedBy("mLock") + private void removeProviderLocked(LocationProvider provider) { + if (mProviders.remove(provider)) { + long identity = Binder.clearCallingIdentity(); + try { + provider.onUseableChangedLocked(); + } finally { + Binder.restoreCallingIdentity(identity); } } - - long identity = Binder.clearCallingIdentity(); - try { - // Use system settings - ContentResolver cr = mContext.getContentResolver(); - String allowedProviders = Settings.Secure.getStringForUser( - cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId); - return TextUtils.delimitedStringContains(allowedProviders, ',', provider); - } finally { - Binder.restoreCallingIdentity(identity); - } } - - /** - * Returns "true" if access to the specified location provider is allowed by the specified - * user's settings. Access to all location providers is forbidden to non-location-provider - * processes belonging to background users. - * - * @param provider the name of the location provider - * @param uid the requestor's UID - * @param userId the user id to query - */ - private boolean isAllowedByUserSettingsLocked(String provider, int uid, int userId) { - if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) { - return false; + @GuardedBy("mLock") + @Nullable + private LocationProvider getLocationProviderLocked(String providerName) { + for (LocationProvider provider : mProviders) { + if (providerName.equals(provider.getName())) { + return provider; + } } - return isAllowedByUserSettingsLockedForUser(provider, userId); + + return null; } - /** - * Returns the permission string associated with the specified resolution level. - * - * @param resolutionLevel the resolution level - * @return the permission string - */ private String getResolutionPermission(int resolutionLevel) { switch (resolutionLevel) { case RESOLUTION_LEVEL_FINE: @@ -1567,13 +1768,6 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Returns the resolution level allowed to the given PID/UID pair. - * - * @param pid the PID - * @param uid the UID - * @return resolution level allowed to the pid/uid pair - */ private int getAllowedResolutionLevel(int pid, int uid) { if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, pid, uid) == PERMISSION_GRANTED) { @@ -1586,39 +1780,22 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Returns the resolution level allowed to the caller - * - * @return resolution level allowed to caller - */ private int getCallerAllowedResolutionLevel() { return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid()); } - /** - * Throw SecurityException if specified resolution level is insufficient to use geofences. - * - * @param allowedResolutionLevel resolution level allowed to caller - */ private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) { if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) { throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission"); } } - /** - * Return the minimum resolution level required to use the specified location provider. - * - * @param provider the name of the location provider - * @return minimum resolution level required for provider - */ - private int getMinimumResolutionLevelForProviderUse(String provider) { - if (LocationManager.GPS_PROVIDER.equals(provider) || - LocationManager.PASSIVE_PROVIDER.equals(provider)) { + @GuardedBy("mLock") + private int getMinimumResolutionLevelForProviderUseLocked(String provider) { + if (GPS_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) { // gps and passive providers require FINE permission return RESOLUTION_LEVEL_FINE; - } else if (LocationManager.NETWORK_PROVIDER.equals(provider) || - LocationManager.FUSED_PROVIDER.equals(provider)) { + } else if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) { // network and fused providers are ok with COARSE or FINE return RESOLUTION_LEVEL_COARSE; } else { @@ -1627,7 +1804,7 @@ public class LocationManagerService extends ILocationManager.Stub { continue; } - ProviderProperties properties = lp.getProperties(); + ProviderProperties properties = lp.getPropertiesLocked(); if (properties != null) { if (properties.mRequiresSatellite) { // provider requiring satellites require FINE permission @@ -1643,16 +1820,10 @@ public class LocationManagerService extends ILocationManager.Stub { return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE } - /** - * Throw SecurityException if specified resolution level is insufficient to use the named - * location provider. - * - * @param allowedResolutionLevel resolution level allowed to caller - * @param providerName the name of the location provider - */ - private void checkResolutionLevelIsSufficientForProviderUse(int allowedResolutionLevel, + @GuardedBy("mLock") + private void checkResolutionLevelIsSufficientForProviderUseLocked(int allowedResolutionLevel, String providerName) { - int requiredResolutionLevel = getMinimumResolutionLevelForProviderUse(providerName); + int requiredResolutionLevel = getMinimumResolutionLevelForProviderUseLocked(providerName); if (allowedResolutionLevel < requiredResolutionLevel) { switch (requiredResolutionLevel) { case RESOLUTION_LEVEL_FINE: @@ -1668,20 +1839,6 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Throw SecurityException if WorkSource use is not allowed (i.e. can't blame other packages - * for battery). - */ - private void checkDeviceStatsAllowed() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.UPDATE_DEVICE_STATS, null); - } - - private void checkUpdateAppOpsAllowed() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.UPDATE_APP_OPS_STATS, null); - } - public static int resolutionLevelToOp(int allowedResolutionLevel) { if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) { if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) { @@ -1739,19 +1896,17 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public List<String> getAllProviders() { - ArrayList<String> out; synchronized (mLock) { - out = new ArrayList<>(mProviders.size()); + ArrayList<String> providers = new ArrayList<>(mProviders.size()); for (LocationProvider provider : mProviders) { String name = provider.getName(); - if (LocationManager.FUSED_PROVIDER.equals(name)) { + if (FUSED_PROVIDER.equals(name)) { continue; } - out.add(name); + providers.add(name); } + return providers; } - if (D) Log.d(TAG, "getAllProviders()=" + out); - return out; } /** @@ -1762,37 +1917,28 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public List<String> getProviders(Criteria criteria, boolean enabledOnly) { int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - ArrayList<String> out; - int uid = Binder.getCallingUid(); - long identity = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - out = new ArrayList<>(mProviders.size()); - for (LocationProvider provider : mProviders) { - String name = provider.getName(); - if (LocationManager.FUSED_PROVIDER.equals(name)) { - continue; - } - if (allowedResolutionLevel >= getMinimumResolutionLevelForProviderUse(name)) { - if (enabledOnly - && !isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) { - continue; - } - if (criteria != null - && !android.location.LocationProvider.propertiesMeetCriteria( - name, provider.getProperties(), criteria)) { - continue; - } - out.add(name); - } + synchronized (mLock) { + ArrayList<String> providers = new ArrayList<>(mProviders.size()); + for (LocationProvider provider : mProviders) { + String name = provider.getName(); + if (FUSED_PROVIDER.equals(name)) { + continue; + } + if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) { + continue; + } + if (enabledOnly && !provider.isUseableLocked()) { + continue; + } + if (criteria != null + && !android.location.LocationProvider.propertiesMeetCriteria( + name, provider.getPropertiesLocked(), criteria)) { + continue; } + providers.add(name); } - } finally { - Binder.restoreCallingIdentity(identity); + return providers; } - - if (D) Log.d(TAG, "getProviders()=" + out); - return out; } /** @@ -1804,71 +1950,36 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public String getBestProvider(Criteria criteria, boolean enabledOnly) { - String result; - List<String> providers = getProviders(criteria, enabledOnly); - if (!providers.isEmpty()) { - result = pickBest(providers); - if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); - return result; + if (providers.isEmpty()) { + providers = getProviders(null, enabledOnly); } - providers = getProviders(null, enabledOnly); + if (!providers.isEmpty()) { - result = pickBest(providers); - if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); - return result; + if (providers.contains(GPS_PROVIDER)) { + return GPS_PROVIDER; + } else if (providers.contains(NETWORK_PROVIDER)) { + return NETWORK_PROVIDER; + } else { + return providers.get(0); + } } - if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + null); return null; } - private String pickBest(List<String> providers) { - if (providers.contains(LocationManager.GPS_PROVIDER)) { - return LocationManager.GPS_PROVIDER; - } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) { - return LocationManager.NETWORK_PROVIDER; - } else { - return providers.get(0); - } - } - - @Override - public boolean providerMeetsCriteria(String provider, Criteria criteria) { - LocationProvider p = mProvidersByName.get(provider); - if (p == null) { - throw new IllegalArgumentException("provider=" + provider); - } - - boolean result = android.location.LocationProvider.propertiesMeetCriteria( - p.getName(), p.getProperties(), criteria); - if (D) Log.d(TAG, "providerMeetsCriteria(" + provider + ", " + criteria + ")=" + result); - return result; - } - - private void updateProvidersSettingsLocked() { - for (LocationProvider p : mProviders) { - p.setSettingsEnabled(isAllowedByUserSettingsLockedForUser(p.getName(), mCurrentUserId)); - } - - mContext.sendBroadcastAsUser(new Intent(LocationManager.MODE_CHANGED_ACTION), - UserHandle.ALL); - } - - private void updateProviderListenersLocked(String provider) { - LocationProvider p = mProvidersByName.get(provider); - if (p == null) return; - - boolean enabled = p.isEnabled(); + @GuardedBy("mLock") + private void updateProviderUseableLocked(LocationProvider provider) { + boolean useable = provider.isUseableLocked(); ArrayList<Receiver> deadReceivers = null; - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); + ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); if (records != null) { for (UpdateRecord record : records) { - if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { + if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { // Sends a notification message to the receiver - if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) { + if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) { if (deadReceivers == null) { deadReceivers = new ArrayList<>(); } @@ -1887,25 +1998,37 @@ public class LocationManagerService extends ILocationManager.Stub { applyRequirementsLocked(provider); } - private void applyRequirementsLocked(String provider) { - LocationProvider p = mProvidersByName.get(provider); - if (p == null) return; + @GuardedBy("mLock") + private void applyRequirementsLocked(String providerName) { + LocationProvider provider = getLocationProviderLocked(providerName); + if (provider != null) { + applyRequirementsLocked(provider); + } + } - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); + @GuardedBy("mLock") + private void applyRequirementsLocked(LocationProvider provider) { + ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); WorkSource worksource = new WorkSource(); ProviderRequest providerRequest = new ProviderRequest(); - ContentResolver resolver = mContext.getContentResolver(); - long backgroundThrottleInterval = Settings.Global.getLong( - resolver, - Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, - DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS); + long backgroundThrottleInterval; - if (p.isEnabled() && records != null && !records.isEmpty()) { + long identity = Binder.clearCallingIdentity(); + try { + backgroundThrottleInterval = Settings.Global.getLong( + mContext.getContentResolver(), + Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, + DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS); + } finally { + Binder.restoreCallingIdentity(identity); + } + + if (provider.isUseableLocked() && records != null && !records.isEmpty()) { // initialize the low power mode to true and set to false if any of the records requires providerRequest.lowPowerMode = true; for (UpdateRecord record : records) { - if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { + if (isCurrentProfileLocked(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { if (checkLocationAccess( record.mReceiver.mIdentity.mPid, record.mReceiver.mIdentity.mUid, @@ -1945,7 +2068,8 @@ public class LocationManagerService extends ILocationManager.Stub { // under that threshold. long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2; for (UpdateRecord record : records) { - if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { + if (isCurrentProfileLocked( + UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) { LocationRequest locationRequest = record.mRequest; // Don't assign battery blame for update records whose @@ -1972,7 +2096,7 @@ public class LocationManagerService extends ILocationManager.Stub { } if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest); - p.setRequest(providerRequest, worksource); + provider.setRequestLocked(providerRequest, worksource); } /** @@ -1995,34 +2119,11 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public String[] getBackgroundThrottlingWhitelist() { synchronized (mLock) { - return mBackgroundThrottlePackageWhitelist.toArray( - new String[0]); + return mBackgroundThrottlePackageWhitelist.toArray(new String[0]); } } - private void updateBackgroundThrottlingWhitelistLocked() { - String setting = Settings.Global.getString( - mContext.getContentResolver(), - Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST); - if (setting == null) { - setting = ""; - } - - mBackgroundThrottlePackageWhitelist.clear(); - mBackgroundThrottlePackageWhitelist.addAll( - SystemConfig.getInstance().getAllowUnthrottledLocation()); - mBackgroundThrottlePackageWhitelist.addAll( - Arrays.asList(setting.split(","))); - } - - private void updateLastLocationMaxAgeLocked() { - mLastLocationMaxAgeMs = - Settings.Global.getLong( - mContext.getContentResolver(), - Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS, - DEFAULT_LAST_LOCATION_MAX_AGE_MS); - } - + @GuardedBy("mLock") private boolean isThrottlingExemptLocked(Identity identity) { if (identity.mUid == Process.SYSTEM_UID) { return true; @@ -2032,8 +2133,8 @@ public class LocationManagerService extends ILocationManager.Stub { return true; } - for (LocationProviderProxy provider : mProxyProviders) { - if (identity.mPackageName.equals(provider.getConnectedPackageName())) { + for (LocationProvider provider : mProviders) { + if (identity.mPackageName.equals(provider.getPackageLocked())) { return true; } } @@ -2119,6 +2220,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } + @GuardedBy("mLock") private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) { IBinder binder = listener.asBinder(); @@ -2137,6 +2239,7 @@ public class LocationManagerService extends ILocationManager.Stub { return receiver; } + @GuardedBy("mLock") private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) { Receiver receiver = mReceivers.get(intent); @@ -2202,67 +2305,65 @@ public class LocationManagerService extends ILocationManager.Stub { throw new SecurityException("invalid package name: " + packageName); } - private void checkPendingIntent(PendingIntent intent) { - if (intent == null) { - throw new IllegalArgumentException("invalid pending intent: " + null); - } - } - - private Receiver checkListenerOrIntentLocked(ILocationListener listener, PendingIntent intent, - int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) { - if (intent == null && listener == null) { - throw new IllegalArgumentException("need either listener or intent"); - } else if (intent != null && listener != null) { - throw new IllegalArgumentException("cannot register both listener and intent"); - } else if (intent != null) { - checkPendingIntent(intent); - return getReceiverLocked(intent, pid, uid, packageName, workSource, hideFromAppOps); - } else { - return getReceiverLocked(listener, pid, uid, packageName, workSource, hideFromAppOps); - } - } - @Override public void requestLocationUpdates(LocationRequest request, ILocationListener listener, PendingIntent intent, String packageName) { - if (request == null) request = DEFAULT_LOCATION_REQUEST; - checkPackageName(packageName); - int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, - request.getProvider()); - WorkSource workSource = request.getWorkSource(); - if (workSource != null && !workSource.isEmpty()) { - checkDeviceStatsAllowed(); - } - boolean hideFromAppOps = request.getHideFromAppOps(); - if (hideFromAppOps) { - checkUpdateAppOpsAllowed(); - } - boolean callerHasLocationHardwarePermission = - mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) - == PERMISSION_GRANTED; - LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel, - callerHasLocationHardwarePermission); - - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - // providers may use public location API's, need to clear identity - long identity = Binder.clearCallingIdentity(); - try { - // We don't check for MODE_IGNORED here; we will do that when we go to deliver - // a location. - checkLocationAccess(pid, uid, packageName, allowedResolutionLevel); + synchronized (mLock) { + if (request == null) request = DEFAULT_LOCATION_REQUEST; + checkPackageName(packageName); + int allowedResolutionLevel = getCallerAllowedResolutionLevel(); + checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel, + request.getProvider()); + WorkSource workSource = request.getWorkSource(); + if (workSource != null && !workSource.isEmpty()) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.UPDATE_DEVICE_STATS, null); + } + boolean hideFromAppOps = request.getHideFromAppOps(); + if (hideFromAppOps) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.UPDATE_APP_OPS_STATS, null); + } + boolean callerHasLocationHardwarePermission = + mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) + == PERMISSION_GRANTED; + LocationRequest sanitizedRequest = createSanitizedRequest(request, + allowedResolutionLevel, + callerHasLocationHardwarePermission); + + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); - synchronized (mLock) { - Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid, - packageName, workSource, hideFromAppOps); - requestLocationUpdatesLocked(sanitizedRequest, recevier, uid, packageName); + long identity = Binder.clearCallingIdentity(); + try { + + // We don't check for MODE_IGNORED here; we will do that when we go to deliver + // a location. + checkLocationAccess(pid, uid, packageName, allowedResolutionLevel); + + if (intent == null && listener == null) { + throw new IllegalArgumentException("need either listener or intent"); + } else if (intent != null && listener != null) { + throw new IllegalArgumentException( + "cannot register both listener and intent"); + } + + Receiver receiver; + if (intent != null) { + receiver = getReceiverLocked(intent, pid, uid, packageName, workSource, + hideFromAppOps); + } else { + receiver = getReceiverLocked(listener, pid, uid, packageName, workSource, + hideFromAppOps); + } + requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName); + } finally { + Binder.restoreCallingIdentity(identity); } - } finally { - Binder.restoreCallingIdentity(identity); } } + @GuardedBy("mLock") private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver, int uid, String packageName) { // Figure out the provider. Either its explicitly request (legacy use cases), or @@ -2273,7 +2374,7 @@ public class LocationManagerService extends ILocationManager.Stub { throw new IllegalArgumentException("provider name must not be null"); } - LocationProvider provider = mProvidersByName.get(name); + LocationProvider provider = getLocationProviderLocked(name); if (provider == null) { throw new IllegalArgumentException("provider doesn't exist: " + name); } @@ -2292,7 +2393,7 @@ public class LocationManagerService extends ILocationManager.Stub { oldRecord.disposeLocked(false); } - if (provider.isEnabled()) { + if (provider.isUseableLocked()) { applyRequirementsLocked(name); } else { // Notify the listener that updates are currently disabled @@ -2308,14 +2409,23 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName) { checkPackageName(packageName); - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + int uid = Binder.getCallingUid(); + + if (intent == null && listener == null) { + throw new IllegalArgumentException("need either listener or intent"); + } else if (intent != null && listener != null) { + throw new IllegalArgumentException("cannot register both listener and intent"); + } synchronized (mLock) { - Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid, - packageName, null, false); + Receiver receiver; + if (intent != null) { + receiver = getReceiverLocked(intent, pid, uid, packageName, null, false); + } else { + receiver = getReceiverLocked(listener, pid, uid, packageName, null, false); + } - // providers may use public location API's, need to clear identity long identity = Binder.clearCallingIdentity(); try { removeUpdatesLocked(receiver); @@ -2325,6 +2435,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } + @GuardedBy("mLock") private void removeUpdatesLocked(Receiver receiver) { if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver))); @@ -2356,51 +2467,53 @@ public class LocationManagerService extends ILocationManager.Stub { } } - private void applyAllProviderRequirementsLocked() { - for (LocationProvider p : mProviders) { - applyRequirementsLocked(p.getName()); - } - } - @Override - public Location getLastLocation(LocationRequest request, String packageName) { - if (D) Log.d(TAG, "getLastLocation: " + request); - if (request == null) request = DEFAULT_LOCATION_REQUEST; - int allowedResolutionLevel = getCallerAllowedResolutionLevel(); - checkPackageName(packageName); - checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, - request.getProvider()); - // no need to sanitize this request, as only the provider name is used - - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long identity = Binder.clearCallingIdentity(); - try { - if (mBlacklist.isBlacklisted(packageName)) { - if (D) { - Log.d(TAG, "not returning last loc for blacklisted app: " + - packageName); + public Location getLastLocation(LocationRequest r, String packageName) { + if (D) Log.d(TAG, "getLastLocation: " + r); + synchronized (mLock) { + LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST; + int allowedResolutionLevel = getCallerAllowedResolutionLevel(); + checkPackageName(packageName); + checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel, + request.getProvider()); + // no need to sanitize this request, as only the provider name is used + + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long identity = Binder.clearCallingIdentity(); + try { + if (mBlacklist.isBlacklisted(packageName)) { + if (D) { + Log.d(TAG, "not returning last loc for blacklisted app: " + + packageName); + } + return null; } - return null; - } - if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) { - if (D) { - Log.d(TAG, "not returning last loc for no op app: " + - packageName); + if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) { + if (D) { + Log.d(TAG, "not returning last loc for no op app: " + + packageName); + } + return null; } - return null; - } - synchronized (mLock) { // Figure out the provider. Either its explicitly request (deprecated API's), // or use the fused provider String name = request.getProvider(); if (name == null) name = LocationManager.FUSED_PROVIDER; - LocationProvider provider = mProvidersByName.get(name); + LocationProvider provider = getLocationProviderLocked(name); if (provider == null) return null; - if (!isAllowedByUserSettingsLocked(name, uid, mCurrentUserId)) return null; + // only the current user or location providers may get location this way + if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isLocationProviderLocked( + uid)) { + return null; + } + + if (!provider.isUseableLocked()) { + return null; + } Location location; if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) { @@ -2416,9 +2529,12 @@ public class LocationManagerService extends ILocationManager.Stub { // Don't return stale location to apps with foreground-only location permission. String op = resolutionLevelToOpStr(allowedResolutionLevel); - long locationAgeMs = SystemClock.elapsedRealtime() - - location.getElapsedRealtimeNanos() / NANOS_PER_MILLI; - if ((locationAgeMs > mLastLocationMaxAgeMs) + long locationAgeMs = SystemClock.elapsedRealtime() + - location.getElapsedRealtimeNanos() / NANOS_PER_MILLI; + if ((locationAgeMs > Settings.Global.getLong( + mContext.getContentResolver(), + Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS, + DEFAULT_LAST_LOCATION_MAX_AGE_MS)) && (mAppOps.unsafeCheckOp(op, uid, packageName) == AppOpsManager.MODE_FOREGROUND)) { return null; @@ -2433,24 +2549,13 @@ public class LocationManagerService extends ILocationManager.Stub { } else { return new Location(location); } + return null; + } finally { + Binder.restoreCallingIdentity(identity); } - return null; - } finally { - Binder.restoreCallingIdentity(identity); } } - /** - * Provides an interface to inject and set the last location if location is not available - * currently. - * - * This helps in cases where the product (Cars for example) has saved the last known location - * before powering off. This interface lets the client inject the saved location while the GPS - * chipset is getting its first fix, there by improving user experience. - * - * @param location - Location object to inject - * @return true if update was successful, false if not - */ @Override public boolean injectLocation(Location location) { mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE, @@ -2464,38 +2569,23 @@ public class LocationManagerService extends ILocationManager.Stub { } return false; } - LocationProvider p = null; - String provider = location.getProvider(); - if (provider != null) { - p = mProvidersByName.get(provider); - } - if (p == null) { - if (D) { - Log.d(TAG, "injectLocation(): unknown provider"); - } - return false; - } + synchronized (mLock) { - if (!isAllowedByUserSettingsLockedForUser(provider, mCurrentUserId)) { - if (D) { - Log.d(TAG, "Location disabled in Settings for current user:" + mCurrentUserId); - } + LocationProvider provider = getLocationProviderLocked(location.getProvider()); + if (provider == null || !provider.isUseableLocked()) { return false; - } else { - // NOTE: If last location is already available, location is not injected. If - // provider's normal source (like a GPS chipset) have already provided an output, - // there is no need to inject this location. - if (mLastLocation.get(provider) == null) { - updateLastLocationLocked(location, provider); - } else { - if (D) { - Log.d(TAG, "injectLocation(): Location exists. Not updating"); - } - return false; - } } + + // NOTE: If last location is already available, location is not injected. If + // provider's normal source (like a GPS chipset) have already provided an output + // there is no need to inject this location. + if (mLastLocation.get(provider.getName()) != null) { + return false; + } + + updateLastLocationLocked(location, provider.getName()); + return true; } - return true; } @Override @@ -2504,39 +2594,49 @@ public class LocationManagerService extends ILocationManager.Stub { if (request == null) request = DEFAULT_LOCATION_REQUEST; int allowedResolutionLevel = getCallerAllowedResolutionLevel(); checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel); - checkPendingIntent(intent); + if (intent == null) { + throw new IllegalArgumentException("invalid pending intent: " + null); + } checkPackageName(packageName); - checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, - request.getProvider()); - // Require that caller can manage given document - boolean callerHasLocationHardwarePermission = - mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) - == PERMISSION_GRANTED; - LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel, - callerHasLocationHardwarePermission); + synchronized (mLock) { + checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel, + request.getProvider()); + // Require that caller can manage given document + boolean callerHasLocationHardwarePermission = + mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) + == PERMISSION_GRANTED; + LocationRequest sanitizedRequest = createSanitizedRequest(request, + allowedResolutionLevel, + callerHasLocationHardwarePermission); - if (D) Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent); + if (D) { + Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent); + } - // geo-fence manager uses the public location API, need to clear identity - int uid = Binder.getCallingUid(); - // TODO: http://b/23822629 - if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) { - // temporary measure until geofences work for secondary users - Log.w(TAG, "proximity alerts are currently available only to the primary user"); - return; - } - long identity = Binder.clearCallingIdentity(); - try { - mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel, - uid, packageName); - } finally { - Binder.restoreCallingIdentity(identity); + // geo-fence manager uses the public location API, need to clear identity + int uid = Binder.getCallingUid(); + // TODO: http://b/23822629 + if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) { + // temporary measure until geofences work for secondary users + Log.w(TAG, "proximity alerts are currently available only to the primary user"); + return; + } + long identity = Binder.clearCallingIdentity(); + try { + mGeofenceManager.addFence(sanitizedRequest, geofence, intent, + allowedResolutionLevel, + uid, packageName); + } finally { + Binder.restoreCallingIdentity(identity); + } } } @Override public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) { - checkPendingIntent(intent); + if (intent == null) { + throw new IllegalArgumentException("invalid pending intent: " + null); + } checkPackageName(packageName); if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent); @@ -2641,6 +2741,7 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (mLock) { Identity callerIdentity = new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName); + // TODO(b/120481270): Register for client death notification and update map. mGnssNavigationMessageListeners.put(listener.asBinder(), callerIdentity); long identity = Binder.clearCallingIdentity(); @@ -2670,25 +2771,26 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public boolean sendExtraCommand(String provider, String command, Bundle extras) { - if (provider == null) { + public boolean sendExtraCommand(String providerName, String command, Bundle extras) { + if (providerName == null) { // throw NullPointerException to remain compatible with previous implementation throw new NullPointerException(); } - checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), - provider); + synchronized (mLock) { + checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(), + providerName); - // and check for ACCESS_LOCATION_EXTRA_COMMANDS - if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) - != PERMISSION_GRANTED)) { - throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission"); - } + // and check for ACCESS_LOCATION_EXTRA_COMMANDS + if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) + != PERMISSION_GRANTED)) { + throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission"); + } - synchronized (mLock) { - LocationProvider p = mProvidersByName.get(provider); - if (p == null) return false; + LocationProvider provider = getLocationProviderLocked(providerName); + if (provider != null) { + provider.sendExtraCommandLocked(command, extras); + } - p.sendExtraCommand(command, extras); return true; } } @@ -2707,44 +2809,29 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * @return null if the provider does not exist - * @throws SecurityException if the provider is not allowed to be - * accessed by the caller - */ @Override - public ProviderProperties getProviderProperties(String provider) { - checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), - provider); - - LocationProvider p; + public ProviderProperties getProviderProperties(String providerName) { synchronized (mLock) { - p = mProvidersByName.get(provider); - } + checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(), + providerName); - if (p == null) return null; - return p.getProperties(); + LocationProvider provider = getLocationProviderLocked(providerName); + if (provider == null) { + return null; + } + return provider.getPropertiesLocked(); + } } - /** - * @return null if the provider does not exist - * @throws SecurityException if the provider is not allowed to be - * accessed by the caller - */ @Override public String getNetworkProviderPackage() { - LocationProvider p; synchronized (mLock) { - p = mProvidersByName.get(LocationManager.NETWORK_PROVIDER); - } - - if (p == null) { - return null; - } - if (p.mProvider instanceof LocationProviderProxy) { - return ((LocationProviderProxy) p.mProvider).getConnectedPackageName(); + LocationProvider provider = getLocationProviderLocked(NETWORK_PROVIDER); + if (provider == null) { + return null; + } + return provider.getPackageLocked(); } - return null; } @Override @@ -2780,241 +2867,96 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Returns the current location enabled/disabled status for a user - * - * @param userId the id of the user - * @return true if location is enabled - */ - @Override - public boolean isLocationEnabledForUser(int userId) { - // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); - - long identity = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - final String allowedProviders = Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - userId); - if (allowedProviders == null) { - return false; - } - final List<String> providerList = Arrays.asList(allowedProviders.split(",")); - for (String provider : mRealProviders.keySet()) { - if (provider.equals(LocationManager.PASSIVE_PROVIDER) - || provider.equals(LocationManager.FUSED_PROVIDER)) { - continue; - } - if (providerList.contains(provider)) { - return true; - } - } - return false; - } - } finally { - Binder.restoreCallingIdentity(identity); - } + private boolean isLocationEnabled() { + return isLocationEnabledForUser(mCurrentUserId); } - /** - * Enable or disable location for a user - * - * @param enabled true to enable location, false to disable location - * @param userId the id of the user - */ @Override - public void setLocationEnabledForUser(boolean enabled, int userId) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS, - "Requires WRITE_SECURE_SETTINGS permission"); - + public boolean isLocationEnabledForUser(int userId) { // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS, + "Requires INTERACT_ACROSS_USERS permission"); + } long identity = Binder.clearCallingIdentity(); try { - synchronized (mLock) { - final Set<String> allRealProviders = mRealProviders.keySet(); - // Update all providers on device plus gps and network provider when disabling - // location - Set<String> allProvidersSet = new ArraySet<>(allRealProviders.size() + 2); - allProvidersSet.addAll(allRealProviders); - // When disabling location, disable gps and network provider that could have been - // enabled by location mode api. - if (!enabled) { - allProvidersSet.add(LocationManager.GPS_PROVIDER); - allProvidersSet.add(LocationManager.NETWORK_PROVIDER); - } - if (allProvidersSet.isEmpty()) { - return; - } - // to ensure thread safety, we write the provider name with a '+' or '-' - // and let the SettingsProvider handle it rather than reading and modifying - // the list of enabled providers. - final String prefix = enabled ? "+" : "-"; - StringBuilder locationProvidersAllowed = new StringBuilder(); - for (String provider : allProvidersSet) { - if (provider.equals(LocationManager.PASSIVE_PROVIDER) - || provider.equals(LocationManager.FUSED_PROVIDER)) { - continue; - } - locationProvidersAllowed.append(prefix); - locationProvidersAllowed.append(provider); - locationProvidersAllowed.append(","); - } - // Remove the trailing comma - locationProvidersAllowed.setLength(locationProvidersAllowed.length() - 1); - Settings.Secure.putStringForUser( + boolean enabled; + try { + enabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, + userId) != Settings.Secure.LOCATION_MODE_OFF; + } catch (Settings.SettingNotFoundException e) { + // OS upgrade case where mode isn't set yet + enabled = !TextUtils.isEmpty(Settings.Secure.getStringForUser( mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - locationProvidersAllowed.toString(), - userId); - } + userId)); + + try { + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, + enabled + ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY + : Settings.Secure.LOCATION_MODE_OFF, + userId); + } catch (RuntimeException ex) { + // any problem with writing should not be propagated + Slog.e(TAG, "error updating location mode", ex); + } + } + return enabled; } finally { Binder.restoreCallingIdentity(identity); } } - /** - * Returns the current enabled/disabled status of a location provider and user - * - * @param providerName name of the provider - * @param userId the id of the user - * @return true if the provider exists and is enabled - */ @Override public boolean isProviderEnabledForUser(String providerName, int userId) { // Check INTERACT_ACROSS_USERS permission if userId is not current user id. - checkInteractAcrossUsersPermission(userId); - - if (!isLocationEnabledForUser(userId)) { - return false; + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS, + "Requires INTERACT_ACROSS_USERS permission"); } // Fused provider is accessed indirectly via criteria rather than the provider-based APIs, // so we discourage its use - if (LocationManager.FUSED_PROVIDER.equals(providerName)) return false; - - long identity = Binder.clearCallingIdentity(); - try { - LocationProvider provider; - synchronized (mLock) { - provider = mProvidersByName.get(providerName); - } - return provider != null && provider.isEnabled(); - } finally { - Binder.restoreCallingIdentity(identity); - } - } + if (FUSED_PROVIDER.equals(providerName)) return false; - /** - * Enable or disable a single location provider. - * - * @param provider name of the provider - * @param enabled true to enable the provider. False to disable the provider - * @param userId the id of the user to set - * @return true if the value was set, false on errors - */ - @Override - public boolean setProviderEnabledForUser(String provider, boolean enabled, int userId) { - return false; - } - - /** - * Method for checking INTERACT_ACROSS_USERS permission if specified user id is not the same as - * current user id - * - * @param userId the user id to get or set value - */ - private void checkInteractAcrossUsersPermission(int userId) { - int uid = Binder.getCallingUid(); - if (UserHandle.getUserId(uid) != userId) { - if (ActivityManager.checkComponentPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS, uid, -1, true) - != PERMISSION_GRANTED) { - throw new SecurityException("Requires INTERACT_ACROSS_USERS permission"); - } + synchronized (mLock) { + LocationProvider provider = getLocationProviderLocked(providerName); + return provider != null && provider.isUseableForUserLocked(userId); } } - /** - * Returns "true" if the UID belongs to a bound location provider. - * - * @param uid the uid - * @return true if uid belongs to a bound location provider - */ - private boolean isUidALocationProvider(int uid) { + @GuardedBy("mLock") + private boolean isLocationProviderLocked(int uid) { if (uid == Process.SYSTEM_UID) { return true; } - if (mGeocodeProvider != null) { - if (doesUidHavePackage(uid, mGeocodeProvider.getConnectedPackageName())) return true; - } - for (LocationProviderProxy proxy : mProxyProviders) { - if (doesUidHavePackage(uid, proxy.getConnectedPackageName())) return true; - } - return false; - } - private void checkCallerIsProvider() { - if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER) - == PERMISSION_GRANTED) { - return; - } - - // Previously we only used the INSTALL_LOCATION_PROVIDER - // check. But that is system or signature - // protection level which is not flexible enough for - // providers installed oustide the system image. So - // also allow providers with a UID matching the - // currently bound package name - - if (isUidALocationProvider(Binder.getCallingUid())) { - return; - } - - throw new SecurityException("need INSTALL_LOCATION_PROVIDER permission, " + - "or UID of a currently bound location provider"); - } - - /** - * Returns true if the given package belongs to the given uid. - */ - private boolean doesUidHavePackage(int uid, String packageName) { - if (packageName == null) { - return false; - } String[] packageNames = mPackageManager.getPackagesForUid(uid); if (packageNames == null) { return false; } - for (String name : packageNames) { - if (packageName.equals(name)) { + for (LocationProvider provider : mProviders) { + String packageName = provider.getPackageLocked(); + if (packageName == null) { + continue; + } + if (ArrayUtils.contains(packageNames, packageName)) { return true; } } return false; } - @Override - public void reportLocation(Location location, boolean passive) { - checkCallerIsProvider(); - - if (!location.isComplete()) { - Log.w(TAG, "Dropping incomplete location: " + location); - return; - } - - mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location); - Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location); - m.arg1 = (passive ? 1 : 0); - mLocationHandler.sendMessageAtFrontOfQueue(m); - } - - - private static boolean shouldBroadcastSafe( + @GuardedBy("mLock") + private static boolean shouldBroadcastSafeLocked( Location loc, Location lastLoc, UpdateRecord record, long now) { // Always broadcast the first update if (lastLoc == null) { @@ -3046,26 +2988,36 @@ public class LocationManagerService extends ILocationManager.Stub { return record.mRealRequest.getExpireAt() >= now; } - private void handleLocationChangedLocked(Location location, boolean passive) { + @GuardedBy("mLock") + private void handleLocationChangedLocked(Location location, LocationProvider provider) { + if (!mProviders.contains(provider)) { + return; + } + if (!location.isComplete()) { + Log.w(TAG, "Dropping incomplete location: " + location); + return; + } + + if (!provider.isPassiveLocked()) { + // notify passive provider of the new location + mPassiveProvider.updateLocation(location); + } + if (D) Log.d(TAG, "incoming location: " + location); long now = SystemClock.elapsedRealtime(); - String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider()); - // Skip if the provider is unknown. - LocationProvider p = mProvidersByName.get(provider); - if (p == null) return; - updateLastLocationLocked(location, provider); + updateLastLocationLocked(location, provider.getName()); // mLastLocation should have been updated from the updateLastLocationLocked call above. - Location lastLocation = mLastLocation.get(provider); + Location lastLocation = mLastLocation.get(provider.getName()); if (lastLocation == null) { Log.e(TAG, "handleLocationChangedLocked() updateLastLocation failed"); return; } // Update last known coarse interval location if enough time has passed. - Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider); + Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider.getName()); if (lastLocationCoarseInterval == null) { lastLocationCoarseInterval = new Location(location); - mLastLocationCoarseInterval.put(provider, lastLocationCoarseInterval); + mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval); } long timeDiffNanos = location.getElapsedRealtimeNanos() - lastLocationCoarseInterval.getElapsedRealtimeNanos(); @@ -3079,7 +3031,7 @@ public class LocationManagerService extends ILocationManager.Stub { lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); // Skip if there are no UpdateRecords for this provider. - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); + ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); if (records == null || records.size() == 0) return; // Fetch coarse location @@ -3088,13 +3040,6 @@ public class LocationManagerService extends ILocationManager.Stub { coarseLocation = mLocationFudger.getOrCreate(noGPSLocation); } - // Fetch latest status update time - long newStatusUpdateTime = p.getStatusUpdateTime(); - - // Get latest status - Bundle extras = new Bundle(); - int status = p.getStatus(extras); - ArrayList<Receiver> deadReceivers = null; ArrayList<UpdateRecord> deadUpdateRecords = null; @@ -3104,8 +3049,8 @@ public class LocationManagerService extends ILocationManager.Stub { boolean receiverDead = false; int receiverUserId = UserHandle.getUserId(receiver.mIdentity.mUid); - if (!isCurrentProfile(receiverUserId) - && !isUidALocationProvider(receiver.mIdentity.mUid)) { + if (!isCurrentProfileLocked(receiverUserId) + && !isLocationProviderLocked(receiver.mIdentity.mUid)) { if (D) { Log.d(TAG, "skipping loc update for background user " + receiverUserId + " (current user: " + mCurrentUserId + ", app: " + @@ -3142,7 +3087,8 @@ public class LocationManagerService extends ILocationManager.Stub { } if (notifyLocation != null) { Location lastLoc = r.mLastFixBroadcast; - if ((lastLoc == null) || shouldBroadcastSafe(notifyLocation, lastLoc, r, now)) { + if ((lastLoc == null) + || shouldBroadcastSafeLocked(notifyLocation, lastLoc, r, now)) { if (lastLoc == null) { lastLoc = new Location(notifyLocation); r.mLastFixBroadcast = lastLoc; @@ -3150,7 +3096,8 @@ public class LocationManagerService extends ILocationManager.Stub { lastLoc.set(notifyLocation); } if (!receiver.callLocationChangedLocked(notifyLocation)) { - Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver); + Slog.w(TAG, "RemoteException calling onLocationChanged on " + + receiver); receiverDead = true; } r.mRealRequest.decrementNumUpdates(); @@ -3161,12 +3108,16 @@ public class LocationManagerService extends ILocationManager.Stub { // guarded behind this setting now. should be removed completely post-Q if (Settings.Global.getInt(mContext.getContentResolver(), LOCATION_DISABLE_STATUS_CALLBACKS, 1) == 0) { + long newStatusUpdateTime = provider.getStatusUpdateTimeLocked(); + Bundle extras = new Bundle(); + int status = provider.getStatusLocked(extras); + long prevStatusUpdateTime = r.mLastStatusBroadcast; if ((newStatusUpdateTime > prevStatusUpdateTime) && (prevStatusUpdateTime != 0 || status != AVAILABLE)) { r.mLastStatusBroadcast = newStatusUpdateTime; - if (!receiver.callStatusChangedLocked(provider, status, extras)) { + if (!receiver.callStatusChangedLocked(provider.getName(), status, extras)) { receiverDead = true; Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver); } @@ -3205,12 +3156,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } - /** - * Updates last location with the given location - * - * @param location new location to update - * @param provider Location provider to update for - */ + @GuardedBy("mLock") private void updateLastLocationLocked(Location location, String provider) { Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); Location lastNoGPSLocation; @@ -3229,75 +3175,6 @@ public class LocationManagerService extends ILocationManager.Stub { lastLocation.set(location); } - private class LocationWorkerHandler extends Handler { - public LocationWorkerHandler(Looper looper) { - super(looper, null, true); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_LOCATION_CHANGED: - handleLocationChanged((Location) msg.obj, msg.arg1 == 1); - break; - } - } - } - - private boolean isMockProvider(String provider) { - synchronized (mLock) { - return mMockProviders.containsKey(provider); - } - } - - private void handleLocationChanged(Location location, boolean passive) { - // create a working copy of the incoming Location so that the service can modify it without - // disturbing the caller's copy - Location myLocation = new Location(location); - String provider = myLocation.getProvider(); - - // set "isFromMockProvider" bit if location came from a mock provider. we do not clear this - // bit if location did not come from a mock provider because passive/fused providers can - // forward locations from mock providers, and should not grant them legitimacy in doing so. - if (!myLocation.isFromMockProvider() && isMockProvider(provider)) { - myLocation.setIsFromMockProvider(true); - } - - synchronized (mLock) { - if (!passive) { - // notify passive provider of the new location - mPassiveProvider.updateLocation(myLocation); - } - handleLocationChangedLocked(myLocation, passive); - } - } - - private final PackageMonitor mPackageMonitor = new PackageMonitor() { - @Override - public void onPackageDisappeared(String packageName, int reason) { - // remove all receivers associated with this package name - synchronized (mLock) { - ArrayList<Receiver> deadReceivers = null; - - for (Receiver receiver : mReceivers.values()) { - if (receiver.mIdentity.mPackageName.equals(packageName)) { - if (deadReceivers == null) { - deadReceivers = new ArrayList<>(); - } - deadReceivers.add(receiver); - } - } - - // perform removal outside of mReceivers loop - if (deadReceivers != null) { - for (Receiver receiver : deadReceivers) { - removeUpdatesLocked(receiver); - } - } - } - } - }; - // Geocoder @Override @@ -3343,63 +3220,60 @@ public class LocationManagerService extends ILocationManager.Stub { return; } - if (LocationManager.PASSIVE_PROVIDER.equals(name)) { + if (PASSIVE_PROVIDER.equals(name)) { throw new IllegalArgumentException("Cannot mock the passive location provider"); } - long identity = Binder.clearCallingIdentity(); synchronized (mLock) { - // remove the real provider if we are replacing GPS or network provider - if (LocationManager.GPS_PROVIDER.equals(name) - || LocationManager.NETWORK_PROVIDER.equals(name) - || LocationManager.FUSED_PROVIDER.equals(name)) { - LocationProvider p = mProvidersByName.get(name); - if (p != null) { - removeProviderLocked(p); + long identity = Binder.clearCallingIdentity(); + try { + LocationProvider oldProvider = getLocationProviderLocked(name); + if (oldProvider != null) { + if (oldProvider.isMock()) { + throw new IllegalArgumentException( + "Provider \"" + name + "\" already exists"); + } + + removeProviderLocked(oldProvider); } - } - addTestProviderLocked(name, properties); - } - Binder.restoreCallingIdentity(identity); - } - private void addTestProviderLocked(String name, ProviderProperties properties) { - if (mProvidersByName.get(name) != null) { - throw new IllegalArgumentException("Provider \"" + name + "\" already exists"); + MockLocationProvider mockProviderManager = new MockLocationProvider(name); + addProviderLocked(mockProviderManager); + mockProviderManager.attachLocked(new MockProvider(mockProviderManager, properties)); + } finally { + Binder.restoreCallingIdentity(identity); + } } - - LocationProvider provider = new LocationProvider(name); - MockProvider mockProvider = new MockProvider(provider, properties); - - addProviderLocked(provider); - mMockProviders.put(name, mockProvider); - mLastLocation.put(name, null); - mLastLocationCoarseInterval.put(name, null); } @Override - public void removeTestProvider(String provider, String opPackageName) { + public void removeTestProvider(String name, String opPackageName) { if (!canCallerAccessMockLocation(opPackageName)) { return; } synchronized (mLock) { - MockProvider mockProvider = mMockProviders.remove(provider); - if (mockProvider == null) { - throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); - } - long identity = Binder.clearCallingIdentity(); try { - removeProviderLocked(mProvidersByName.get(provider)); + LocationProvider testProvider = getLocationProviderLocked(name); + if (testProvider == null || !testProvider.isMock()) { + throw new IllegalArgumentException("Provider \"" + name + "\" unknown"); + } + + removeProviderLocked(testProvider); // reinstate real provider if available - LocationProvider realProvider = mRealProviders.get(provider); + LocationProvider realProvider = null; + for (LocationProvider provider : mRealProviders) { + if (name.equals(provider.getName())) { + realProvider = provider; + break; + } + } + if (realProvider != null) { addProviderLocked(realProvider); } - mLastLocation.put(provider, null); - mLastLocationCoarseInterval.put(provider, null); } finally { Binder.restoreCallingIdentity(identity); } @@ -3407,78 +3281,60 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public void setTestProviderLocation(String provider, Location loc, String opPackageName) { + public void setTestProviderLocation(String providerName, Location location, + String opPackageName) { if (!canCallerAccessMockLocation(opPackageName)) { return; } synchronized (mLock) { - MockProvider mockProvider = mMockProviders.get(provider); - if (mockProvider == null) { - throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); + LocationProvider testProvider = getLocationProviderLocked(providerName); + if (testProvider == null || !testProvider.isMock()) { + throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); } - // Ensure that the location is marked as being mock. There's some logic to do this in - // handleLocationChanged(), but it fails if loc has the wrong provider (bug 33091107). - Location mock = new Location(loc); - mock.setIsFromMockProvider(true); - - if (!TextUtils.isEmpty(loc.getProvider()) && !provider.equals(loc.getProvider())) { - // The location has an explicit provider that is different from the mock provider - // name. The caller may be trying to fool us via bug 33091107. + String locationProvider = location.getProvider(); + if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) { + // The location has an explicit provider that is different from the mock + // provider name. The caller may be trying to fool us via b/33091107. EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), - provider + "!=" + loc.getProvider()); + providerName + "!=" + location.getProvider()); } - // clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required - long identity = Binder.clearCallingIdentity(); - try { - mockProvider.setLocation(mock); - } finally { - Binder.restoreCallingIdentity(identity); - } + ((MockLocationProvider) testProvider).setLocationLocked(location); } } @Override - public void setTestProviderEnabled(String provider, boolean enabled, String opPackageName) { + public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) { if (!canCallerAccessMockLocation(opPackageName)) { return; } synchronized (mLock) { - MockProvider mockProvider = mMockProviders.get(provider); - if (mockProvider == null) { - throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); - } - long identity = Binder.clearCallingIdentity(); - try { - mockProvider.setEnabled(enabled); - } finally { - Binder.restoreCallingIdentity(identity); + LocationProvider testProvider = getLocationProviderLocked(providerName); + if (testProvider == null || !testProvider.isMock()) { + throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); } + + ((MockLocationProvider) testProvider).setEnabledLocked(enabled); } } @Override - public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime, - String opPackageName) { + public void setTestProviderStatus(String providerName, int status, Bundle extras, + long updateTime, String opPackageName) { if (!canCallerAccessMockLocation(opPackageName)) { return; } synchronized (mLock) { - MockProvider mockProvider = mMockProviders.get(provider); - if (mockProvider == null) { - throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); + LocationProvider testProvider = getLocationProviderLocked(providerName); + if (testProvider == null || !testProvider.isMock()) { + throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); } - mockProvider.setStatus(status, extras, updateTime); - } - } - private void log(String log) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.d(TAG, log); + ((MockLocationProvider) testProvider).setStatusLocked(status, extras, updateTime); } } @@ -3494,6 +3350,7 @@ public class LocationManagerService extends ILocationManager.Stub { return; } pw.println("Current Location Manager state:"); + pw.println(" Location Mode: " + isLocationEnabled()); pw.println(" Location Listeners:"); for (Receiver receiver : mReceivers.values()) { pw.println(" " + receiver); @@ -3548,12 +3405,6 @@ public class LocationManagerService extends ILocationManager.Stub { pw.append(" "); mBlacklist.dump(pw); - if (mMockProviders.size() > 0) { - pw.println(" Mock Providers:"); - for (Map.Entry<String, MockProvider> i : mMockProviders.entrySet()) { - i.getValue().dump(fd, pw, args); - } - } if (mLocationControllerExtraPackage != null) { pw.println(" Location controller extra package: " + mLocationControllerExtraPackage @@ -3574,7 +3425,7 @@ public class LocationManagerService extends ILocationManager.Stub { return; } for (LocationProvider provider : mProviders) { - provider.dump(fd, pw, args); + provider.dumpLocked(fd, pw, args); } if (mGnssBatchingInProgress) { pw.println(" GNSS batching in progress"); diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 9f353a80a5be..faca7507e49c 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -149,6 +149,8 @@ public class VibratorService extends IVibratorService.Stub native static boolean vibratorSupportsAmplitudeControl(); native static void vibratorSetAmplitude(int amplitude); native static long vibratorPerformEffect(long effect, long strength); + static native boolean vibratorSupportsExternalControl(); + static native void vibratorSetExternalControl(boolean enabled); private final IUidObserver mUidObserver = new IUidObserver.Stub() { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) { @@ -532,12 +534,6 @@ public class VibratorService extends IVibratorService.Stub return; } verifyIncomingUid(uid); - if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) - > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { - Slog.e(TAG, "Ignoring incoming vibration as process with uid = " - + uid + " is background"); - return; - } if (!verifyVibrationEffect(effect)) { return; } @@ -575,6 +571,13 @@ public class VibratorService extends IVibratorService.Stub } Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg, reason); + if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) + > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + && vib.isHapticFeedback()) { + Slog.e(TAG, "Ignoring incoming vibration as process with uid = " + + uid + " is background"); + return; + } linkVibration(vib); long ident = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 36ca4dcbea33..33af9f6f75d4 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -395,7 +395,7 @@ public class BiometricService extends SystemService { // Notify SysUI that the biometric has been authenticated. SysUI already knows // the implicit/explicit state and will react accordingly. - mStatusBarService.onBiometricAuthenticated(); + mStatusBarService.onBiometricAuthenticated(true); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } @@ -412,17 +412,20 @@ public class BiometricService extends SystemService { return; } - mStatusBarService.onBiometricHelp(getContext().getResources().getString( - com.android.internal.R.string.biometric_not_recognized)); - if (requireConfirmation) { + mStatusBarService.onBiometricAuthenticated(false); + + // TODO: This logic will need to be updated if BP is multi-modal + if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) { + // Pause authentication. onBiometricAuthenticated(false) causes the + // dialog to show a "try again" button for passive modalities. mCurrentAuthSession.mState = STATE_AUTH_PAUSED; - mStatusBarService.showBiometricTryAgain(); // Cancel authentication. Skip the token/package check since we are // cancelling from system server. The interface is permission protected so // this is fine. cancelInternal(null /* token */, null /* package */, false /* fromClient */); } + mCurrentAuthSession.mClientReceiver.onAuthenticationFailed(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); @@ -579,8 +582,10 @@ public class BiometricService extends SystemService { } if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) { - final boolean mContinuing = mCurrentAuthSession != null - && mCurrentAuthSession.mState == STATE_AUTH_PAUSED; + final boolean continuing = mCurrentAuthSession != null && + (mCurrentAuthSession.mState == STATE_AUTH_PAUSED + || mCurrentAuthSession.mState == STATE_AUTH_PAUSED_CANCELED); + mCurrentAuthSession = mPendingAuthSession; mPendingAuthSession = null; @@ -602,7 +607,7 @@ public class BiometricService extends SystemService { modality |= pair.getKey(); } - if (!mContinuing) { + if (!continuing) { mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle, mInternalReceiver, modality, requireConfirmation, userId); mActivityTaskManager.registerTaskStackListener(mTaskStackListener); @@ -706,7 +711,8 @@ public class BiometricService extends SystemService { mCurrentModality = modality; - // Actually start authentication + // Start preparing for authentication. Authentication starts when + // all modalities requested have invoked onReadyForAuthentication. authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle, callingUid, callingPid, callingUserId, modality); }); @@ -725,6 +731,9 @@ public class BiometricService extends SystemService { IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle, int callingUid, int callingPid, int callingUserId, int modality) { try { + final boolean requireConfirmation = bundle.getBoolean( + BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */); + // Generate random cookies to pass to the services that should prepare to start // authenticating. Store the cookie here and wait for all services to "ack" // with the cookie. Once all cookies are received, we can show the prompt @@ -748,7 +757,7 @@ public class BiometricService extends SystemService { Slog.w(TAG, "Iris unsupported"); } if ((modality & TYPE_FACE) != 0) { - mFaceService.prepareForAuthentication(true /* requireConfirmation */, + mFaceService.prepareForAuthentication(requireConfirmation, token, sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid, callingPid, callingUserId); } diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java index 72f73f6aaf67..f4d8d4bdec83 100644 --- a/services/core/java/com/android/server/biometrics/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/face/FaceService.java @@ -156,7 +156,7 @@ public class FaceService extends BiometricServiceBase { mDaemonWrapper, mHalDeviceId, token, new BiometricPromptServiceListenerImpl(wrapperReceiver), mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie, - true /* requireConfirmation */); + requireConfirmation); authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId); } diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index ea01a0b4253e..1b787b8bc76d 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -279,9 +279,9 @@ final class Constants { * <p>True means enabling muting logic. * <p>False means never mute device. */ - // TODO(OEM): set to true to disable muting. + // TODO(OEM): Change property to ro and set to true to disable muting. static final String PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE = - "ro.hdmi.property_system_audio_mode_muting_enable"; + "persist.sys.hdmi.property_system_audio_mode_muting_enable"; // Set to false to allow playback device to go to suspend mode even // when it's an active source. True by default. diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java index 11faa56f704a..2da698be56be 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java @@ -252,6 +252,10 @@ abstract class HdmiCecFeatureAction { return (HdmiCecLocalDevicePlayback) mSource; } + protected final HdmiCecLocalDeviceSource source() { + return (HdmiCecLocalDeviceSource) mSource; + } + protected final HdmiCecLocalDeviceTv tv() { return (HdmiCecLocalDeviceTv) mSource; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 7860122ed9ea..c338e21105b8 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -27,10 +27,12 @@ import android.util.Slog; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; + import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; + import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -51,6 +53,11 @@ abstract class HdmiCecLocalDevice { // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior. // When it expires, we can assume <User Control Release> is received. private static final int FOLLOWER_SAFETY_TIMEOUT = 550; + /** + * Return value of {@link #getLocalPortFromPhysicalAddress(int)} + */ + private static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1; + private static final int TARGET_SAME_PHYSICAL_ADDRESS = 0; protected final HdmiControlService mService; protected final int mDeviceType; @@ -434,10 +441,14 @@ abstract class HdmiCecLocalDevice { return true; } + // Audio System device with no Playback device type + // needs to refactor this function if it's also a switch protected boolean handleRoutingChange(HdmiCecMessage message) { return false; } + // Audio System device with no Playback device type + // needs to refactor this function if it's also a switch protected boolean handleRoutingInformation(HdmiCecMessage message) { return false; } @@ -1033,4 +1044,45 @@ abstract class HdmiCecLocalDevice { pw.println("mActiveSource: " + mActiveSource); pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath)); } + + /** + * Method to parse target physical address to the port number on the current device. + * + * <p>This check assumes target address is valid. + * @param targetPhysicalAddress is the physical address of the target device + * @return + * <p>If the target device is under the current device, return the port number of current device + * that the target device is connected to. + * + * <p>If the target device has the same physical address as the current device, return + * {@link #TARGET_SAME_PHYSICAL_ADDRESS}. + * + * <p>If the target device is not under the current device, return + * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}. + */ + protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) { + int myPhysicalAddress = mService.getPhysicalAddress(); + if (myPhysicalAddress == targetPhysicalAddress) { + return TARGET_SAME_PHYSICAL_ADDRESS; + } + int finalMask = 0xF000; + int mask; + int port = 0; + for (mask = 0x0F00; mask > 0x000F; mask >>= 4) { + if ((myPhysicalAddress & mask) == 0) { + port = mask & targetPhysicalAddress; + break; + } else { + finalMask |= mask; + } + } + if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) { + while (mask != 0x000F) { + mask >>= 4; + port >>= 4; + } + return port; + } + return TARGET_NOT_UNDER_LOCAL_DEVICE; + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 7358eaa587e3..d8a2d8928274 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -36,7 +36,7 @@ import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android * system. */ -public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { +public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { private static final String TAG = "HdmiCecLocalDeviceAudioSystem"; @@ -143,43 +143,17 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) { if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON) || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON) - && lastSystemAudioControlStatus)) { + && lastSystemAudioControlStatus)) { addAndStartAction(new SystemAudioInitiationActionFromAvr(this)); } } - @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { - assertRunOnServiceThread(); - int logicalAddress = message.getSource(); - int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); - if (!mActiveSource.equals(activeSource)) { - setActiveSource(activeSource); - } - return true; - } - - @Override - @ServiceThreadOnly - protected boolean handleSetStreamPath(HdmiCecMessage message) { - assertRunOnServiceThread(); - int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - // If current device is the target path, playback device should handle it. - // If the path is under the current device, should switch - int port = getLocalPortFromPhysicalAddress(physicalAddress); - if (port > 0) { - routeToPort(port); - } - return true; - } - @Override @ServiceThreadOnly protected int getPreferredAddress() { assertRunOnServiceThread(); return SystemProperties.getInt( - Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED); + Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED); } @Override @@ -323,7 +297,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { for (int i = 0; i < params.length; i++) { byte val = params[i]; audioFormatCodes[i] = - val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE; + val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE; } return audioFormatCodes; } @@ -333,7 +307,23 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) { assertRunOnServiceThread(); boolean systemAudioStatusOn = message.getParams().length != 0; - if (!setSystemAudioMode(systemAudioStatusOn)) { + // Check if the request comes from a non-TV device. + // Need to check if TV supports System Audio Control + // if non-TV device tries to turn on the feature + if (message.getSource() != Constants.ADDR_TV) { + if (systemAudioStatusOn) { + handleSystemAudioModeOnFromNonTvDevice(message); + return true; + } + } else { + // If TV request the feature on + // cache TV supporting System Audio Control + // until Audio System loses its physical address. + setTvSystemAudioModeSupport(true); + } + // If TV or Audio System does not support the feature, + // will send abort command. + if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) { mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); return true; } @@ -348,7 +338,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { assertRunOnServiceThread(); - if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) { + if (!checkSupportAndSetSystemAudioMode( + HdmiUtils.parseCommandParamSystemAudioStatus(message))) { mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); } return true; @@ -358,7 +349,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { assertRunOnServiceThread(); - if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) { + if (!checkSupportAndSetSystemAudioMode( + HdmiUtils.parseCommandParamSystemAudioStatus(message))) { mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); } return true; @@ -390,7 +382,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { private void notifyArcStatusToAudioService(boolean enabled) { // Note that we don't set any name to ARC. mService.getAudioManager() - .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", ""); + .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", ""); } private void reportAudioStatus(HdmiCecMessage message) { @@ -406,7 +398,16 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { mAddress, message.getSource(), scaledVolume, mute)); } - protected boolean setSystemAudioMode(boolean newSystemAudioMode) { + /** + * Method to check if device support System Audio Control. If so, wake up device if necessary. + * + * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode + * @param newSystemAudioMode turning feature on or off. True is on. False is off. + * @return true or false. + * + * <p>False when device does not support the feature. Otherwise returns true. + */ + protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) { if (!isSystemAudioControlFeatureEnabled()) { HdmiLogger.debug( "Cannot turn " @@ -422,6 +423,17 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) { mService.wakeUp(); } + setSystemAudioMode(newSystemAudioMode); + return true; + } + + /** + * Real work to turn on or off System Audio Mode. + * + * Use {@link #checkSupportAndSetSystemAudioMode(boolean)} + * if trying to turn on or off the feature. + */ + private void setSystemAudioMode(boolean newSystemAudioMode) { int targetPhysicalAddress = getActiveSource().physicalAddress; int port = getLocalPortFromPhysicalAddress(targetPhysicalAddress); if (newSystemAudioMode && port >= 0) { @@ -449,48 +461,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { mService.announceSystemAudioModeChange(newSystemAudioMode); } } - return true; - } - - /** - * Method to parse target physical address to the port number on the current device. - * - * <p>This check assumes target address is valid. - * @param targetPhysicalAddress is the physical address of the target device - * @return - * <p>If the target device is under the current device, return the port number of current device - * that the target device is connected to. - * - * <p>If the target device has the same physical address as the current device, return - * {@link #TARGET_SAME_PHYSICAL_ADDRESS}. - * - * <p>If the target device is not under the current device, return - * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}. - */ - protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) { - int myPhysicalAddress = mService.getPhysicalAddress(); - if (myPhysicalAddress == targetPhysicalAddress) { - return TARGET_SAME_PHYSICAL_ADDRESS; - } - int finalMask = 0xF000; - int mask; - int port = 0; - for (mask = 0x0F00; mask > 0x000F; mask >>= 4) { - if ((myPhysicalAddress & mask) == 0) { - port = mask & targetPhysicalAddress; - break; - } else { - finalMask |= mask; - } - } - if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) { - while (mask != 0x000F) { - mask >>= 4; - port >>= 4; - } - return port; - } - return TARGET_NOT_UNDER_LOCAL_DEVICE; } protected void switchToAudioInput() { @@ -534,7 +504,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { return; } - if (setSystemAudioMode(false)) { + if (checkSupportAndSetSystemAudioMode(false)) { // send <Set System Audio Mode> [“Off”] mService.sendCecCommand( HdmiCecMessageBuilder.buildSetSystemAudioMode( @@ -557,6 +527,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { * <p>The result of the query may be cached until Audio device type is put in standby or loses * its physical address. */ + // TODO(amyjojo): making mTvSystemAudioModeSupport null originally and fix the logic. void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) { if (!mTvSystemAudioModeSupport) { addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback)); @@ -565,6 +536,37 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { } } + /** + * Handler of System Audio Mode Request on from non TV device + */ + void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) { + if (!isSystemAudioControlFeatureEnabled()) { + HdmiLogger.debug( + "Cannot turn on" + "system audio mode " + + "because the System Audio Control feature is disabled."); + mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return; + } + // Wake up device if it is still on standby + if (mService.isPowerStandbyOrTransient()) { + mService.wakeUp(); + } + // Check if TV supports System Audio Control. + // Handle broadcasting setSystemAudioMode on or aborting message on callback. + queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() { + public void onResult(boolean supported) { + if (supported) { + setSystemAudioMode(true); + mService.sendCecCommand( + HdmiCecMessageBuilder.buildSetSystemAudioMode( + mAddress, Constants.ADDR_BROADCAST, true)); + } else { + mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + } + } + }); + } + void setTvSystemAudioModeSupport(boolean supported) { mTvSystemAudioModeSupport = supported; } @@ -588,14 +590,4 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { assertRunOnServiceThread(); mAutoDeviceOff = autoDeviceOff; } - - private void routeToPort(int portId) { - // TODO(AMYJOJO): route to specific input of the port - mLocalActivePath = portId; - } - - @VisibleForTesting - protected int getLocalActivePath() { - return mLocalActivePath; - } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index d45b00bd904b..be7588aad45c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -26,6 +26,7 @@ import android.os.SystemProperties; import android.provider.Settings.Global; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.LocalePicker; import com.android.internal.app.LocalePicker.LocaleInfo; import com.android.internal.util.IndentingPrintWriter; @@ -35,12 +36,10 @@ import java.io.UnsupportedEncodingException; import java.util.List; import java.util.Locale; -import java.util.List; - /** * Represent a logical device of type Playback residing in Android system. */ -final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { +final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { private static final String TAG = "HdmiCecLocalDevicePlayback"; private static final boolean WAKE_ON_HOTPLUG = @@ -62,6 +61,11 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { // If true, turn off TV upon standby. False by default. private boolean mAutoTvOff; + // Local active port number used for Routing Control. + // Default 0 means HOME is the current active path. Temp solution only. + // TODO(amyjojo): adding system constants for input ports to TIF mapping. + private int mLocalActivePath = 0; + HdmiCecLocalDevicePlayback(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_PLAYBACK); @@ -100,25 +104,6 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { } @ServiceThreadOnly - void oneTouchPlay(IHdmiControlCallback callback) { - assertRunOnServiceThread(); - List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class); - if (!actions.isEmpty()) { - Slog.i(TAG, "oneTouchPlay already in progress"); - actions.get(0).addCallback(callback); - return; - } - OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV, - callback); - if (action == null) { - Slog.w(TAG, "Cannot initiate oneTouchPlay"); - invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION); - return; - } - addAndStartAction(action); - } - - @ServiceThreadOnly void queryDisplayStatus(IHdmiControlCallback callback) { assertRunOnServiceThread(); List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class); @@ -227,21 +212,6 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { return !getWakeLock().isHeld(); } - @Override - @ServiceThreadOnly - protected boolean handleActiveSource(HdmiCecMessage message) { - assertRunOnServiceThread(); - int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - mayResetActiveSource(physicalAddress); - return true; // Broadcast message. - } - - private void mayResetActiveSource(int physicalAddress) { - if (physicalAddress != mService.getPhysicalAddress()) { - setActiveSource(false); - } - } - @ServiceThreadOnly protected boolean handleUserControlPressed(HdmiCecMessage message) { assertRunOnServiceThread(); @@ -254,10 +224,21 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { protected boolean handleSetStreamPath(HdmiCecMessage message) { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - maySetActiveSource(physicalAddress); - maySendActiveSource(message.getSource()); - wakeUpIfActiveSource(); - return true; // Broadcast message. + // If current device is the target path, set to Active Source. + // If the path is under the current device, should switch + int port = getLocalPortFromPhysicalAddress(physicalAddress); + if (port == 0) { + setActiveSource(true); + maySendActiveSource(message.getSource()); + wakeUpIfActiveSource(); + } else if (port > 0) { + // Wake up the device if the power is in standby mode for routing + if (mService.isPowerStandbyOrTransient()) { + mService.wakeUp(); + } + routeToPort(port); + } + return true; } // Samsung model we tested sends <Routing Change> and <Request Active Source> @@ -306,14 +287,6 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { } } - @Override - @ServiceThreadOnly - protected boolean handleRequestActiveSource(HdmiCecMessage message) { - assertRunOnServiceThread(); - maySendActiveSource(message.getSource()); - return true; // Broadcast message. - } - @ServiceThreadOnly protected boolean handleSetMenuLanguage(HdmiCecMessage message) { assertRunOnServiceThread(); @@ -383,6 +356,16 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { checkIfPendingActionsCleared(); } + private void routeToPort(int portId) { + // TODO(AMYJOJO): route to specific input of the port + mLocalActivePath = portId; + } + + @VisibleForTesting + protected int getLocalActivePath() { + return mLocalActivePath; + } + @Override protected void dump(final IndentingPrintWriter pw) { super.dump(pw); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java new file mode 100644 index 000000000000..f9180b798a77 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; + +import java.util.List; + +/** + * Represent a logical source device residing in Android system. + */ +abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { + + private static final String TAG = "HdmiCecLocalDeviceSource"; + + private boolean mIsActiveSource = false; + + protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) { + super(service, deviceType); + } + + @Override + @ServiceThreadOnly + void onHotplug(int portId, boolean connected) { + assertRunOnServiceThread(); + mCecMessageCache.flushAll(); + // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3. + if (mService.isPowerStandbyOrTransient()) { + mService.wakeUp(); + } + } + + @ServiceThreadOnly + void oneTouchPlay(IHdmiControlCallback callback) { + assertRunOnServiceThread(); + List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class); + if (!actions.isEmpty()) { + Slog.i(TAG, "oneTouchPlay already in progress"); + actions.get(0).addCallback(callback); + return; + } + OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV, + callback); + if (action == null) { + Slog.w(TAG, "Cannot initiate oneTouchPlay"); + invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION); + return; + } + addAndStartAction(action); + } + + @ServiceThreadOnly + private void invokeCallback(IHdmiControlCallback callback, int result) { + assertRunOnServiceThread(); + try { + callback.onComplete(result); + } catch (RemoteException e) { + Slog.e(TAG, "Invoking callback failed:" + e); + } + } + + @ServiceThreadOnly + protected boolean handleActiveSource(HdmiCecMessage message) { + assertRunOnServiceThread(); + int logicalAddress = message.getSource(); + int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); + ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); + if (physicalAddress != mService.getPhysicalAddress() + || !mActiveSource.equals(activeSource)) { + setActiveSource(activeSource); + setActiveSource(false); + } + return true; + } + + @Override + @ServiceThreadOnly + protected boolean handleRequestActiveSource(HdmiCecMessage message) { + assertRunOnServiceThread(); + if (mIsActiveSource) { + mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource( + mAddress, mService.getPhysicalAddress())); + } + return true; + } + + @ServiceThreadOnly + void setActiveSource(boolean on) { + assertRunOnServiceThread(); + mIsActiveSource = on; + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index e3a4084ab7f3..10f6f922c53c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1830,9 +1830,13 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly private void oneTouchPlay(final IHdmiControlCallback callback) { assertRunOnServiceThread(); - HdmiCecLocalDevicePlayback source = playback(); + HdmiCecLocalDeviceSource source = playback(); if (source == null) { - Slog.w(TAG, "Local playback device not available"); + source = audioSystem(); + } + + if (source == null) { + Slog.w(TAG, "Local source device not available"); invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); return; } diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java index 5c66316da60c..c7ba7ccbd9bb 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java @@ -16,8 +16,8 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiControlManager; -import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback; +import android.hardware.hdmi.IHdmiControlCallback; import android.os.RemoteException; import android.util.Slog; @@ -55,7 +55,7 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { private int mPowerStatusCounter = 0; // Factory method. Ensures arguments are valid. - static OneTouchPlayAction create(HdmiCecLocalDevicePlayback source, + static OneTouchPlayAction create(HdmiCecLocalDeviceSource source, int targetAddress, IHdmiControlCallback callback) { if (source == null || callback == null) { Slog.e(TAG, "Wrong arguments"); @@ -84,8 +84,8 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { private void broadcastActiveSource() { sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath())); - // Because only playback device can create this action, it's safe to cast. - playback().setActiveSource(true); + // Because only source device can create this action, it's safe to cast. + source().setActiveSource(true); } private void queryDevicePowerStatus() { diff --git a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java index 2fdcb5106595..a6e69656a956 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; import android.hardware.tv.cec.V1_0.SendMessageResult; + import com.android.internal.annotations.VisibleForTesting; /** @@ -91,7 +92,7 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { mSendRequestActiveSourceRetryCount++; sendRequestActiveSource(); } else { - audioSystem().setSystemAudioMode(false); + audioSystem().checkSupportAndSetSystemAudioMode(false); finish(); } } @@ -106,7 +107,7 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { mSendSetSystemAudioModeRetryCount++; sendSetSystemAudioMode(on, dest); } else { - audioSystem().setSystemAudioMode(false); + audioSystem().checkSupportAndSetSystemAudioMode(false); finish(); } } @@ -115,7 +116,7 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { private void handleActiveSourceTimeout() { HdmiLogger.debug("Cannot get active source."); - audioSystem().setSystemAudioMode(false); + audioSystem().checkSupportAndSetSystemAudioMode(false); finish(); } @@ -123,12 +124,12 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { audioSystem().queryTvSystemAudioModeSupport( supported -> { if (supported) { - if (audioSystem().setSystemAudioMode(true)) { + if (audioSystem().checkSupportAndSetSystemAudioMode(true)) { sendSetSystemAudioMode(true, Constants.ADDR_BROADCAST); } finish(); } else { - audioSystem().setSystemAudioMode(false); + audioSystem().checkSupportAndSetSystemAudioMode(false); finish(); } }); diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index d96b6cba119b..e7c3c7bbe21b 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -1951,6 +1951,11 @@ public class InputManagerService extends IInputManager.Stub } // Native callback. + private int getPointerDisplayId() { + return mWindowManagerCallbacks.getPointerDisplayId(); + } + + // Native callback. private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) { if (!mSystemReady) { return null; @@ -2017,6 +2022,8 @@ public class InputManagerService extends IInputManager.Stub KeyEvent event, int policyFlags); public int getPointerLayer(); + + public int getPointerDisplayId(); } /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d45869e07586..840c6e48a190 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1674,40 +1674,93 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public List<InputMethodInfo> getInputMethodList() { - return getInputMethodList(false /* isVrOnly */); + final int callingUserId = UserHandle.getCallingUserId(); + synchronized (mMethodMap) { + final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, + mSettings.getCurrentUserId(), null); + if (resolvedUserIds.length != 1) { + return Collections.emptyList(); + } + final long ident = Binder.clearCallingIdentity(); + try { + return getInputMethodListLocked(false /* isVrOnly */, resolvedUserIds[0]); + } finally { + Binder.restoreCallingIdentity(ident); + } + } } @Override public List<InputMethodInfo> getVrInputMethodList() { - return getInputMethodList(true /* isVrOnly */); - } - - private List<InputMethodInfo> getInputMethodList(final boolean isVrOnly) { + final int callingUserId = UserHandle.getCallingUserId(); synchronized (mMethodMap) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked()) { + final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, + mSettings.getCurrentUserId(), null); + if (resolvedUserIds.length != 1) { return Collections.emptyList(); } - ArrayList<InputMethodInfo> methodList = new ArrayList<>(); - for (InputMethodInfo info : mMethodList) { - - if (info.isVrOnly() == isVrOnly) { - methodList.add(info); - } + final long ident = Binder.clearCallingIdentity(); + try { + return getInputMethodListLocked(true /* isVrOnly */, resolvedUserIds[0]); + } finally { + Binder.restoreCallingIdentity(ident); } - return methodList; } } @Override public List<InputMethodInfo> getEnabledInputMethodList() { + final int callingUserId = UserHandle.getCallingUserId(); synchronized (mMethodMap) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked()) { + final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, + mSettings.getCurrentUserId(), null); + if (resolvedUserIds.length != 1) { return Collections.emptyList(); } + final long ident = Binder.clearCallingIdentity(); + try { + return getEnabledInputMethodListLocked(resolvedUserIds[0]); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + @GuardedBy("mMethodMap") + private List<InputMethodInfo> getInputMethodListLocked(boolean isVrOnly, + @UserIdInt int userId) { + final ArrayList<InputMethodInfo> methodList; + if (userId == mSettings.getCurrentUserId()) { + // Create a copy. + methodList = new ArrayList<>(mMethodList); + } else { + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + methodList = new ArrayList<>(); + final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = + new ArrayMap<>(); + AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); + queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, + methodList); + } + methodList.removeIf(imi -> imi.isVrOnly() != isVrOnly); + return methodList; + } + + @GuardedBy("mMethodMap") + private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId) { + if (userId == mSettings.getCurrentUserId()) { return mSettings.getEnabledInputMethodListLocked(); } + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); + final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = + new ArrayMap<>(); + AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); + queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, + methodList); + final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), + mContext.getContentResolver(), methodMap, methodList, userId, true); + return settings.getEnabledInputMethodListLocked(); } /** @@ -1717,11 +1770,27 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, boolean allowsImplicitlySelectedSubtypes) { + final int callingUserId = UserHandle.getCallingUserId(); synchronized (mMethodMap) { - // TODO: Make this work even for non-current users? - if (!calledFromValidUserLocked()) { + final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, + mSettings.getCurrentUserId(), null); + if (resolvedUserIds.length != 1) { return Collections.emptyList(); } + final long ident = Binder.clearCallingIdentity(); + try { + return getEnabledInputMethodSubtypeListLocked(imiId, + allowsImplicitlySelectedSubtypes, resolvedUserIds[0]); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + @GuardedBy("mMethodMap") + private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId, + boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) { + if (userId == mSettings.getCurrentUserId()) { final InputMethodInfo imi; if (imiId == null && mCurMethodId != null) { imi = mMethodMap.get(mCurMethodId); @@ -1734,6 +1803,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mSettings.getEnabledInputMethodSubtypeListLocked( mContext, imi, allowsImplicitlySelectedSubtypes); } + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); + final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); + final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = + new ArrayMap<>(); + AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); + queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, + methodList); + final InputMethodInfo imi = methodMap.get(imiId); + if (imi == null) { + return Collections.emptyList(); + } + final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), + mContext.getContentResolver(), methodMap, methodList, userId, true); + return settings.getEnabledInputMethodSubtypeListLocked( + mContext, imi, allowsImplicitlySelectedSubtypes); } /** @@ -4545,6 +4629,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) { boolean all = false; boolean brief = false; + int userIdToBeResolved = UserHandle.USER_CURRENT; while (true) { final String nextOption = shellCommand.getNextOption(); if (nextOption == null) { @@ -4557,19 +4642,34 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case "-s": brief = true; break; + case "-u": + case "--user": + userIdToBeResolved = UserHandle.parseUserArg(shellCommand.getNextArgRequired()); + break; } } - final List<InputMethodInfo> methods = all ? - getInputMethodList() : getEnabledInputMethodList(); - final PrintWriter pr = shellCommand.getOutPrintWriter(); - final Printer printer = x -> pr.println(x); - final int N = methods.size(); - for (int i = 0; i < N; ++i) { - if (brief) { - pr.println(methods.get(i).getId()); - } else { - pr.print(methods.get(i).getId()); pr.println(":"); - methods.get(i).dump(printer, " "); + synchronized (mMethodMap) { + final PrintWriter pr = shellCommand.getOutPrintWriter(); + final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, + mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); + for (int userId : userIds) { + final List<InputMethodInfo> methods = all + ? getInputMethodListLocked(false, userId) + : getEnabledInputMethodListLocked(userId); + if (userIds.length > 1) { + pr.print("User #"); + pr.print(userId); + pr.println(":"); + } + for (InputMethodInfo info : methods) { + if (brief) { + pr.println(info.getId()); + } else { + pr.print(info.getId()); + pr.println(":"); + info.dump(pr::println, " "); + } + } } } return ShellCommandResult.SUCCESS; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 1137bf967d24..2f7687148e2e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -29,9 +29,12 @@ import android.content.res.Resources; import android.os.Build; import android.os.LocaleList; import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManagerInternal; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.IntArray; import android.util.Pair; import android.util.Printer; import android.util.Slog; @@ -42,8 +45,10 @@ import android.view.textservice.SpellCheckerInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.StartInputFlags; +import com.android.server.LocalServices; import com.android.server.textservices.TextServicesManagerInternal; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; @@ -1286,4 +1291,56 @@ final class InputMethodUtils { return true; } + /** + * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a + * list of real user IDs. + * + * <p>Currently this method also converts profile user ID to profile parent user ID.</p> + * + * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and + * {@link UserHandle#USER_ALL} are also supported + * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT} + * is specified in {@code userIdToBeResolved}. + * @param warningWriter A {@link PrintWriter} to output some debug messages. {@code null} if + * no debug message is required. + * @return An integer array that contain user IDs. + */ + static int[] resolveUserId(@UserIdInt int userIdToBeResolved, + @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter) { + final UserManagerInternal userManagerInternal = + LocalServices.getService(UserManagerInternal.class); + + if (userIdToBeResolved == UserHandle.USER_ALL) { + final IntArray result = new IntArray(); + for (int userId : userManagerInternal.getUserIds()) { + final int parentUserId = userManagerInternal.getProfileParentId(userId); + if (result.indexOf(parentUserId) < 0) { + result.add(parentUserId); + } + } + return result.toArray(); + } + + final int sourceUserId; + if (userIdToBeResolved == UserHandle.USER_CURRENT) { + sourceUserId = currentUserId; + } else if (userIdToBeResolved < 0) { + if (warningWriter != null) { + warningWriter.print("Pseudo user ID "); + warningWriter.print(userIdToBeResolved); + warningWriter.println(" is not supported."); + } + return new int[]{}; + } else if (userManagerInternal.exists(userIdToBeResolved)) { + sourceUserId = userIdToBeResolved; + } else { + if (warningWriter != null) { + warningWriter.print("User #"); + warningWriter.print(userIdToBeResolved); + warningWriter.println(" does not exit."); + } + return new int[]{}; + } + return new int[]{userManagerInternal.getProfileParentId(sourceUserId)}; + } } diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java index 4c7c420214bd..b3f101848692 100644 --- a/services/core/java/com/android/server/location/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java @@ -29,7 +29,7 @@ import java.io.PrintWriter; import java.util.List; /** - * Location Manager's interface for location providers. + * Location Manager's interface for location providers. Always starts as disabled. * * @hide */ @@ -41,12 +41,6 @@ public abstract class AbstractLocationProvider { public interface LocationProviderManager { /** - * Called on location provider construction to make the location service aware of this - * provider and what it's initial enabled/disabled state should be. - */ - void onAttachProvider(AbstractLocationProvider locationProvider, boolean initiallyEnabled); - - /** * May be called to inform the location service of a change in this location provider's * enabled/disabled state. */ @@ -74,13 +68,7 @@ public abstract class AbstractLocationProvider { private final LocationProviderManager mLocationProviderManager; protected AbstractLocationProvider(LocationProviderManager locationProviderManager) { - this(locationProviderManager, true); - } - - protected AbstractLocationProvider(LocationProviderManager locationProviderManager, - boolean initiallyEnabled) { mLocationProviderManager = locationProviderManager; - mLocationProviderManager.onAttachProvider(this, initiallyEnabled); } /** diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 3c81a4569407..269767ac3be2 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -559,7 +559,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager, Looper looper) { - super(locationProviderManager, true); + super(locationProviderManager); mContext = context; @@ -652,6 +652,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements }, UserHandle.ALL, intentFilter, null, mHandler); setProperties(PROPERTIES); + setEnabled(true); } /** diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index dfcef70c8248..a6da8c5e7713 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -101,7 +101,7 @@ public class LocationProviderProxy extends AbstractLocationProvider { private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager, String action, int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - super(locationProviderManager, false); + super(locationProviderManager); mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId, diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java index bfbebf74e93d..86bc9f313551 100644 --- a/services/core/java/com/android/server/location/MockProvider.java +++ b/services/core/java/com/android/server/location/MockProvider.java @@ -43,7 +43,7 @@ public class MockProvider extends AbstractLocationProvider { public MockProvider( LocationProviderManager locationProviderManager, ProviderProperties properties) { - super(locationProviderManager, true); + super(locationProviderManager); mEnabled = true; mLocation = null; @@ -52,6 +52,7 @@ public class MockProvider extends AbstractLocationProvider { mExtras = null; setProperties(properties); + setEnabled(true); } /** Sets the enabled state of this mock provider. */ @@ -63,8 +64,11 @@ public class MockProvider extends AbstractLocationProvider { /** Sets the location to report for this mock provider. */ public void setLocation(Location l) { mLocation = new Location(l); + if (!mLocation.isFromMockProvider()) { + mLocation.setIsFromMockProvider(true); + } if (mEnabled) { - reportLocation(l); + reportLocation(mLocation); } } diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java index 70d64b02e4b4..30260b2fe14c 100644 --- a/services/core/java/com/android/server/location/PassiveProvider.java +++ b/services/core/java/com/android/server/location/PassiveProvider.java @@ -43,11 +43,12 @@ public class PassiveProvider extends AbstractLocationProvider { private boolean mReportLocation; public PassiveProvider(LocationProviderManager locationProviderManager) { - super(locationProviderManager, true); + super(locationProviderManager); mReportLocation = false; setProperties(PROPERTIES); + setEnabled(true); } @Override diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index b70c64edc2cc..8ecceb9664f5 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -116,7 +116,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private boolean mIsActive = false; private boolean mDestroyed = false; - private long mDuration; + private long mDuration = -1; private String mMetadataDescription; public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName, diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 052d5797c1e7..7f2e047d7b99 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -40,6 +40,8 @@ import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; import android.media.IAudioService; import android.media.IRemoteVolumeController; +import android.media.MediaController2; +import android.media.Session2CommandGroup; import android.media.Session2Token; import android.media.session.IActiveSessionsListener; import android.media.session.ICallback; @@ -54,6 +56,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; @@ -73,6 +76,7 @@ import android.util.SparseIntArray; import android.view.KeyEvent; import android.view.ViewConfiguration; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -97,17 +101,23 @@ public class MediaSessionService extends SystemService implements Monitor { private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000; private final SessionManagerImpl mSessionManagerImpl; - + private final MessageHandler mHandler = new MessageHandler(); + private final PowerManager.WakeLock mMediaEventWakeLock; + private final int mLongPressTimeout; + private final INotificationManager mNotificationManager; + private final Object mLock = new Object(); // Keeps the full user id for each user. + @GuardedBy("mLock") private final SparseIntArray mFullUserIds = new SparseIntArray(); + @GuardedBy("mLock") private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<FullUserRecord>(); + @GuardedBy("mLock") private final ArrayList<SessionsListenerRecord> mSessionsListeners = new ArrayList<SessionsListenerRecord>(); - private final Object mLock = new Object(); - private final MessageHandler mHandler = new MessageHandler(); - private final PowerManager.WakeLock mMediaEventWakeLock; - private final int mLongPressTimeout; - private final INotificationManager mNotificationManager; + // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in + // one place. + @GuardedBy("mLock") + private final List<Session2Token> mSession2Tokens = new ArrayList<>(); private KeyguardManager mKeyguardManager; private IAudioService mAudioService; @@ -722,6 +732,10 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println(indent + "Restored MediaButtonReceiverComponentType: " + mRestoredMediaButtonReceiverComponentType); mPriorityStack.dump(pw, indent); + pw.println(indent + "Session2Tokens - " + mSession2Tokens.size()); + for (Session2Token session2Token : mSession2Tokens) { + pw.println(indent + " " + session2Token); + } } @Override @@ -904,7 +918,17 @@ public class MediaSessionService extends SystemService implements Monitor { if (DEBUG) { Log.d(TAG, "Session2 is created " + sessionToken); } - // TODO: Keep the session. + if (uid != sessionToken.getUid()) { + throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid + + " but actually=" + sessionToken.getUid()); + } + Controller2Callback callback = new Controller2Callback(sessionToken); + // Note: It's safe not to keep controller here because it wouldn't be GC'ed until + // it's closed. + // TODO: Keep controller as well for better readability + // because the GC behavior isn't straightforward. + MediaController2 controller = new MediaController2(getContext(), sessionToken, + new HandlerExecutor(mHandler), callback); } finally { Binder.restoreCallingIdentity(token); } @@ -1930,4 +1954,26 @@ public class MediaSessionService extends SystemService implements Monitor { obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget(); } } + + private class Controller2Callback extends MediaController2.ControllerCallback { + private final Session2Token mToken; + + Controller2Callback(Session2Token token) { + mToken = token; + } + + @Override + public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) { + synchronized (mLock) { + mSession2Tokens.add(mToken); + } + } + + @Override + public void onDisconnected(MediaController2 controller) { + synchronized (mLock) { + mSession2Tokens.remove(mToken); + } + } + } } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 23293567be46..48ee9dcae0b6 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -18,6 +18,7 @@ package com.android.server.pm; import android.annotation.NonNull; import android.apex.ApexInfo; +import android.apex.ApexInfoList; import android.apex.IApexService; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; @@ -127,28 +128,55 @@ public class StagingManager { return false; } - void commitSession(@NonNull PackageInstallerSession sessionInfo) { - updateStoredSession(sessionInfo); + private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) { + final IApexService apex = IApexService.Stub.asInterface( + ServiceManager.getService("apexservice")); + boolean success; + try { + success = apex.submitStagedSession(sessionId, apexInfoList); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to contact apexservice", re); + return false; + } + return success; + } - mBgHandler.post(() -> { - sessionInfo.setStagedSessionReady(); - - SessionInfo session = sessionInfo.generateInfo(false); - // For APEXes, we validate the signature here before we write the package to the - // staging directory. For APKs, the signature verification will be done by the package - // manager at the point at which it applies the staged install. - // - // TODO: Decide whether we want to fail fast by detecting signature mismatches right - // away. - if ((sessionInfo.params.installFlags & PackageManager.INSTALL_APEX) != 0) { - if (!validateApexSignatureLocked(session.resolvedBaseCodePath, - session.appPackageName)) { - sessionInfo.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED); + void preRebootVerification(@NonNull PackageInstallerSession session) { + boolean success = true; + if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) { + + final ApexInfoList apexInfoList = new ApexInfoList(); + + if (!submitSessionToApexService(session.sessionId, apexInfoList)) { + success = false; + } else { + // For APEXes, we validate the signature here before we mark the session as ready, + // so we fail the session early if there is a signature mismatch. For APKs, the + // signature verification will be done by the package manager at the point at which + // it applies the staged install. + // + // TODO: Decide whether we want to fail fast by detecting signature mismatches right + // away. + for (ApexInfo apexPackage : apexInfoList.apexInfos) { + if (!validateApexSignatureLocked(apexPackage.packagePath, + apexPackage.packageName)) { + success = false; + break; + } } } + } + if (success) { + session.setStagedSessionReady(); + } else { + session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED); + } + mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId); + } - mPm.sendSessionUpdatedBroadcast(sessionInfo.generateInfo(false), sessionInfo.userId); - }); + void commitSession(@NonNull PackageInstallerSession session) { + updateStoredSession(session); + mBgHandler.post(() -> preRebootVerification(session)); } void createSession(@NonNull PackageInstallerSession sessionInfo) { diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index dd04652a29b3..aaa187468f8d 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -620,9 +620,6 @@ public class UserRestrictionsUtils { && callingUid != Process.SYSTEM_UID) { return true; } else if (String.valueOf(Settings.Secure.LOCATION_MODE_OFF).equals(value)) { - // Note LOCATION_MODE will be converted into LOCATION_PROVIDERS_ALLOWED - // in android.provider.Settings.Secure.putStringForUser(), so we shouldn't come - // here normally, but we still protect it here from a direct provider write. return false; } restriction = UserManager.DISALLOW_SHARE_LOCATION; diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index fc21adbdcca3..7c1e6198080d 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -598,11 +598,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onBiometricAuthenticated() { + public void onBiometricAuthenticated(boolean authenticated) { enforceBiometricDialog(); if (mBar != null) { try { - mBar.onBiometricAuthenticated(); + mBar.onBiometricAuthenticated(authenticated); } catch (RemoteException ex) { } } @@ -641,17 +641,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } } - @Override - public void showBiometricTryAgain() { - enforceBiometricDialog(); - if (mBar != null) { - try { - mBar.showBiometricTryAgain(); - } catch (RemoteException ex) { - } - } - } - // TODO(b/117478341): make it aware of multi-display if needed. @Override public void disable(int what, IBinder token, String pkg) { diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 639ed02a1e48..f9c9d33c561a 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -1,5 +1,6 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -9,7 +10,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.os.Debug; import android.os.IBinder; import android.util.Slog; -import android.view.InputApplicationHandle; import android.view.KeyEvent; import android.view.WindowManager; @@ -204,6 +204,37 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal + WindowManagerService.TYPE_LAYER_OFFSET; } + /** Callback to get pointer display id. */ + @Override + public int getPointerDisplayId() { + synchronized (mService.mGlobalLock) { + // If desktop mode is not enabled, show on the default display. + if (!mService.mForceDesktopModeOnExternalDisplays) { + return DEFAULT_DISPLAY; + } + + // Look for the topmost freeform display. + int firstExternalDisplayId = DEFAULT_DISPLAY; + for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) { + final DisplayContent displayContent = mService.mRoot.mChildren.get(i); + // Heuristic solution here. Currently when "Freeform windows" developer option is + // enabled we automatically put secondary displays in freeform mode and emulating + // "desktop mode". It also makes sense to show the pointer on the same display. + if (displayContent.getWindowingMode() == WINDOWING_MODE_FREEFORM) { + return displayContent.getDisplayId(); + } + + if (firstExternalDisplayId == DEFAULT_DISPLAY + && displayContent.getDisplayId() != DEFAULT_DISPLAY) { + firstExternalDisplayId = displayContent.getDisplayId(); + } + } + + // Look for the topmost non-default display + return firstExternalDisplayId; + } + } + /** Waits until the built-in input devices have been configured. */ public boolean waitForInputDevicesReady(long timeoutMillis) { synchronized (mInputDevicesReadyMonitor) { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index b4fe83704ff2..c8c5e8f136af 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -107,6 +107,7 @@ cc_defaults { "android.hardware.gnss@1.0", "android.hardware.gnss@1.1", "android.hardware.gnss@2.0", + "android.hardware.input.classifier@1.0", "android.hardware.ir@1.0", "android.hardware.light@2.0", "android.hardware.power@1.0", @@ -119,6 +120,7 @@ cc_defaults { "android.hardware.vibrator@1.0", "android.hardware.vibrator@1.1", "android.hardware.vibrator@1.2", + "android.hardware.vibrator@1.3", "android.hardware.vr@1.0", "android.frameworks.schedulerservice@1.0", "android.frameworks.sensorservice@1.0", diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp index defcfd9a3370..63dca62104a1 100644 --- a/services/core/jni/com_android_server_VibratorService.cpp +++ b/services/core/jni/com_android_server_VibratorService.cpp @@ -22,6 +22,7 @@ #include <android/hardware/vibrator/1.1/types.h> #include <android/hardware/vibrator/1.2/IVibrator.h> #include <android/hardware/vibrator/1.2/types.h> +#include <android/hardware/vibrator/1.3/IVibrator.h> #include "jni.h" #include <nativehelper/JNIHelp.h> @@ -42,6 +43,7 @@ using android::hardware::vibrator::V1_1::Effect_1_1; namespace V1_0 = android::hardware::vibrator::V1_0; namespace V1_1 = android::hardware::vibrator::V1_1; namespace V1_2 = android::hardware::vibrator::V1_2; +namespace V1_3 = android::hardware::vibrator::V1_3; namespace android { @@ -136,6 +138,19 @@ static void vibratorSetAmplitude(JNIEnv*, jobject, jint amplitude) { } } +static jboolean vibratorSupportsExternalControl(JNIEnv*, jobject) { + return halCall(&V1_3::IVibrator::supportsExternalControl).withDefault(false); +} + +static void vibratorSetExternalControl(JNIEnv*, jobject, jboolean enabled) { + Status status = halCall(&V1_3::IVibrator::setExternalControl, static_cast<uint32_t>(enabled)) + .withDefault(Status::UNKNOWN_ERROR); + if (status != Status::OK) { + ALOGE("Failed to set vibrator external control (%" PRIu32 ").", + static_cast<uint32_t>(status)); + } +} + static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) { Status status; uint32_t lengthMs; @@ -187,7 +202,9 @@ static const JNINativeMethod method_table[] = { { "vibratorOff", "()V", (void*)vibratorOff }, { "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl}, { "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude}, - { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect} + { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}, + { "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl}, + { "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl}, }; int register_android_server_VibratorService(JNIEnv *env) diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 43d2dcf7e0d1..0929e20c9187 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -107,6 +107,7 @@ static struct { jmethodID getLongPressTimeout; jmethodID getPointerLayer; jmethodID getPointerIcon; + jmethodID getPointerDisplayId; jmethodID getKeyboardLayoutOverlay; jmethodID getDeviceAlias; jmethodID getTouchCalibrationForInputDevice; @@ -174,15 +175,6 @@ static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t styl loadSystemIconAsSpriteWithPointerIcon(env, contextObj, style, &pointerIcon, outSpriteIcon); } -static void updatePointerControllerFromViewport( - sp<PointerController> controller, const DisplayViewport* const viewport) { - if (controller != nullptr && viewport != nullptr) { - const int32_t width = viewport->logicalRight - viewport->logicalLeft; - const int32_t height = viewport->logicalBottom - viewport->logicalTop; - controller->setDisplayViewport(width, height, viewport->orientation); - } -} - enum { WM_ACTION_PASS_TO_USER = 1, }; @@ -310,14 +302,19 @@ private: // Input devices to be disabled SortedVector<int32_t> disabledInputDevices; + + // Associated Pointer controller display. + int32_t pointerDisplayId; } mLocked GUARDED_BY(mLock); std::atomic<bool> mInteractive; - void updateInactivityTimeoutLocked(const sp<PointerController>& controller); + void updateInactivityTimeoutLocked(); void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); void ensureSpriteControllerLocked(); - + const DisplayViewport* findDisplayViewportLocked(int32_t displayId); + int32_t getPointerDisplayId(); + void updatePointerDisplayLocked(); static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); static inline JNIEnv* jniEnv() { @@ -342,6 +339,7 @@ NativeInputManager::NativeInputManager(jobject contextObj, mLocked.pointerGesturesEnabled = true; mLocked.showTouches = false; mLocked.pointerCapture = false; + mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT; } mInteractive = true; @@ -391,9 +389,10 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c return false; } -static const DisplayViewport* findInternalViewport(const std::vector<DisplayViewport>& viewports) { - for (const DisplayViewport& v : viewports) { - if (v.type == ViewportType::VIEWPORT_INTERNAL) { +const DisplayViewport* NativeInputManager::findDisplayViewportLocked(int32_t displayId) + REQUIRES(mLock) { + for (const DisplayViewport& v : mLocked.viewports) { + if (v.displayId == displayId) { return &v; } } @@ -420,20 +419,14 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO } } - const DisplayViewport* newInternalViewport = findInternalViewport(viewports); - { + // Get the preferred pointer controller displayId. + int32_t pointerDisplayId = getPointerDisplayId(); + + { // acquire lock AutoMutex _l(mLock); - const DisplayViewport* oldInternalViewport = findInternalViewport(mLocked.viewports); - // Internal viewport has changed if there wasn't one earlier, and there is one now, or, - // if they are different. - const bool internalViewportChanged = (newInternalViewport != nullptr) && - (oldInternalViewport == nullptr || (*oldInternalViewport != *newInternalViewport)); - if (internalViewportChanged) { - sp<PointerController> controller = mLocked.pointerController.promote(); - updatePointerControllerFromViewport(controller, newInternalViewport); - } mLocked.viewports = viewports; - } + mLocked.pointerDisplayId = pointerDisplayId; + } // release lock mInputManager->getReader()->requestRefreshConfiguration( InputReaderConfiguration::CHANGE_DISPLAY_INFO); @@ -556,15 +549,42 @@ sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32 controller = new PointerController(this, mLooper, mLocked.spriteController); mLocked.pointerController = controller; + updateInactivityTimeoutLocked(); + } - const DisplayViewport* internalViewport = findInternalViewport(mLocked.viewports); - updatePointerControllerFromViewport(controller, internalViewport); + updatePointerDisplayLocked(); - updateInactivityTimeoutLocked(controller); - } return controller; } +int32_t NativeInputManager::getPointerDisplayId() { + JNIEnv* env = jniEnv(); + jint pointerDisplayId = env->CallIntMethod(mServiceObj, + gServiceClassInfo.getPointerDisplayId); + if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) { + pointerDisplayId = ADISPLAY_ID_DEFAULT; + } + + return pointerDisplayId; +} + +void NativeInputManager::updatePointerDisplayLocked() REQUIRES(mLock) { + ATRACE_CALL(); + + sp<PointerController> controller = mLocked.pointerController.promote(); + if (controller != nullptr) { + const DisplayViewport* viewport = findDisplayViewportLocked(mLocked.pointerDisplayId); + if (viewport == nullptr) { + ALOGW("Can't find pointer display viewport, fallback to default display."); + viewport = findDisplayViewportLocked(ADISPLAY_ID_DEFAULT); + } + + if (viewport != nullptr) { + controller->setDisplayViewport(*viewport); + } + } +} + void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) { if (mLocked.spriteController == nullptr) { JNIEnv* env = jniEnv(); @@ -821,16 +841,16 @@ void NativeInputManager::setSystemUiVisibility(int32_t visibility) { if (mLocked.systemUiVisibility != visibility) { mLocked.systemUiVisibility = visibility; - - sp<PointerController> controller = mLocked.pointerController.promote(); - if (controller != nullptr) { - updateInactivityTimeoutLocked(controller); - } + updateInactivityTimeoutLocked(); } } -void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) - REQUIRES(mLock) { +void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { + sp<PointerController> controller = mLocked.pointerController.promote(); + if (controller == nullptr) { + return; + } + bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN; controller->setInactivityTimeout(lightsOut ? PointerController::INACTIVITY_TIMEOUT_SHORT @@ -1824,6 +1844,9 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz, "getPointerIcon", "()Landroid/view/PointerIcon;"); + GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz, + "getPointerDisplayId", "()I"); + GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz, "getKeyboardLayoutOverlay", "(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;"); diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 5e84ab0d66f7..7dac79599ac6 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -71,6 +71,7 @@ import static java.util.stream.Collectors.toList; import android.annotation.Nullable; import android.app.Application; +import android.app.ApplicationPackageManager; import android.app.IBackupAgent; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; @@ -134,6 +135,8 @@ import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowQueuedWork; @@ -157,6 +160,7 @@ import java.util.stream.Stream; @Config( shadows = { FrameworkShadowLooper.class, + KeyValueBackupTaskTest.ShadowApplicationPackageManager.class, ShadowBackupDataInput.class, ShadowBackupDataOutput.class, ShadowEventLog.class, @@ -244,6 +248,7 @@ public class KeyValueBackupTaskTest { @After public void tearDown() throws Exception { ShadowBackupDataInput.reset(); + ShadowApplicationPackageManager.reset(); } @Test @@ -2435,7 +2440,8 @@ public class KeyValueBackupTaskTest { mPackageManager.setApplicationEnabledSetting( packageData.packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); PackageInfo packageInfo = getPackageInfo(packageData); - mShadowPackageManager.addPackage(packageInfo); + mShadowPackageManager.installPackage(packageInfo); + ShadowApplicationPackageManager.setPackageInfo(packageInfo); mContext.sendBroadcast(getPackageAddedIntent(packageData)); // Run the backup looper because on the receiver we post MSG_SCHEDULE_BACKUP_PACKAGE mShadowBackupLooper.runToEndOfTasks(); @@ -2848,4 +2854,29 @@ public class KeyValueBackupTaskTest { throw mException; } } + + /** + * Extends {@link org.robolectric.shadows.ShadowApplicationPackageManager} to return the correct + * package in user-specific invocations. + */ + @Implements(value = ApplicationPackageManager.class) + public static class ShadowApplicationPackageManager + extends org.robolectric.shadows.ShadowApplicationPackageManager { + private static PackageInfo sPackageInfo; + + static void setPackageInfo(PackageInfo packageInfo) { + sPackageInfo = packageInfo; + } + + @Override + protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId) { + return sPackageInfo; + } + + /** Clear {@link #sPackageInfo}. */ + @Resetter + public static void reset() { + sPackageInfo = null; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index bdede3354ef7..7049b215083a 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -17,6 +17,7 @@ package com.android.server.hdmi; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; +import static com.android.server.hdmi.Constants.ADDR_TUNER_1; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; @@ -64,70 +65,71 @@ public class HdmiCecLocalDeviceAudioSystemTest { @Before public void setUp() { mHdmiControlService = - new HdmiControlService(InstrumentationRegistry.getTargetContext()) { - @Override - AudioManager getAudioManager() { - return new AudioManager() { - @Override - public int getStreamVolume(int streamType) { - switch (streamType) { - case STREAM_MUSIC: - return mMusicVolume; - default: - return 0; - } + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + @Override + AudioManager getAudioManager() { + return new AudioManager() { + @Override + public int getStreamVolume(int streamType) { + switch (streamType) { + case STREAM_MUSIC: + return mMusicVolume; + default: + return 0; } - - @Override - public boolean isStreamMute(int streamType) { - switch (streamType) { - case STREAM_MUSIC: - return mMusicMute; - default: - return false; - } + } + + @Override + public boolean isStreamMute(int streamType) { + switch (streamType) { + case STREAM_MUSIC: + return mMusicMute; + default: + return false; } - - @Override - public int getStreamMaxVolume(int streamType) { - switch (streamType) { - case STREAM_MUSIC: - return mMusicMaxVolume; - default: - return 100; - } + } + + @Override + public int getStreamMaxVolume(int streamType) { + switch (streamType) { + case STREAM_MUSIC: + return mMusicMaxVolume; + default: + return 100; } - - @Override - public void adjustStreamVolume( - int streamType, int direction, int flags) { - switch (streamType) { - case STREAM_MUSIC: - if (direction == AudioManager.ADJUST_UNMUTE) { - mMusicMute = false; - } else if (direction == AudioManager.ADJUST_MUTE) { - mMusicMute = true; - } - default: - } + } + + @Override + public void adjustStreamVolume( + int streamType, int direction, int flags) { + switch (streamType) { + case STREAM_MUSIC: + if (direction == AudioManager.ADJUST_UNMUTE) { + mMusicMute = false; + } else if (direction == AudioManager.ADJUST_MUTE) { + mMusicMute = true; + } + break; + default: } + } - @Override - public void setWiredDeviceConnectionState( - int type, int state, String address, String name) { - // Do nothing. - } - }; - } + @Override + public void setWiredDeviceConnectionState( + int type, int state, String address, String name) { + // Do nothing. + } + }; + } - @Override - void wakeUp() {} + @Override + void wakeUp() {} - @Override - boolean isControlEnabled() { - return true; - } - }; + @Override + boolean isControlEnabled() { + return true; + } + }; mMyLooper = mTestLooper.getLooper(); mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService); @@ -135,7 +137,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiControlService.setIoLooper(mMyLooper); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = - HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); + HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); @@ -170,11 +172,12 @@ public class HdmiCecLocalDeviceAudioSystemTest { @Test public void handleGiveSystemAudioModeStatus_originalOff() throws Exception { HdmiCecMessage expectedMessage = - HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); + HdmiCecMessageBuilder.buildReportSystemAudioMode( + ADDR_AUDIO_SYSTEM, ADDR_TV, false); HdmiCecMessage messageGive = HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -190,9 +193,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false); assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( - MESSAGE_REQUEST_SAD_LCPM)) - .isTrue(); + mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + MESSAGE_REQUEST_SAD_LCPM)) + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -206,11 +209,11 @@ public class HdmiCecLocalDeviceAudioSystemTest { Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, Constants.ABORT_NOT_IN_CORRECT_MODE); - mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false); + mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false); assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( - MESSAGE_REQUEST_SAD_LCPM)) - .isEqualTo(true); + mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + MESSAGE_REQUEST_SAD_LCPM)) + .isEqualTo(true); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -224,11 +227,11 @@ public class HdmiCecLocalDeviceAudioSystemTest { Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, Constants.ABORT_UNABLE_TO_DETERMINE); - mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true); + mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true); assertThat( - mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( - MESSAGE_REQUEST_SAD_LCPM)) - .isEqualTo(true); + mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor( + MESSAGE_REQUEST_SAD_LCPM)) + .isEqualTo(true); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } @@ -244,17 +247,17 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); // Check if correctly turned on mNativeWrapper.clearResultMessages(); expectedMessage = - HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true); + HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true); assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)).isTrue(); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); assertThat(mMusicMute).isFalse(); @@ -273,15 +276,15 @@ public class HdmiCecLocalDeviceAudioSystemTest { HdmiCecMessageBuilder.buildSetSystemAudioMode( ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff)) - .isTrue(); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); mNativeWrapper.clearResultMessages(); expectedMessage = - HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); + HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isTrue(); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); assertThat(mMusicMute).isTrue(); @@ -292,7 +295,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.setAutoDeviceOff(false); mHdmiCecLocalDeviceAudioSystem.setAutoTvOff(false); // Set system audio control on first - mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true); + mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true); // Check if standby correctly turns off the feature mHdmiCecLocalDeviceAudioSystem.onStandby(false, STANDBY_SCREEN_OFF); mTestLooper.dispatchAll(); @@ -309,9 +312,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true); assertThat( - mHdmiCecLocalDeviceAudioSystem.getActions( - SystemAudioInitiationActionFromAvr.class)) - .isNotEmpty(); + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isNotEmpty(); } @Test @@ -320,9 +323,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false); assertThat( - mHdmiCecLocalDeviceAudioSystem.getActions( - SystemAudioInitiationActionFromAvr.class)) - .isEmpty(); + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isEmpty(); } @Test @@ -331,9 +334,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false); assertThat( - mHdmiCecLocalDeviceAudioSystem.getActions( - SystemAudioInitiationActionFromAvr.class)) - .isEmpty(); + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isEmpty(); } @Test @@ -342,9 +345,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true); assertThat( - mHdmiCecLocalDeviceAudioSystem.getActions( - SystemAudioInitiationActionFromAvr.class)) - .isNotEmpty(); + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isNotEmpty(); } @Test @@ -354,12 +357,12 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue(); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource)) - .isTrue(); + .isTrue(); } @Test public void terminateSystemAudioMode_systemAudioModeOff() throws Exception { - mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false); + mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false); assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse(); mMusicMute = false; HdmiCecMessage message = @@ -373,7 +376,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { @Test public void terminateSystemAudioMode_systemAudioModeOn() throws Exception { - mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true); + mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true); assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue(); mMusicMute = false; HdmiCecMessage expectedMessage = @@ -458,7 +461,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue(); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class)) - .isNotEmpty(); + .isNotEmpty(); } @Test @@ -473,7 +476,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue(); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class)) - .isNotEmpty(); + .isNotEmpty(); } @Test @@ -521,12 +524,37 @@ public class HdmiCecLocalDeviceAudioSystemTest { assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } + public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() { + HdmiCecMessage message = + HdmiCecMessageBuilder.buildSystemAudioModeRequest( + ADDR_TUNER_1, ADDR_AUDIO_SYSTEM, + mAvrPhysicalAddress, true); + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildFeatureAbortCommand( + ADDR_AUDIO_SYSTEM, + ADDR_TUNER_1, + Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, + Constants.ABORT_REFUSED); + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); + } + @Test - public void handleSetStreamPath_underCurrentDevice() { - assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(0); + public void handleSystemAudioModeRequest_fromNonTV_tVSupport() { HdmiCecMessage message = - HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetStreamPath(message)).isTrue(); - assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(1); + HdmiCecMessageBuilder.buildSystemAudioModeRequest( + ADDR_TUNER_1, ADDR_AUDIO_SYSTEM, + mAvrPhysicalAddress, true); + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildSetSystemAudioMode( + ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true); + mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true); + + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java new file mode 100644 index 000000000000..76f638cb4351 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.hdmi; + +import static com.android.server.hdmi.Constants.ADDR_TV; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Looper; +import android.os.test.TestLooper; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; + +@SmallTest +@RunWith(JUnit4.class) +/** Tests for {@link HdmiCecLocalDevicePlayback} class. */ +public class HdmiCecLocalDevicePlaybackTest { + + private HdmiControlService mHdmiControlService; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback; + private FakeNativeWrapper mNativeWrapper; + private Looper mMyLooper; + private TestLooper mTestLooper = new TestLooper(); + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + private int mPlaybackPhysicalAddress; + + @Before + public void setUp() { + mHdmiControlService = + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + @Override + void wakeUp() { + } + + @Override + boolean isControlEnabled() { + return true; + } + }; + + mMyLooper = mTestLooper.getLooper(); + mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService); + mHdmiCecLocalDevicePlayback.init(); + mHdmiControlService.setIoLooper(mMyLooper); + mNativeWrapper = new FakeNativeWrapper(); + mHdmiCecController = + HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); + mHdmiControlService.setCecController(mHdmiCecController); + mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + mLocalDevices.add(mHdmiCecLocalDevicePlayback); + mHdmiControlService.initPortInfo(); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + mNativeWrapper.clearResultMessages(); + mPlaybackPhysicalAddress = 0x2000; + mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress); + } + + @Test + public void handleSetStreamPath_underCurrentDevice() { + assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(0); + HdmiCecMessage message = + HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100); + assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue(); + assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(1); + } +} diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 38efc743fe34..3d7cbb51a3ad 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -657,9 +657,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return; } - if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) { - model.setStopped(); - } + model.setStopped(); + try { callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event); } catch (DeadObjectException e) { @@ -802,9 +801,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return; } - if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) { - modelData.setStopped(); - } + modelData.setStopped(); try { modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event); diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 2820836282a1..dcaa49996d0b 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -239,6 +239,30 @@ public final class Call { "android.telecom.event.HANDOVER_FAILED"; public static class Details { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = { "DIRECTION_" }, + value = {DIRECTION_UNKNOWN, DIRECTION_INCOMING, DIRECTION_OUTGOING}) + public @interface CallDirection {} + + /** + * Indicates that the call is neither and incoming nor an outgoing call. This can be the + * case for calls reported directly by a {@link ConnectionService} in special cases such as + * call handovers. + */ + public static final int DIRECTION_UNKNOWN = -1; + + /** + * Indicates that the call is an incoming call. + */ + public static final int DIRECTION_INCOMING = 0; + + /** + * Indicates that the call is an outgoing call. + */ + public static final int DIRECTION_OUTGOING = 1; + /** Call can currently be put on hold or unheld. */ public static final int CAPABILITY_HOLD = 0x00000001; @@ -519,6 +543,7 @@ public final class Call { private final Bundle mIntentExtras; private final long mCreationTimeMillis; private final CallIdentification mCallIdentification; + private final @CallDirection int mCallDirection; /** * Whether the supplied capabilities supports the specified capability. @@ -838,6 +863,14 @@ public final class Call { return mCallIdentification; } + /** + * Indicates whether the call is an incoming or outgoing call. + * @return The call's direction. + */ + public @CallDirection int getCallDirection() { + return mCallDirection; + } + @Override public boolean equals(Object o) { if (o instanceof Details) { @@ -859,7 +892,8 @@ public final class Call { areBundlesEqual(mExtras, d.mExtras) && areBundlesEqual(mIntentExtras, d.mIntentExtras) && Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) && - Objects.equals(mCallIdentification, d.mCallIdentification); + Objects.equals(mCallIdentification, d.mCallIdentification) && + Objects.equals(mCallDirection, d.mCallDirection); } return false; } @@ -881,7 +915,8 @@ public final class Call { mExtras, mIntentExtras, mCreationTimeMillis, - mCallIdentification); + mCallIdentification, + mCallDirection); } /** {@hide} */ @@ -902,7 +937,8 @@ public final class Call { Bundle extras, Bundle intentExtras, long creationTimeMillis, - CallIdentification callIdentification) { + CallIdentification callIdentification, + int callDirection) { mTelecomCallId = telecomCallId; mHandle = handle; mHandlePresentation = handlePresentation; @@ -920,6 +956,7 @@ public final class Call { mIntentExtras = intentExtras; mCreationTimeMillis = creationTimeMillis; mCallIdentification = callIdentification; + mCallDirection = callDirection; } /** {@hide} */ @@ -941,7 +978,8 @@ public final class Call { parcelableCall.getExtras(), parcelableCall.getIntentExtras(), parcelableCall.getCreationTimeMillis(), - parcelableCall.getCallIdentification()); + parcelableCall.getCallIdentification(), + parcelableCall.getCallDirection()); } @Override diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java index 97af06c1d64c..87834fd5109d 100644 --- a/telecomm/java/android/telecom/CallIdentification.java +++ b/telecomm/java/android/telecom/CallIdentification.java @@ -250,8 +250,8 @@ public final class CallIdentification implements Parcelable { mDetails = details; mPhoto = photo; mNuisanceConfidence = nuisanceConfidence; - mCallScreeningAppName = callScreeningPackageName; - mCallScreeningPackageName = callScreeningAppName; + mCallScreeningAppName = callScreeningAppName; + mCallScreeningPackageName = callScreeningPackageName; } private String mName; @@ -430,4 +430,22 @@ public final class CallIdentification implements Parcelable { return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence, mCallScreeningAppName, mCallScreeningPackageName); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[CallId mName="); + sb.append(Log.pii(mName)); + sb.append(", mDesc="); + sb.append(mDescription); + sb.append(", mDet="); + sb.append(mDetails); + sb.append(", conf="); + sb.append(mNuisanceConfidence); + sb.append(", appName="); + sb.append(mCallScreeningAppName); + sb.append(", pkgName="); + sb.append(mCallScreeningPackageName); + return sb.toString(); + } } diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java index be96b3cac6f6..826ad82dfbb2 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -21,6 +21,7 @@ import android.annotation.SdkConstant; import android.app.Service; import android.content.ComponentName; import android.content.Intent; +import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -33,8 +34,9 @@ import com.android.internal.telecom.ICallScreeningService; /** * This service can be implemented by the default dialer (see - * {@link TelecomManager#getDefaultDialerPackage()}) to allow or disallow incoming calls before - * they are shown to a user. + * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow + * incoming calls before they are shown to a user. This service can also provide + * {@link CallIdentification} information for calls. * <p> * Below is an example manifest registration for a {@code CallScreeningService}. * <pre> @@ -56,6 +58,34 @@ import com.android.internal.telecom.ICallScreeningService; * information about a {@link Call.Details call} which will be shown to the user in the * Dialer app.</li> * </ol> + * <p> + * <h2>Becoming the {@link CallScreeningService}</h2> + * Telecom will bind to a single app chosen by the user which implements the + * {@link CallScreeningService} API when there are new incoming and outgoing calls. + * <p> + * The code snippet below illustrates how your app can request that it fills the call screening + * role. + * <pre> + * {@code + * private static final int REQUEST_ID = 1; + * + * public void requestRole() { + * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE); + * Intent intent = roleManager.createRequestRoleIntent("android.app.role.CALL_SCREENING_APP"); + * startActivityForResult(intent, REQUEST_ID); + * } + * + * @Override + * public void onActivityResult(int requestCode, int resultCode, Intent data) { + * if (requestCode == REQUEST_ID) { + * if (resultCode == android.app.Activity.RESULT_OK) { + * // Your app is now the call screening app + * } else { + * // Your app is not the call screening app + * } + * } + * } + * </pre> */ public abstract class CallScreeningService extends Service { /** @@ -222,30 +252,46 @@ public abstract class CallScreeningService extends Service { } /** - * Called when a new incoming call is added. - * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)} - * should be called to allow or disallow the call. + * Called when a new incoming or outgoing call is added which is not in the user's contact list. + * <p> + * A {@link CallScreeningService} must indicate whether an incoming call is allowed or not by + * calling + * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}. + * Your app can tell if a call is an incoming call by checking to see if + * {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}. + * <p> + * For incoming or outgoing calls, the {@link CallScreeningService} can call + * {@link #provideCallIdentification(Call.Details, CallIdentification)} in order to provide + * {@link CallIdentification} for the call. * <p> * Note: The {@link Call.Details} instance provided to a call screening service will only have * the following properties set. The rest of the {@link Call.Details} properties will be set to * their default value or {@code null}. * <ul> - * <li>{@link Call.Details#getState()}</li> + * <li>{@link Call.Details#getCallDirection()}</li> * <li>{@link Call.Details#getConnectTimeMillis()}</li> * <li>{@link Call.Details#getCreationTimeMillis()}</li> * <li>{@link Call.Details#getHandle()}</li> * <li>{@link Call.Details#getHandlePresentation()}</li> * </ul> + * <p> + * Only calls where the {@link Call.Details#getHandle() handle} {@link Uri#getScheme() scheme} + * is {@link PhoneAccount#SCHEME_TEL} are passed for call + * screening. Further, only calls which are not in the user's contacts are passed for + * screening. For outgoing calls, no post-dial digits are passed. * - * @param callDetails Information about a new incoming call, see {@link Call.Details}. + * @param callDetails Information about a new call, see {@link Call.Details}. */ public abstract void onScreenCall(@NonNull Call.Details callDetails); /** - * Responds to the given call, either allowing it or disallowing it. + * Responds to the given incoming call, either allowing it or disallowing it. * <p> * The {@link CallScreeningService} calls this method to inform the system whether the call * should be silently blocked or not. + * <p> + * Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is + * {@link Call.Details#DIRECTION_INCOMING}. * * @param callDetails The call to allow. * <p> diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 1aeeca73c0b9..f5f0af7e4666 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -40,11 +40,30 @@ import java.util.Collections; import java.util.List; /** - * This service is implemented by any app that wishes to provide the user-interface for managing - * phone calls. Telecom binds to this service while there exists a live (active or incoming) call, - * and uses it to notify the in-call app of any live and recently disconnected calls. An app must - * first be set as the default phone app (See {@link TelecomManager#getDefaultDialerPackage()}) - * before the telecom service will bind to its {@code InCallService} implementation. + * This service is implemented by an app that wishes to provide functionality for managing + * phone calls. + * <p> + * There are three types of apps which Telecom can bind to when there exists a live (active or + * incoming) call: + * <ol> + * <li>Default Dialer/Phone app - the default dialer/phone app is one which provides the + * in-call user interface while the device is in a call. A device is bundled with a system + * provided default dialer/phone app. The user may choose a single app to take over this role + * from the system app.</li> + * <li>Default Car-mode Dialer/Phone app - the default car-mode dialer/phone app is one which + * provides the in-call user interface while the device is in a call and the device is in car + * mode. The user may choose a single app to fill this role.</li> + * <li>Call Companion app - a call companion app is one which provides no user interface itself, + * but exposes call information to another display surface, such as a wearable device. The + * user may choose multiple apps to fill this role.</li> + * </ol> + * <p> + * Apps which wish to fulfill one of the above roles use the {@link android.app.role.RoleManager} + * to request that they fill the desired role. + * + * <h2>Becoming the Default Phone App</h2> + * An app filling the role of the default phone app provides a user interface while the device is in + * a call, and the device is not in car mode. * <p> * Below is an example manifest registration for an {@code InCallService}. The meta-data * {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular @@ -82,12 +101,34 @@ import java.util.List; * } * </pre> * <p> - * When a user installs your application and runs it for the first time, you should prompt the user - * to see if they would like your application to be the new default phone app. See the - * {@link TelecomManager#ACTION_CHANGE_DEFAULT_DIALER} intent documentation for more information on - * how to do this. + * When a user installs your application and runs it for the first time, you should use the + * {@link android.app.role.RoleManager} to prompt the user to see if they would like your app to + * be the new default phone app. + * <p id="requestRole"> + * The code below shows how your app can request to become the default phone/dialer app: + * <pre> + * {@code + * private static final int REQUEST_ID = 1; + * + * public void requestRole() { + * RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE); + * Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER); + * startActivityForResult(intent, REQUEST_ID); + * } + * + * @Override + * public void onActivityResult(int requestCode, int resultCode, Intent data) { + * if (requestCode == REQUEST_ID) { + * if (resultCode == android.app.Activity.RESULT_OK) { + * // Your app is now the default dialer app + * } else { + * // Your app is not the default dialer app + * } + * } + * } + * </pre> * <p id="incomingCallNotification"> - * <h2>Showing the Incoming Call Notification</h2> + * <h3>Showing the Incoming Call Notification</h3> * When your app receives a new incoming call via {@link InCallService#onCallAdded(Call)}, it is * responsible for displaying an incoming call UI for the incoming call. It should do this using * {@link android.app.NotificationManager} APIs to post a new incoming call notification. @@ -121,7 +162,7 @@ import java.util.List; * heads-up notification if the user is actively using the phone. When the user is not using the * phone, your full-screen incoming call UI is used instead. * For example: - * <pre><code> + * <pre><code>{@code * // Create an intent which triggers your fullscreen incoming call user interface. * Intent intent = new Intent(Intent.ACTION_MAIN, null); * intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); @@ -151,7 +192,49 @@ import java.util.List; * NotificationManager notificationManager = mContext.getSystemService( * NotificationManager.class); * notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build()); - * </code></pre> + * }</pre> + * <p> + * <h2>Becoming the Default Car-mode Phone App</h2> + * An app filling the role of the default car-mode dialer/phone app provides a user interface while + * the device is in a call, and in car mode. See + * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information about car mode. + * When the device is in car mode, Telecom binds to the default car-mode dialer/phone app instead + * of the usual dialer/phone app. + * <p> + * Similar to the requirements for becoming the default dialer/phone app, your app must declare a + * manifest entry for its {@link InCallService} implementation. Your manifest entry should ensure + * the following conditions are met: + * <ul> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li> + * <li>Set the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} metadata to + * {@code true}<li> + * <li>Your app must request the permission + * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li> + * </ul> + * <p> + * Your app should request to fill the role {@code android.app.role.CAR_MODE_DIALER_APP} in order to + * become the default (see <a href="#requestRole">above</a> for how to request your app fills this + * role). + * + * <h2>Becoming a Call Companion App</h2> + * An app which fills the companion app role does not directly provide a user interface while the + * device is in a call. Instead, it is typically used to relay information about calls to another + * display surface, such as a wearable device. + * <p> + * Similar to the requirements for becoming the default dialer/phone app, your app must declare a + * manifest entry for its {@link InCallService} implementation. Your manifest entry should + * ensure the following conditions are met: + * <ul> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li> + * <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} + * metadata.</li> + * <li>Your app must request the permission + * {@link android.Manifest.permission.CALL_COMPANION_APP}.</li> + * </ul> + * <p> + * Your app should request to fill the role {@code android.app.role.CALL_COMPANION_APP} in order to + * become a call companion app (see <a href="#requestRole">above</a> for how to request your app + * fills this role). */ public abstract class InCallService extends Service { diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 911786e455c2..f7dec83c3ace 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; +import android.telecom.Call.Details.CallDirection; import java.util.ArrayList; import java.util.Collections; @@ -64,6 +65,7 @@ public final class ParcelableCall implements Parcelable { private final Bundle mExtras; private final long mCreationTimeMillis; private final CallIdentification mCallIdentification; + private final int mCallDirection; public ParcelableCall( String id, @@ -92,7 +94,8 @@ public final class ParcelableCall implements Parcelable { Bundle intentExtras, Bundle extras, long creationTimeMillis, - CallIdentification callIdentification) { + CallIdentification callIdentification, + int callDirection) { mId = id; mState = state; mDisconnectCause = disconnectCause; @@ -120,6 +123,7 @@ public final class ParcelableCall implements Parcelable { mExtras = extras; mCreationTimeMillis = creationTimeMillis; mCallIdentification = callIdentification; + mCallDirection = callDirection; } /** The unique ID of the call. */ @@ -318,6 +322,13 @@ public final class ParcelableCall implements Parcelable { return mCallIdentification; } + /** + * Indicates whether the call is an incoming or outgoing call. + */ + public @CallDirection int getCallDirection() { + return mCallDirection; + } + /** Responsible for creating ParcelableCall objects for deserialized Parcels. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public static final Parcelable.Creator<ParcelableCall> CREATOR = @@ -356,6 +367,7 @@ public final class ParcelableCall implements Parcelable { ParcelableRttCall rttCall = source.readParcelable(classLoader); long creationTimeMillis = source.readLong(); CallIdentification callIdentification = source.readParcelable(classLoader); + int callDirection = source.readInt(); return new ParcelableCall( id, state, @@ -383,7 +395,8 @@ public final class ParcelableCall implements Parcelable { intentExtras, extras, creationTimeMillis, - callIdentification); + callIdentification, + callDirection); } @Override @@ -429,6 +442,7 @@ public final class ParcelableCall implements Parcelable { destination.writeParcelable(mRttCall, 0); destination.writeLong(mCreationTimeMillis); destination.writeParcelable(mCallIdentification, 0); + destination.writeInt(mCallDirection); } @Override |